کشو (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) |
|---|---|---|---|
open | boolean | undefined | وضعیت باز/بسته بودن (کنترلشده) |
onOpenChange | (open: boolean) => void | undefined | تابع فراخوانی هنگام تغییر وضعیت |
direction | "top" | "right" | "bottom" | "left" | "bottom" | جهت باز شدن کشو |
shouldScaleBackground | boolean | true | کوچک شدن پسزمینه هنگام باز شدن |
modal | boolean | true | حالت مودال (بلاک کردن تعامل با پسزمینه) |
DrawerContent
| پراپ (Prop) | نوع (Type) | پیشفرض (Default) | توضیحات (Description) |
|---|---|---|---|
className | string | undefined | کلاسهای CSS اضافی |
children | React.ReactNode | undefined | محتوای کشو |
دسترسیپذیری (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>