راهنمای ساخت با دیگ
صفحههای کامپوننت میگویند هر کدام چه 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 | کاربر باید بتواند به صفحهٔ مشخصی برگردد |
حالتهایی که هر صفحه باید جواب بدهد
پیش از تمامشدن کار، این فهرست را روی هر کنترل و هر لیست مرور کنید. بیشترِ باگهای رابط کاربری در حالتهای پایین این فهرستاند، نه بالای آن.
دادهٔ فارسی
عدد و تاریخ و مبلغ هیچوقت دستی قالببندی نشوند. ابزارهای 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 خودش راست میشود */}