راهنمای ساخت با دیگ

صفحه‌های کامپوننت می‌گویند هر کدام چه API دارند. این صفحه چیز دیگری می‌گوید: برای هر کارِ مشخص کدام کامپوننت، در چه حالتی، و کجای صفحه. مخاطبش هم ایجنت‌های AI است، هم توسعه‌دهنده، هم طراح محصول.

قاعدهٔ اول: هیچ عدد خامی ننویسید

اگر در کدتان text-[13px] یا p-[18px] یا #202930 دیدید، یعنی از سیستم بیرون زده‌اید. هر سه یک توکن دارند.

// نادرست
<p className="text-[13px] text-[#4c5c6b]">توضیح</p>
<div className="p-[20px] gap-[8px]">…</div>

// درست
<p className="text-description text-muted-foreground">توضیح</p>
<div className="p-card gap-stack">…</div>

اسکلت یک صفحهٔ پنل

هر صفحهٔ داخلیِ یک پنل اداری همین ساختار را دارد. عرض بیشینه، حاشیهٔ صفحه و فاصلهٔ بلوک‌ها همه توکن‌اند.

<SidebarProvider>
  <AppSidebar />
  <SidebarInset>
    <header className="flex h-16 items-center border-b px-6">…</header>

    <main className="flex-1 overflow-y-auto px-gutter py-section">
      <div className="mx-auto flex max-w-page flex-col gap-section">

        {/* تیتر صفحه و کنش‌های سطح صفحه، هم‌ردیف */}
        <div className="flex flex-wrap items-center justify-between gap-inline-loose">
          <div className="flex flex-col gap-stack-tight">
            <h1 className="text-title font-semibold tracking-heading">عنوان</h1>
            <p className="text-description text-muted-foreground">زیرعنوان</p>
          </div>
          <Button>کنش اصلی</Button>
        </div>

        {/* محتوا */}
        <Card>…</Card>
      </div>
    </main>
  </SidebarInset>
</SidebarProvider>

نکتهٔ مهم برای صفحه‌هایی که با هم تب می‌شوند: بلوک تیتر را در هر دو صفحه هم‌ساختار نگه دارید و ارتفاع نوار ابزار جدول را ثابت کنید، وگرنه کاربر موقع جابه‌جایی بین تب‌ها پرش می‌بیند.

انتخاب کامپوننت: ورودی و انتخاب

کارکامپوننتشرط
انتخاب یکی از چند گزینهٔ مشخص و کوتاهSelectگزینه‌ها بسته‌اند و کاربر نباید مقدار تازه بسازداگر بیش از ۱۵ گزینه دارید، Autocomplete بگذارید
انتخاب از فهرست بلند با جستجوAutocompleteفهرست بلند است ولی همچنان بستهمتن آزادِ خارج از فهرست را نگه نمی‌دارد
دو تا پنج گزینه که همیشه دیده شوندToggleGroupمقایسهٔ گزینه‌ها مهم است، مثل بازهٔ زمانی یا چیدمانبرای بیش از پنج گزینه فضا کم می‌آورد
انتخاب تکی با توضیح زیر هر گزینهRadioGroupهر گزینه نیاز به شرح دارد، مثل نوع اشتراک
روشن و خاموش یک تنظیمSwitchاثرش فوری استاگر باید ذخیره شود، Checkbox درست‌تر است
پذیرش شرایط یا انتخاب چندتاییCheckboxکاربر صفر تا چند مورد برمی‌دارد

انتخاب کامپوننت: لایه‌های شناور

کارکامپوننتشرط
کار جداگانه‌ای که کاربر باید تمامش کندDialogفرم یا انتخاب چندمرحله‌ایبرای پیام ساده از Toast استفاده کنید
تایید کار برگشت‌ناپذیرAlertDialogحذف، پرداخت، خروج بدون ذخیرهبستن با کلیک بیرون ندارد و همین درست است
پنل کناری برای محتوای کمکیSheetفیلترها، جزئیات یک ردیف، تنظیمات
همان پنل روی موبایلDrawerکشیدن از لبه طبیعی‌تر از پنل کناری است
محتوای کوچک وابسته به یک دکمهPopoverانتخابگر رنگ، تقویم، فرم دو فیلدی
توضیح یک‌خطی برای آیکونTooltipدکمه فقط آیکون داردهیچ‌وقت تنها راه رسیدن به اطلاعات نباشد، روی لمس باز نمی‌شود

انتخاب کامپوننت: بازخورد

کارکامپوننتشرط
وضعیت ماندگار روی صفحهAlertخطای فرم، هشدار دسترسی، نتیجهٔ یک اقدام
اطلاع گذراToastذخیره شد، کپی شد، به آرشیو رفتبرای خطایی که کاربر باید رفعش کند به کار نرود
پیشرفت کاری که طول می‌کشدProgressدرصد مشخص است
جای خالیِ محتوای در راهSkeletonساختار نهایی از قبل معلوم استبرای انتظار کوتاه‌تر از نیم‌ثانیه چیزی نشان ندهید

انتخاب کامپوننت: ساختار و ناوبری

کارکامپوننتشرط
پوستهٔ صفحهٔ پنلSidebar + SidebarInsetهر صفحهٔ داخلیِ پنل اداری
جابه‌جایی بین نماهای هم‌سطحTabsجاری و آرشیو، ماهانه و سالانهبرای مراحل پشت سر هم از Stepper استفاده کنید
مسیر رسیدن به صفحهٔ فعلیBreadcrumbعمق بیش از دو سطح
فهرست داده با ستونTableمقایسهٔ ردیف‌ها مهم استروی موبایل به کارت تبدیلش کنید، اسکرول افقی ندهید
صفحه‌بندی فهرست بلندPaginationکاربر باید بتواند به صفحهٔ مشخصی برگردد

حالت‌هایی که هر صفحه باید جواب بدهد

پیش از تمام‌شدن کار، این فهرست را روی هر کنترل و هر لیست مرور کنید. بیشترِ باگ‌های رابط کاربری در حالت‌های پایین این فهرست‌اند، نه بالای آن.

پیش‌فرضحالت عادی و پرتکرار
هاورفقط روی اشاره‌گر دقیق، روی لمس معنا ندارد
فوکوسهمیشه دیدنی، هرگز outline: none بدون جایگزین
فعاللحظهٔ فشردن
غیرفعالبا دلیل؛ اگر دلیلش معلوم نیست، پنهانش کنید
فقط‌خواندنیقابل انتخاب و کپی، برخلاف غیرفعال
نامعتبربا aria-invalid و پیام کنارش
بارگذاریکنترل قفل و متن جایگزین
خالیچرا خالی است و قدم بعدی چیست
سرریزمتن بلند فارسی، نام طولانی، عدد بزرگ

دادهٔ فارسی

عدد و تاریخ و مبلغ هیچ‌وقت دستی قالب‌بندی نشوند. ابزارهای persian برای همین‌اند.

import { toFaDigits, formatNumber, formatJalali } from "@/lib/persian"

<span className="tabular-nums">{formatNumber(2400000000)}</span>
<span className="tabular-nums">{formatJalali(date)}</span>

// هرجا عدد در ستون می‌نشیند، tabular-nums لازم است
// وگرنه عرض ارقام فرق می‌کند و ستون می‌لرزد

راست‌به‌چپ

هیچ‌وقت left و right ننویسید. کامپوننت‌های دیگ خودشان منطقی‌اند و پراپ‌هایشان هم منطقی است.

// نادرست
<div className="ml-4 text-left border-l">…</div>
<Sidebar side="left" />

// درست
<div className="ms-4 text-start border-s">…</div>
<Sidebar side="start" />   {/* در RTL خودش راست می‌شود */}

ضدالگوها

کپی‌کردن کامپوننت برای یک تغییر کوچکاول prop و className را امتحان کنید؛ اگر نشد، یافته‌اش را گزارش کنید تا در خود دیگ حل شود
استفاده از Tooltip برای اطلاعات ضروریروی لمس باز نمی‌شود و صفحه‌خوان همیشه نمی‌خواندش
Toast برای خطایی که باید رفع شودمی‌پرد و می‌رود؛ خطای فرم باید کنار همان فیلد بماند
غیرفعال‌کردن دکمه بدون توضیحکاربر نمی‌فهمد چه کم است؛ دلیل را کنارش بنویسید
اسکرول افقی در جدولستون‌ها را ادغام کنید یا روی موبایل به کارت تبدیل کنید
دو کامپوننت شناور روی هم بدون ترتیب لایهاز نردبان z استفاده کنید تا تولتیپ زیر مودال گم نشود