کشو (Drawer)

کامپوننت کشو برای نمایش پنل‌های کشویی از لبه‌های صفحه استفاده می‌شود. این کامپوننت برای موبایل بهینه شده و از حرکات لمسی پشتیبانی می‌کند.

نصب (Installation)

1npx @quark-lab/rad-ui add drawer

نمونه‌ها (Examples)

استفاده پایه (Basic Usage)

ساده‌ترین حالت استفاده از کشو که از پایین صفحه باز می‌شود

مشاهده کد
1import {
2  Drawer,
3  DrawerClose,
4  DrawerContent,
5  DrawerDescription,
6  DrawerFooter,
7  DrawerHeader,
8  DrawerTitle,
9  DrawerTrigger,
10} from "@/components/ui/drawer";
11import { Button } from "@/components/ui/button";
12
13export function DrawerDemo() {
14  return (
15    <Drawer>
16      <DrawerTrigger asChild>
17        <Button variant="outline">باز کردن کشو</Button>
18      </DrawerTrigger>
19      <DrawerContent>
20        <div className="mx-auto w-full max-w-sm">
21          <DrawerHeader>
22            <DrawerTitle>عنوان کشو</DrawerTitle>
23            <DrawerDescription>
24              این یک توضیح کوتاه درباره محتوای کشو است
25            </DrawerDescription>
26          </DrawerHeader>
27          <div className="p-4">
28            <p>محتوای اصلی کشو در اینجا قرار می‌گیرد</p>
29          </div>
30          <DrawerFooter>
31            <Button>تایید</Button>
32            <DrawerClose asChild>
33              <Button variant="outline">انصراف</Button>
34            </DrawerClose>
35          </DrawerFooter>
36        </div>
37      </DrawerContent>
38    </Drawer>
39  );
40}

جهت‌ها (Directions)

کشو می‌تواند از چهار جهت باز شود: بالا، راست، پایین و چپ

مشاهده کد
1import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
2import { Button } from "@/components/ui/button";
3
4const DRAWER_SIDES = ["top", "right", "bottom", "left"] as const;
5
6export function DrawerDirections() {
7  return (
8    <div className="flex flex-wrap gap-2">
9      {DRAWER_SIDES.map((side) => (
10        <Drawer key={side} direction={side}>
11          <DrawerTrigger asChild>
12            <Button variant="outline" className="capitalize">
13              {side}
14            </Button>
15          </DrawerTrigger>
16          <DrawerContent>
17            <DrawerHeader>
18              <DrawerTitle>کشو از {side}</DrawerTitle>
19            </DrawerHeader>
20            <div className="p-4">محتوای کشو</div>
21            <DrawerFooter>
22              <DrawerClose asChild>
23                <Button variant="outline">بستن</Button>
24              </DrawerClose>
25            </DrawerFooter>
26          </DrawerContent>
27        </Drawer>
28      ))}
29    </div>
30  );
31}

با فرم (With Form)

کشو با فرم ورودی برای ویرایش اطلاعات

مشاهده کد
1<Drawer>
2  <DrawerTrigger asChild>
3    <Button variant="outline">ویرایش پروفایل</Button>
4  </DrawerTrigger>
5  <DrawerContent>
6    <div className="mx-auto w-full max-w-sm">
7      <DrawerHeader>
8        <DrawerTitle>ویرایش پروفایل</DrawerTitle>
9        <DrawerDescription>
10          تغییرات خود را اعمال کنید و دکمه ذخیره را بزنید
11        </DrawerDescription>
12      </DrawerHeader>
13      <div className="p-4 space-y-4">
14        <div className="space-y-2">
15          <Label htmlFor="name">نام</Label>
16          <Input id="name" defaultValue="علی کاوسی" />
17        </div>
18        <div className="space-y-2">
19          <Label htmlFor="email">ایمیل</Label>
20          <Input id="email" type="email" defaultValue="ali@example.com" />
21        </div>
22      </div>
23      <DrawerFooter>
24        <Button>ذخیره تغییرات</Button>
25        <DrawerClose asChild>
26          <Button variant="outline">انصراف</Button>
27        </DrawerClose>
28      </DrawerFooter>
29    </div>
30  </DrawerContent>
31</Drawer>

محتوای قابل اسکرول (Scrollable Content)

کشو با محتوای طولانی که قابل اسکرول است

مشاهده کد
1<Drawer direction="right">
2  <DrawerTrigger asChild>
3    <Button variant="outline">محتوای قابل اسکرول</Button>
4  </DrawerTrigger>
5  <DrawerContent>
6    <DrawerHeader>
7      <DrawerTitle>محتوای طولانی</DrawerTitle>
8      <DrawerDescription>این کشو محتوای قابل اسکرول دارد</DrawerDescription>
9    </DrawerHeader>
10    <div className="overflow-y-auto px-4 flex-1">
11      {Array.from({ length: 10 }).map((_, index) => (
12        <p key={index} className="mb-4 leading-relaxed">
13          لورم ایپسوم متن ساختگی...
14        </p>
15      ))}
16    </div>
17    <DrawerFooter>
18      <DrawerClose asChild>
19        <Button variant="outline">بستن</Button>
20      </DrawerClose>
21    </DrawerFooter>
22  </DrawerContent>
23</Drawer>

کنترل شده (Controlled)

کنترل وضعیت باز/بسته کشو با استفاده از state

وضعیت کشو: بسته

مشاهده کد
1const [open, setOpen] = useState(false);
2
3<Drawer open={open} onOpenChange={setOpen}>
4  <DrawerTrigger asChild>
5    <Button variant="outline">کشو کنترل‌شده</Button>
6  </DrawerTrigger>
7  <DrawerContent>
8    <DrawerHeader>
9      <DrawerTitle>کشو کنترل‌شده</DrawerTitle>
10      <DrawerDescription>
11        این کشو با استفاده از state کنترل می‌شود
12      </DrawerDescription>
13    </DrawerHeader>
14    <DrawerFooter>
15      <Button onClick={() => setOpen(false)}>بستن با کد</Button>
16    </DrawerFooter>
17  </DrawerContent>
18</Drawer>

دیالوگ واکنش‌گرا (Responsive Dialog)

ترکیب Dialog و Drawer برای ساخت یک دیالوگ واکنش‌گرا. در دسکتاپ Dialog و در موبایل Drawer نمایش داده می‌شود.

مشاهده کد
1import * as React from "react";
2import { Button } from "@/components/ui/button";
3import {
4  Dialog,
5  DialogContent,
6  DialogDescription,
7  DialogHeader,
8  DialogTitle,
9  DialogTrigger,
10} from "@/components/ui/dialog";
11import {
12  Drawer,
13  DrawerClose,
14  DrawerContent,
15  DrawerDescription,
16  DrawerFooter,
17  DrawerHeader,
18  DrawerTitle,
19  DrawerTrigger,
20} from "@/components/ui/drawer";
21import { Input } from "@/components/ui/input";
22import { Label } from "@/components/ui/label";
23import { cn } from "@/lib/utils";
24import { useMediaQuery } from "@/hooks/use-media-query";
25
26export function ResponsiveDialog() {
27  const [open, setOpen] = React.useState(false);
28  const isDesktop = useMediaQuery("(min-width: 768px)");
29
30  if (isDesktop) {
31    return (
32      <Dialog open={open} onOpenChange={setOpen}>
33        <DialogTrigger asChild>
34          <Button variant="outline">ویرایش پروفایل</Button>
35        </DialogTrigger>
36        <DialogContent className="sm:max-w-[425px]">
37          <DialogHeader>
38            <DialogTitle>ویرایش پروفایل</DialogTitle>
39            <DialogDescription>
40              تغییرات خود را اعمال کنید. پس از اتمام روی ذخیره کلیک کنید.
41            </DialogDescription>
42          </DialogHeader>
43          <ProfileForm />
44        </DialogContent>
45      </Dialog>
46    );
47  }
48
49  return (
50    <Drawer open={open} onOpenChange={setOpen}>
51      <DrawerTrigger asChild>
52        <Button variant="outline">ویرایش پروفایل</Button>
53      </DrawerTrigger>
54      <DrawerContent>
55        <DrawerHeader className="text-start">
56          <DrawerTitle>ویرایش پروفایل</DrawerTitle>
57          <DrawerDescription>
58            تغییرات خود را اعمال کنید. پس از اتمام روی ذخیره کلیک کنید.
59          </DrawerDescription>
60        </DrawerHeader>
61        <ProfileForm className="px-4" />
62        <DrawerFooter className="pt-2">
63          <DrawerClose asChild>
64            <Button variant="outline">انصراف</Button>
65          </DrawerClose>
66        </DrawerFooter>
67      </DrawerContent>
68    </Drawer>
69  );
70}

مرجع API (API Reference)

Drawer

پراپ (Prop)نوع (Type)پیش‌فرض (Default)توضیحات (Description)
openbooleanundefinedوضعیت باز/بسته بودن (کنترل‌شده)
onOpenChange(open: boolean) => voidundefinedتابع فراخوانی هنگام تغییر وضعیت
direction"top" | "right" | "bottom" | "left""bottom"جهت باز شدن کشو
shouldScaleBackgroundbooleantrueکوچک شدن پس‌زمینه هنگام باز شدن
modalbooleantrueحالت مودال (بلاک کردن تعامل با پس‌زمینه)

DrawerContent

پراپ (Prop)نوع (Type)پیش‌فرض (Default)توضیحات (Description)
classNamestringundefinedکلاس‌های CSS اضافی
childrenReact.ReactNodeundefinedمحتوای کشو

دسترسی‌پذیری (Accessibility)

کیبورد (Keyboard)

  • Escape - بستن کشو
  • Tab - حرکت بین المان‌های قابل فوکوس داخل کشو
  • Shift + Tab - حرکت به عقب بین المان‌ها

تله فوکوس (Focus Trap)

وقتی کشو باز است، فوکوس در داخل آن محبوس می‌شود و کاربر نمی‌تواند با Tab به خارج از کشو برود

پشتیبانی از لمس (Touch Support)

کشو از حرکات لمسی پشتیبانی می‌کند. کاربران می‌توانند با کشیدن انگشت کشو را ببندند

نقش‌های ARIA

کشو از role="dialog" و aria-modal="true" استفاده می‌کند

بهترین شیوه‌ها (Best Practices)

استفاده در موبایل (Mobile First)

کشو برای تجربه موبایل بهینه شده است. برای دسکتاپ می‌توانید از Dialog استفاده کنید و بر اساس سایز صفحه بین آن‌ها سوییچ کنید

جهت مناسب (Appropriate Direction)

کشو از پایین برای اقدامات سریع و منوها مناسب است. کشو از کنار برای پنل‌های ناوبری و فیلترها بهتر است

عنوان واضح (Clear Title)

همیشه از DrawerTitle استفاده کنید تا کاربران صفحه‌خوان بتوانند محتوای کشو را درک کنند

دکمه‌های اقدام (Action Buttons)

دکمه‌های اصلی و ثانویه را در DrawerFooter قرار دهید. دکمه انصراف همیشه باید وجود داشته باشد

محتوای طولانی (Long Content)

برای محتوای طولانی از overflow-y-auto استفاده کنید. فوتر همیشه باید قابل مشاهده باقی بماند

نحوه استفاده (Usage)

1import { useState } from "react";
2import {
3  Drawer,
4  DrawerClose,
5  DrawerContent,
6  DrawerDescription,
7  DrawerFooter,
8  DrawerHeader,
9  DrawerTitle,
10  DrawerTrigger,
11} from "@/components/ui/drawer";
12import { Button } from "@/components/ui/button";
13
14export default function MyComponent() {
15  const [open, setOpen] = useState(false);
16
17  return (
18    <div>
19      {/* Basic Drawer */}
20      <Drawer>
21        <DrawerTrigger asChild>
22          <Button>Open Drawer</Button>
23        </DrawerTrigger>
24        <DrawerContent>
25          <DrawerHeader>
26            <DrawerTitle>Title</DrawerTitle>
27            <DrawerDescription>Description</DrawerDescription>
28          </DrawerHeader>
29          <div className="p-4">Content goes here</div>
30          <DrawerFooter>
31            <Button>Submit</Button>
32            <DrawerClose asChild>
33              <Button variant="outline">Cancel</Button>
34            </DrawerClose>
35          </DrawerFooter>
36        </DrawerContent>
37      </Drawer>
38
39      {/* Side Drawer */}
40      <Drawer direction="right">
41        <DrawerTrigger asChild>
42          <Button>Open Side Drawer</Button>
43        </DrawerTrigger>
44        <DrawerContent>
45          <DrawerHeader>
46            <DrawerTitle>Side Panel</DrawerTitle>
47          </DrawerHeader>
48          <div className="p-4">Side content</div>
49        </DrawerContent>
50      </Drawer>
51
52      {/* Controlled Drawer */}
53      <Drawer open={open} onOpenChange={setOpen}>
54        <DrawerTrigger asChild>
55          <Button>Controlled</Button>
56        </DrawerTrigger>
57        <DrawerContent>
58          <DrawerHeader>
59            <DrawerTitle>Controlled Drawer</DrawerTitle>
60          </DrawerHeader>
61          <Button onClick={() => setOpen(false)}>
62            Close programmatically
63          </Button>
64        </DrawerContent>
65      </Drawer>
66    </div>
67  );
68}

مثال‌های پیشرفته (Advanced Examples)

کشو تودرتو (Nested Drawer)

باز کردن یک کشو از داخل کشوی دیگر

مشاهده کد
1<Drawer direction="right">
2  <DrawerTrigger asChild>
3    <Button variant="outline">کشو تودرتو</Button>
4  </DrawerTrigger>
5  <DrawerContent>
6    <DrawerHeader>
7      <DrawerTitle>کشو اول</DrawerTitle>
8    </DrawerHeader>
9    <div className="p-4">
10      <Drawer direction="right">
11        <DrawerTrigger asChild>
12          <Button>باز کردن کشو دوم</Button>
13        </DrawerTrigger>
14        <DrawerContent>
15          <DrawerHeader>
16            <DrawerTitle>کشو دوم</DrawerTitle>
17          </DrawerHeader>
18          <DrawerFooter>
19            <DrawerClose asChild>
20              <Button variant="outline">بستن</Button>
21            </DrawerClose>
22          </DrawerFooter>
23        </DrawerContent>
24      </Drawer>
25    </div>
26    <DrawerFooter>
27      <DrawerClose asChild>
28        <Button variant="outline">بستن</Button>
29      </DrawerClose>
30    </DrawerFooter>
31  </DrawerContent>
32</Drawer>