برگه (Sheet)

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

نصب (Installation)

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

نمونه‌ها (Examples)

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

ساده‌ترین حالت استفاده از برگه با عنوان و توضیحات.

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetDescription,
5  SheetHeader,
6  SheetTitle,
7  SheetTrigger,
8} from "@/components/ui/sheet";
9import { Button } from "@/components/ui/button";
10
11export default function BasicExample() {
12  return (
13    <Sheet>
14      <SheetTrigger asChild>
15        <Button variant="outline">باز کردن برگه</Button>
16      </SheetTrigger>
17      <SheetContent>
18        <SheetHeader>
19          <SheetTitle>عنوان برگه</SheetTitle>
20          <SheetDescription>
21            این یک توضیح کوتاه درباره محتوای برگه است
22          </SheetDescription>
23        </SheetHeader>
24        <div className="py-4">
25          <p className="text-muted-foreground">
26            محتوای اصلی برگه در اینجا قرار می‌گیرد
27          </p>
28        </div>
29      </SheetContent>
30    </Sheet>
31  );
32}
33

جهت‌های مختلف (Side Variants)

از پراپ side برای تعیین جهت باز شدن برگه استفاده کنید.

مشاهده کد
1import { Button } from "@/components/ui/button";
2import {
3  Sheet,
4  SheetContent,
5  SheetHeader,
6  SheetTitle,
7  SheetTrigger,
8  SheetDescription,
9} from "@/components/ui/sheet";
10
11const SHEET_SIDES = [
12  { side: "top", label: "بالا" },
13  { side: "right", label: "راست" },
14  { side: "bottom", label: "پایین" },
15  { side: "left", label: "چپ" },
16] as const;
17
18export default function SideExample() {
19  return (
20    <div className="flex flex-wrap gap-2">
21      {SHEET_SIDES.map(({ side, label }) => (
22        <Sheet key={side}>
23          <SheetTrigger asChild>
24            <Button variant="outline">{label}</Button>
25          </SheetTrigger>
26          <SheetContent side={side}>
27            <SheetHeader>
28              <SheetTitle>برگه از سمت {label}</SheetTitle>
29              <SheetDescription>
30                این برگه از سمت {label} باز می‌شود
31              </SheetDescription>
32            </SheetHeader>
33            <div className="py-4">
34              <p className="text-muted-foreground">
35                محتوای برگه در اینجا قرار می‌گیرد
36              </p>
37            </div>
38          </SheetContent>
39        </Sheet>
40      ))}
41    </div>
42  );
43}
44

با فرم (With Form)

برگه با فرم ورودی برای ویرایش اطلاعات.

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetDescription,
5  SheetFooter,
6  SheetHeader,
7  SheetTitle,
8  SheetTrigger,
9  SheetClose,
10} from "@/components/ui/sheet";
11import { Button } from "@/components/ui/button";
12import { Input } from "@/components/ui/input";
13import { Label } from "@/components/ui/label";
14
15export default function WithFormExample() {
16  return (
17    <Sheet>
18      <SheetTrigger asChild>
19        <Button variant="outline">ویرایش پروفایل</Button>
20      </SheetTrigger>
21      <SheetContent>
22        <SheetHeader>
23          <SheetTitle>ویرایش پروفایل</SheetTitle>
24          <SheetDescription>
25            تغییرات خود را اعمال کنید و دکمه ذخیره را بزنید
26          </SheetDescription>
27        </SheetHeader>
28        <div className="grid gap-4 py-4">
29          <div className="grid gap-2">
30            <Label htmlFor="name">نام</Label>
31            <Input id="name" defaultValue="علی کاوسی" />
32          </div>
33          <div className="grid gap-2">
34            <Label htmlFor="username">نام کاربری</Label>
35            <Input id="username" defaultValue="@alikawosi" />
36          </div>
37        </div>
38        <SheetFooter>
39          <SheetClose asChild>
40            <Button variant="outline">انصراف</Button>
41          </SheetClose>
42          <Button type="submit">ذخیره تغییرات</Button>
43        </SheetFooter>
44      </SheetContent>
45    </Sheet>
46  );
47}
48

بدون دکمه بستن (No Close Button)

مخفی کردن دکمه X در گوشه بالای برگه با showCloseButton=false.

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetDescription,
5  SheetFooter,
6  SheetHeader,
7  SheetTitle,
8  SheetTrigger,
9  SheetClose,
10} from "@/components/ui/sheet";
11import { Button } from "@/components/ui/button";
12
13export default function NoCloseButtonExample() {
14  return (
15    <Sheet>
16      <SheetTrigger asChild>
17        <Button variant="outline">بدون دکمه X</Button>
18      </SheetTrigger>
19      <SheetContent showCloseButton={false}>
20        <SheetHeader>
21          <SheetTitle>بدون دکمه بستن</SheetTitle>
22          <SheetDescription>
23            این برگه دکمه X در گوشه بالا ندارد. برای بستن از دکمه پایین استفاده
24            کنید
25          </SheetDescription>
26        </SheetHeader>
27        <SheetFooter>
28          <SheetClose asChild>
29            <Button variant="outline">بستن</Button>
30          </SheetClose>
31        </SheetFooter>
32      </SheetContent>
33    </Sheet>
34  );
35}
36

کنترل شده (Controlled)

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

وضعیت برگه: بسته

مشاهده کد
1import { useState } from "react";
2import {
3  Sheet,
4  SheetContent,
5  SheetDescription,
6  SheetFooter,
7  SheetHeader,
8  SheetTitle,
9  SheetTrigger,
10} from "@/components/ui/sheet";
11import { Button } from "@/components/ui/button";
12
13export default function ControlledExample() {
14  const [open, setOpen] = useState(false);
15
16  return (
17    <div className="flex flex-col items-center gap-4">
18      <Sheet open={open} onOpenChange={setOpen}>
19        <SheetTrigger asChild>
20          <Button variant="outline">برگه کنترل‌شده</Button>
21        </SheetTrigger>
22        <SheetContent>
23          <SheetHeader>
24            <SheetTitle>برگه کنترل‌شده</SheetTitle>
25            <SheetDescription>
26              این برگه با استفاده از state کنترل می‌شود
27            </SheetDescription>
28          </SheetHeader>
29          <SheetFooter>
30            <Button onClick={() => setOpen(false)}>بستن با کد</Button>
31          </SheetFooter>
32        </SheetContent>
33      </Sheet>
34      <p className="text-sm text-muted-foreground">
35        وضعیت برگه: {open ? "باز" : "بسته"}
36      </p>
37    </div>
38  );
39}
40

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

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

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetDescription,
5  SheetFooter,
6  SheetHeader,
7  SheetTitle,
8  SheetTrigger,
9  SheetClose,
10} from "@/components/ui/sheet";
11import { Button } from "@/components/ui/button";
12
13export default function ScrollableExample() {
14  return (
15    <Sheet>
16      <SheetTrigger asChild>
17        <Button variant="outline">محتوای طولانی</Button>
18      </SheetTrigger>
19      <SheetContent>
20        <SheetHeader>
21          <SheetTitle>محتوای قابل اسکرول</SheetTitle>
22          <SheetDescription>
23            این برگه محتوای طولانی دارد که قابل اسکرول است
24          </SheetDescription>
25        </SheetHeader>
26        <div className="flex-1 overflow-y-auto py-4">
27          {Array.from({ length: 10 }).map((_, index) => (
28            <p key={index} className="mb-4 leading-relaxed">
29              لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با
30              استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله
31              در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد
32              نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.
33            </p>
34          ))}
35        </div>
36        <SheetFooter>
37          <SheetClose asChild>
38            <Button variant="outline">بستن</Button>
39          </SheetClose>
40        </SheetFooter>
41      </SheetContent>
42    </Sheet>
43  );
44}
45

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

منوی ناوبری (Navigation Menu)

استفاده از برگه برای منوی ناوبری موبایل.

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetHeader,
5  SheetTitle,
6  SheetTrigger,
7} from "@/components/ui/sheet";
8import { Button } from "@/components/ui/button";
9import { Menu } from "lucide-react";
10
11export default function NavigationExample() {
12  return (
13    <Sheet>
14      <SheetTrigger asChild>
15        <Button variant="ghost" size="icon">
16          <Menu className="h-5 w-5" />
17        </Button>
18      </SheetTrigger>
19      <SheetContent side="right">
20        <SheetHeader>
21          <SheetTitle>منوی ناوبری</SheetTitle>
22        </SheetHeader>
23        <nav className="flex flex-col gap-2 py-4">
24          <a href="#" className="px-4 py-2 hover:bg-muted rounded-md transition-colors">
25            خانه
26          </a>
27          <a href="#" className="px-4 py-2 hover:bg-muted rounded-md transition-colors">
28            محصولات
29          </a>
30          <a href="#" className="px-4 py-2 hover:bg-muted rounded-md transition-colors">
31            درباره ما
32          </a>
33          <a href="#" className="px-4 py-2 hover:bg-muted rounded-md transition-colors">
34            تماس با ما
35          </a>
36        </nav>
37      </SheetContent>
38    </Sheet>
39  );
40}
41

جلوگیری از بسته شدن (Prevent Close)

جلوگیری از بسته شدن برگه با کلیک خارج یا کلید Escape.

مشاهده کد
1import {
2  Sheet,
3  SheetContent,
4  SheetDescription,
5  SheetFooter,
6  SheetHeader,
7  SheetTitle,
8  SheetTrigger,
9  SheetClose,
10} from "@/components/ui/sheet";
11import { Button } from "@/components/ui/button";
12
13export default function PreventCloseExample() {
14  return (
15    <Sheet>
16      <SheetTrigger asChild>
17        <Button variant="outline">فرم اجباری</Button>
18      </SheetTrigger>
19      <SheetContent
20        onPointerDownOutside={(e) => e.preventDefault()}
21        onEscapeKeyDown={(e) => e.preventDefault()}
22        onInteractOutside={(e) => e.preventDefault()}
23      >
24        <SheetHeader>
25          <SheetTitle>فرم اجباری</SheetTitle>
26          <SheetDescription>
27            این فرم باید تکمیل شود و امکان بستن آن با کلیک خارج یا Escape وجود ندارد
28          </SheetDescription>
29        </SheetHeader>
30        <div className="py-6">
31          <p className="text-muted-foreground">محتوای فرم ضروری...</p>
32        </div>
33        <SheetFooter>
34          <SheetClose asChild>
35            <Button variant="outline">انصراف</Button>
36          </SheetClose>
37          <Button type="submit">ارسال</Button>
38        </SheetFooter>
39      </SheetContent>
40    </Sheet>
41  );
42}
43

مرجع API (API Reference)

Sheet

پراپ‌های کامپوننت Sheet.

پراپ (Prop)نوع (Type)پیش‌فرض (Default)توضیحات (Description)
openbooleanundefinedوضعیت باز/بسته بودن (کنترل‌شده)
defaultOpenbooleanfalseوضعیت پیش‌فرض (غیرکنترل‌شده)
onOpenChange(open: boolean) => voidundefinedتابع فراخوانی هنگام تغییر وضعیت
modalbooleantrueحالت مودال (بلاک کردن تعامل با پس‌زمینه)

SheetContent

پراپ‌های کامپوننت SheetContent.

پراپ (Prop)نوع (Type)پیش‌فرض (Default)توضیحات (Description)
side"top" | "right" | "bottom" | "left""right"جهت باز شدن برگه
showCloseButtonbooleantrueنمایش دکمه بستن در گوشه بالا
onEscapeKeyDown(event) => voidundefinedهندلر فشردن کلید Escape
onPointerDownOutside(event) => voidundefinedهندلر کلیک خارج از برگه

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

کیبورد (Keyboard)

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

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

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

نقش‌های ARIA

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

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

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

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

انتخاب جهت مناسب (Choose Side Wisely)

جهت راست برای تنظیمات و فرم‌ها، جهت چپ برای منوی ناوبری، و جهت‌های بالا/پایین برای اعلان‌ها و فیلترها مناسب هستند

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

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

تفاوت با Drawer و Dialog

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