دیالوگ (Dialog)
کامپوننت دیالوگ برای نمایش محتوای مهم در یک پنجره مودال استفاده میشود که توجه کاربر را به خود جلب میکند
نصب (Installation)
1npx @quark-lab/rad-ui add dialogنمونهها (Examples)
استفاده پایه (Basic Usage)
سادهترین حالت استفاده از دیالوگ با عنوان و توضیحات
مشاهده کد
1import {
2 Dialog,
3 DialogContent,
4 DialogDescription,
5 DialogHeader,
6 DialogTitle,
7 DialogTrigger,
8} from "@/components/ui/dialog";
9import { Button } from "@/components/ui/button";
10
11export function DialogDemo() {
12 return (
13 <Dialog>
14 <DialogTrigger asChild>
15 <Button variant="outline">باز کردن دیالوگ</Button>
16 </DialogTrigger>
17 <DialogContent className="sm:max-w-md">
18 <DialogHeader>
19 <DialogTitle>عنوان دیالوگ</DialogTitle>
20 <DialogDescription>
21 این یک توضیح کوتاه درباره محتوای دیالوگ است
22 </DialogDescription>
23 </DialogHeader>
24 <div className="py-4">
25 <p>محتوای اصلی دیالوگ</p>
26 </div>
27 </DialogContent>
28 </Dialog>
29 );
30}با فرم (With Form)
دیالوگ با فرم ورودی برای ویرایش اطلاعات
مشاهده کد
1<Dialog>
2 <DialogTrigger asChild>
3 <Button variant="outline">ویرایش پروفایل</Button>
4 </DialogTrigger>
5 <DialogContent className="sm:max-w-md">
6 <DialogHeader>
7 <DialogTitle>ویرایش پروفایل</DialogTitle>
8 <DialogDescription>
9 تغییرات خود را اعمال کنید و دکمه ذخیره را بزنید
10 </DialogDescription>
11 </DialogHeader>
12 <div className="grid gap-4 py-4">
13 <div className="grid grid-cols-4 items-center gap-4">
14 <Label htmlFor="name">نام</Label>
15 <Input id="name" defaultValue="علی کاوسی" className="col-span-3" />
16 </div>
17 <div className="grid grid-cols-4 items-center gap-4">
18 <Label htmlFor="username">نام کاربری</Label>
19 <Input id="username" defaultValue="@alikawosi" className="col-span-3" />
20 </div>
21 </div>
22 <DialogFooter>
23 <DialogClose asChild>
24 <Button variant="outline">انصراف</Button>
25 </DialogClose>
26 <Button type="submit">ذخیره تغییرات</Button>
27 </DialogFooter>
28 </DialogContent>
29</Dialog>دکمه بستن سفارشی (Custom Close Button)
استفاده از DialogClose برای ایجاد دکمه بستن سفارشی در فوتر
مشاهده کد
1<Dialog>
2 <DialogTrigger asChild>
3 <Button variant="outline">اشتراکگذاری لینک</Button>
4 </DialogTrigger>
5 <DialogContent className="sm:max-w-md">
6 <DialogHeader>
7 <DialogTitle>اشتراکگذاری لینک</DialogTitle>
8 <DialogDescription>
9 هر کسی که این لینک را داشته باشد میتواند محتوا را ببیند
10 </DialogDescription>
11 </DialogHeader>
12 <div className="flex items-center gap-2">
13 <Input defaultValue="https://..." readOnly className="flex-1" />
14 <Button type="button" size="sm">کپی</Button>
15 </div>
16 <DialogFooter className="sm:justify-start">
17 <DialogClose asChild>
18 <Button type="button" variant="outline">بستن</Button>
19 </DialogClose>
20 </DialogFooter>
21 </DialogContent>
22</Dialog>بدون دکمه بستن (No Close Button)
مخفی کردن دکمه X در گوشه بالای دیالوگ با showCloseButton=false
مشاهده کد
1<DialogContent showCloseButton={false}>
2 <DialogHeader>
3 <DialogTitle>بدون دکمه بستن</DialogTitle>
4 <DialogDescription>
5 این دیالوگ دکمه X در گوشه بالا ندارد
6 </DialogDescription>
7 </DialogHeader>
8 <DialogFooter>
9 <DialogClose asChild>
10 <Button variant="outline">بستن</Button>
11 </DialogClose>
12 </DialogFooter>
13</DialogContent>کنترل شده (Controlled)
کنترل وضعیت باز/بسته دیالوگ با استفاده از state
وضعیت دیالوگ: بسته
مشاهده کد
1const [open, setOpen] = useState(false);
2
3<Dialog open={open} onOpenChange={setOpen}>
4 <DialogTrigger asChild>
5 <Button variant="outline">دیالوگ کنترلشده</Button>
6 </DialogTrigger>
7 <DialogContent>
8 <DialogHeader>
9 <DialogTitle>دیالوگ کنترلشده</DialogTitle>
10 <DialogDescription>
11 این دیالوگ با استفاده از state کنترل میشود
12 </DialogDescription>
13 </DialogHeader>
14 <DialogFooter>
15 <Button onClick={() => setOpen(false)}>بستن با کد</Button>
16 </DialogFooter>
17 </DialogContent>
18</Dialog>محتوای قابل اسکرول (Scrollable Content)
دیالوگ با محتوای طولانی که قابل اسکرول است
مشاهده کد
1<DialogContent>
2 <DialogHeader>
3 <DialogTitle>محتوای قابل اسکرول</DialogTitle>
4 <DialogDescription>...</DialogDescription>
5 </DialogHeader>
6 <div className="max-h-[50vh] overflow-y-auto -mx-6 px-6">
7 {/* Long content here */}
8 </div>
9 <DialogFooter>
10 <DialogClose asChild>
11 <Button variant="outline">بستن</Button>
12 </DialogClose>
13 </DialogFooter>
14</DialogContent>فوتر ثابت (Sticky Footer)
دیالوگ با فوتر ثابت که همیشه قابل مشاهده است در حین اسکرول
مشاهده کد
1<DialogContent className="sm:max-w-md flex flex-col max-h-[85vh]">
2 <DialogHeader>
3 <DialogTitle>فوتر ثابت</DialogTitle>
4 <DialogDescription>...</DialogDescription>
5 </DialogHeader>
6 <div className="flex-1 overflow-y-auto -mx-6 px-6">
7 {/* Scrollable content */}
8 </div>
9 <DialogFooter className="border-t border-border pt-4 -mx-6 px-6 -mb-6 pb-6 bg-background">
10 <DialogClose asChild>
11 <Button variant="outline">انصراف</Button>
12 </DialogClose>
13 <Button>تایید</Button>
14 </DialogFooter>
15</DialogContent>مرجع API (API Reference)
Dialog
| پراپ (Prop) | نوع (Type) | پیشفرض (Default) | توضیحات (Description) |
|---|---|---|---|
open | boolean | undefined | وضعیت باز/بسته بودن (کنترلشده) |
defaultOpen | boolean | false | وضعیت پیشفرض (غیرکنترلشده) |
onOpenChange | (open: boolean) => void | undefined | تابع فراخوانی هنگام تغییر وضعیت |
modal | boolean | true | حالت مودال (بلاک کردن تعامل با پسزمینه) |
DialogContent
| پراپ (Prop) | نوع (Type) | پیشفرض (Default) | توضیحات (Description) |
|---|---|---|---|
showCloseButton | boolean | true | نمایش دکمه بستن در گوشه بالا |
onEscapeKeyDown | (event) => void | undefined | هندلر فشردن کلید Escape |
onPointerDownOutside | (event) => void | undefined | هندلر کلیک خارج از دیالوگ |
دسترسیپذیری (Accessibility)
کیبورد (Keyboard)
Escape- بستن دیالوگTab- حرکت بین المانهای قابل فوکوس داخل دیالوگShift + Tab- حرکت به عقب بین المانها
تله فوکوس (Focus Trap)
وقتی دیالوگ باز است، فوکوس در داخل آن محبوس میشود و کاربر نمیتواند با Tab به خارج از دیالوگ برود
بازگشت فوکوس (Focus Return)
پس از بستن دیالوگ، فوکوس به المانی که دیالوگ را باز کرده بود برمیگردد
نقشهای ARIA
دیالوگ از role="dialog" و aria-modal="true" استفاده میکند
بهترین شیوهها (Best Practices)
عنوان واضح (Clear Title)
همیشه از DialogTitle استفاده کنید تا کاربران صفحهخوان بتوانند محتوای دیالوگ را درک کنند
توضیحات کمکی (Description)
از DialogDescription برای توضیح هدف دیالوگ استفاده کنید، مخصوصاً برای اقدامات مخرب
دکمههای اقدام (Action Buttons)
دکمههای اصلی و ثانویه را در DialogFooter قرار دهید. دکمه انصراف همیشه باید وجود داشته باشد
اندازه مناسب (Appropriate Size)
از className برای تنظیم عرض دیالوگ استفاده کنید. برای فرمهای کوچک از sm:max-w-sm و برای محتوای بیشتر از sm:max-w-lg استفاده کنید
محتوای طولانی (Long Content)
برای محتوای طولانی از یک div با max-h و overflow-y-auto استفاده کنید تا دیالوگ از صفحه خارج نشود
نحوه استفاده (Usage)
1import { useState } from "react";
2import {
3 Dialog,
4 DialogContent,
5 DialogDescription,
6 DialogFooter,
7 DialogHeader,
8 DialogTitle,
9 DialogTrigger,
10 DialogClose,
11} from "@/components/ui/dialog";
12import { Button } from "@/components/ui/button";
13
14export default function MyComponent() {
15 const [open, setOpen] = useState(false);
16
17 return (
18 <div>
19 {/* Basic Dialog */}
20 <Dialog>
21 <DialogTrigger asChild>
22 <Button>Open Dialog</Button>
23 </DialogTrigger>
24 <DialogContent>
25 <DialogHeader>
26 <DialogTitle>Title</DialogTitle>
27 <DialogDescription>Description</DialogDescription>
28 </DialogHeader>
29 <p>Content goes here</p>
30 <DialogFooter>
31 <DialogClose asChild>
32 <Button variant="outline">Cancel</Button>
33 </DialogClose>
34 <Button>Confirm</Button>
35 </DialogFooter>
36 </DialogContent>
37 </Dialog>
38
39 {/* Controlled Dialog */}
40 <Dialog open={open} onOpenChange={setOpen}>
41 <DialogTrigger asChild>
42 <Button>Controlled</Button>
43 </DialogTrigger>
44 <DialogContent>
45 <DialogHeader>
46 <DialogTitle>Controlled Dialog</DialogTitle>
47 </DialogHeader>
48 <Button onClick={() => setOpen(false)}>
49 Close programmatically
50 </Button>
51 </DialogContent>
52 </Dialog>
53 </div>
54 );
55}مثالهای پیشرفته (Advanced Examples)
دیالوگ تایید حذف (Delete Confirmation)
نمایش دیالوگ تایید قبل از انجام عملیات حذف
1const [open, setOpen] = useState(false);
2const [isDeleting, setIsDeleting] = useState(false);
3
4const handleDelete = async () => {
5 setIsDeleting(true);
6 try {
7 await deleteItem(itemId);
8 setOpen(false);
9 toast.success("آیتم با موفقیت حذف شد");
10 } catch (error) {
11 toast.error("خطا در حذف آیتم");
12 } finally {
13 setIsDeleting(false);
14 }
15};
16
17<Dialog open={open} onOpenChange={setOpen}>
18 <DialogTrigger asChild>
19 <Button variant="destructive">حذف</Button>
20 </DialogTrigger>
21 <DialogContent>
22 <DialogHeader>
23 <DialogTitle>آیا مطمئن هستید؟</DialogTitle>
24 <DialogDescription>
25 این عملیات غیرقابل بازگشت است.
26 </DialogDescription>
27 </DialogHeader>
28 <DialogFooter>
29 <DialogClose asChild>
30 <Button variant="outline" disabled={isDeleting}>
31 انصراف
32 </Button>
33 </DialogClose>
34 <Button
35 variant="destructive"
36 onClick={handleDelete}
37 disabled={isDeleting}
38 >
39 {isDeleting ? "در حال حذف..." : "حذف"}
40 </Button>
41 </DialogFooter>
42 </DialogContent>
43</Dialog>جلوگیری از بسته شدن (Prevent Close)
جلوگیری از بسته شدن دیالوگ با کلیک خارج یا کلید Escape
1<DialogContent
2 onPointerDownOutside={(e) => e.preventDefault()}
3 onEscapeKeyDown={(e) => e.preventDefault()}
4 onInteractOutside={(e) => e.preventDefault()}
5>
6 <DialogHeader>
7 <DialogTitle>فرم اجباری</DialogTitle>
8 <DialogDescription>
9 این فرم باید تکمیل شود
10 </DialogDescription>
11 </DialogHeader>
12 {/* Form content */}
13 <DialogFooter>
14 <Button type="submit">ارسال</Button>
15 </DialogFooter>
16</DialogContent>