انتخاب قالب صفحه
هر صفحهٔ محصول یک قالب است — فهرست، جزئیات، فرم، تنظیمات، داشبورد یا صفحهٔ کمکی — و قالب عرض، سرِ صفحه، جای اقدامها و حالتها را تعیین میکند
معرفی
هر صفحهٔ یک محصول پرتو با یک قالب ساخته میشود (@partodata/ui/templates). صفحه فقط جایگاههای قالب را پر
میکند: عنوان، اقدامها، فیلترها، داده و حالت داده. هر تصمیمی که دو توسعهدهنده یا دو مدل هوش مصنوعی ممکن است
متفاوت بگیرند — عرض، فاصلهها، اندازهٔ عنوان، جای اقدام اصلی، شکل حالت بارگذاری و خالی و خطا، جای دکمههای فرم — در
خود قالب است و عددی در صفحه نوشته نمیشود.
قالبها درون ProductFrame رندر میشوند که یک بار در layout ریشهٔ محصول است؛
صفحه قاب نمیسازد.
نمونه بصری
پیادهسازی
جدول انتخاب
از بالا به پایین بخوانید؛ اولین ردیفی که درست است قالب صفحه است:
| اگر صفحه… | قالب | عرض |
|---|---|---|
| پیش از ورود است: ورود، کد یکبارمصرف، بازیابی گذرواژه (بیرون از قاب) | AuthPage | کارت 400، در مرکز |
| 404، 403، خطای کل صفحه، خالیِ اولکار یا در حال بهروزرسانی است | UtilityPage | باریک (768)، در مرکز |
| کار اصلیاش یک پرسش است (جستوجوی پست، جستوجوی شواهد) | ListPage با query | مثل فهرست |
| فهرستی از موجودیتها است (با جستوجو و فیلتر یا بی آنها) | ListPage | از content: جدول 1200 (بیش از 8 ستون wide)، فید 680 (با aside 1200)، شبکه 1600 |
| یک موجودیت را با بخشها، زبانهها یا ستون کناری نشان میدهد | DetailPage | پیشفرض (1200)؛ گزارش نمودار و جدول wide |
| یک چیز را در چند مرحله میسازد (منبع، سپس برچسبها، سپس بازبینی) | WizardPage | باریک (768) |
| یک فرم است که یک بار ثبت میشود (ساخت، ویرایش، دعوت) | FormPage | باریک (768) |
| تنظیمات در چند گروه است که هر گروه جدا ذخیره میشود | SettingsPage | باریک (768) |
| شاخص و نمودار با بازهٔ زمانی دارد | DashboardPage | پهن (1600) یا کامل |
| ارتفاع صفحه را پر میکند: پخش زنده کنار متن، نقشه، پنلهای کنار هم، کنسول | CustomPage با layout="fill" و PagePaneها (با DS-GAP) | با نام |
| در هیچکدام جا نمیشود | اول به مسئول طراحی بگویید؛ تا تصمیم، CustomPage با DS-GAP | با نام |
فرم یا تنظیمات؟ نام صفحه تعیین نمیکند، شمارِ دکمههای ذخیره تعیین میکند: یک «ذخیره» و یک «انصراف» برای کل صفحه
← FormPage (حتی اگر اسمش «تنظیمات هشدار» باشد)؛ چند گروه که هر کدام جدا ذخیره میشود ← SettingsPage.
دیالوگ، پنل کناری یا صفحه؟
| اگر کار… | جایش |
|---|---|
| کوتاه و متمرکز است: تأیید، فرم دو سه فیلدی | Dialog / AlertDialog |
| جزئیات یک ردیف یا فرم بلند است و کاربر نباید صفحهٔ جاری را ترک کند | Sheet (پنل کناری) |
| جزئیات ردیفهایی که کاربر پشت هم از فهرست بررسی میکند و پیوند مستقیم لازم دارد (کنسول عملیات) | EntityDrawer + useEntityDrawer (موجودیت باز در نشانی، J/K) |
| یک جریان کامل است، یا باید نشانی (پیوند) داشته باشد | یک صفحه با قالب |
فرم دیالوگ و پنل کناری هم با FormRow ساخته میشود (در یک FormSection، برچسب بالا).
آنچه همهٔ قالبها تعیین میکنند
- یک
h1: عنوان صفحه، 48 پیکسل زیر نوار بالای قاب. نوار بالای قاب عنوان صفحه ندارد.h1دیگری در صفحه در محیط توسعه هشدار میدهد. - ترتیب و فاصله: سرِ صفحه ← (نوارابزار) ← محتوا. سرِ صفحه تا اولین بلوک 48، بخش تا بخش 48، بلوک تا بلوک 24، نوارابزار تا جدول 16 — همه از توکنهای هندسهٔ صفحه. صفحهٔ فهرست کوتاهتر است: نوارابزارش 24 زیر سرِ صفحه، توضیح یک خط، و نوارابزار و سرِ جدول هنگام پیمایش میچسبند.
- عرض از محتوا: جدول با نام، فید تکستونی در اندازهٔ خواندن (680،
content="feed"، با ستون کناری اختیاری)، شبکهٔ کارت پهن. - عرض: با نام (
narrow768 ·default1200 ·wide1600 ·full)، هرگز عدد؛ قالبهاclassNameوstyleنمیپذیرند. - اقدامها: اقدام اصلی فقط یکی است (
primaryAction، یکButtonبدونvariantکه کاری میکند:onClick، یا اگر به صفحهٔ دیگری میرود<Button asChild><Link href>…</Link></Button>)؛ در انتهای سرِ صفحه، یا اگر صفحه ردیف فیلتر دارد در انتهای همان ردیف. اقدامهای دیگر (secondaryActions) در همهٔ صفحههاvariant="default"کنار آناند (خروجی جدول:DataTableExportButton، همیشه بی هیچ شرطی؛ قالب آن را برای فهرست بیردیف غیرفعال با دلیلش نشان میدهد). در فرم، دکمهٔ ثبت اقدام اصلی است و در پایین کارت فرم. اقدام روی چند ردیف انتخابشده،bulkActionsدرselectionیکListPageاست: نوار انتخاب تا ردیفی انتخاب شده جای ردیف نوارابزار را میگیرد — هرگز درsecondaryActionsیا نواری از خود صفحه. - یک اقدام اصلی در هر ناحیهٔ مستقل: سرِ صفحه (با نوارابزارش)، یک
PagePane، یکSettingsSection، یکEntityDrawerیا یک دیالوگ هر کدام ناحیهٔ خودشاناند و هر کدام یک اقدام اصلی دارند. دو تصمیم هموزن (تأیید / رد) یک اقداماند:DecisionActionsدر جای اقدام اصلی همان ناحیه؛ در صف بررسیemphasis="item". - دادهٔ زنده: صفحهای که خودش تازه میشود حالتش را با
pageState({ …, live })میسازد (useLiveRefresh)؛ تازهشدن محتوا را دست نمیزند و شکستش داده را پاک نمیکند (دادهٔ زنده). - بازگشت: صفحهای که مورد منوی خودش را دارد
backندارد؛ صفحهای که از صفحهٔ دیگری باز میشودbackبه همان صفحه؛breadcrumbsفقط دو سطح یا بیشتر عمیق — هرگز هر دو. - نما در نشانی: بازه، تب، فیلترها، مرتبسازی، چیدمان و شمارهٔ صفحه اگر باید با پیوند به اشتراک گذاشته شوند یا پس از
«برگشت» بمانند، فقط با
useViewParamsدر نشانی میروند: نهuseSearchParams، نهuseFilterParams/FilterProvider، نهhistory.replaceState(قاعدهٔ ESLintparto/page-template). دلیل: یک پاسخ برای همهٔ صفحهها؛ نام پارامترها، شکل نوشتن آرایه و تاریخ، push یا replace و خواندن در اولین رندر را در آزمایش X5 سه اجرا به سه شکل ساختند، و تا یک راهِ DS نبود «نه» تنها پاسخ بود. متن تایپشده، انتخاب و جای فید با مکاننما وضعیت جزء میمانند. - حالتها: بارگذاری اسکلتی همشکل محتواست؛ خطا
ErrorStateبا «تلاش مجدد»؛ خالیEmpty— هر سه بهجای محتوا، هرگز داخل جدول یا کارت. سرِ صفحه هیچوقت ناپدید نمیشود. حالت را همیشه با یک قاعده از خروجی درخواست بسازید:pageState({ data: result?.items, isLoading, error, onRetry })—dataخودِ فهرست (یا موجودیت) است، نه شیء صفحهای که فهرست در آن است؛ درListPage،filteredخود صفحه فهرست خالی را «نتیجهای یافت نشد» با متن خود سیستم طراحی میکند. وقتی ردیفها روی صفحهاند و صفحهٔ بعد یا نتیجهٔ فیلتر در راه است، ردیفها کمرنگ میمانند؛ نشانهٔ بارگذاری دیگری لازم نیست.stateدرListPageالزامی است. - عدم دسترسی: در سطح صفحه
UtilityPageنوع403، داخل قاب؛ در سطح یک اقدام، همان اقدام در جایگاه خودش، غیرفعال با دلیلش:GatedActionدورButton(<GatedAction allowed={canCreate} reason="…">) — هرگز پنهان (canCreate && <Button/>)، هرگزdisabledبی دلیل. اقدامی که باConfirmDialogتأیید میشودGatedActionرا درtriggerپنجره دارد، نه پنجره را درونGatedAction. گزینهای از منوی عملیات یک ردیف که کاربر اجازهاش را ندارد از منو کنار گذاشته میشود (گزینهٔ غیرفعال منو نمیتواند دلیلش را بگوید). - کارت: تودرتو نیست. جدول، حالت خالی و نوارابزار داخل کارت نمیروند؛ فرمها کارت خودشان را از قالب میگیرند.
الگوهای رایج
فهرست با جستوجو و فیلتر
<ListPage
title="منشنها"
search={
<SearchInput
placeholder="جستوجو در منشنها"
aria-label="جستوجو در منشنها"
value={q}
onChange={(e) => filterBy(setQ)(e.target.value)}
onClear={() => filterBy(setQ)('')}
/>
}
filters={
<>
<DataTableFacetedFilter
title="پلتفرم"
options={platforms}
selected={selected}
onSelectedChange={filterBy(setSelected)}
/>
<DateRangePicker value={range} onChange={filterBy(setRange)} placeholder="بازهٔ تاریخ" />
</>
}
filtered={q !== '' || selected.length > 0 || range !== undefined}
onClearFilters={clear}
secondaryActions={<DataTableExportButton columns={columns} data={rows} filename="mentions.csv" label="خروجی CSV" />}
primaryAction={
<Button asChild iconStart={<Icons.plus />}>
<Link href="/settings/alerts">ساخت هشدار</Link>
</Button>
}
state={pageState({
data: result?.items,
isLoading,
error,
onRetry: reload,
emptyCopy: { title: 'هنوز منشنی ثبت نشده است' },
})}
pagination={{ currentPage: page, totalPages, onPageChange: setPage, totalRows: result?.total ?? 0, pageSize: 25 }}
>
<DataTable columns={columns} data={rows} />
</ListPage>یک فرم
<Form {...form}>
{/* «تنظیمات هشدار» مورد منوی خودش را دارد: بی back، و «انصراف» تغییرات را کنار میگذارد. */}
<FormPage title="تنظیمات هشدار" onSubmit={form.handleSubmit(save)} onCancel={() => form.reset()} submitLabel="ذخیره">
<FormField
control={form.control}
name="name"
rules={{ required: 'نام هشدار را وارد کنید' }}
render={({ field, fieldState }) => (
<FormRow label="نام هشدار" required error={fieldState.error?.message}>
<Input {...field} />
</FormRow>
)}
/>
</FormPage>
</Form>بخش تازهای که هنوز خالی است
<UtilityPage
kind="empty"
title="هنوز گزارشی ساخته نشده است"
description="گزارشها خلاصهٔ دورهای منشنها هستند و پس از راهاندازی این بخش، اینجا فهرست میشوند."
/>عنوان وضعیت را میگوید، نه نام صفحه. action فقط وقتی هست که چیزی در محصول اولین مورد را بسازد (پیوند یا onClick
به آن)؛ هرگز دکمهای که هیچ کاری نمیکند.
نمونهٔ کامل هر قالب، با داده، مسیرها و حالتها، در قالب شروع است: / داشبورد، /mentions فهرست،
/mentions/[id] جزئیات با زبانه، /settings/alerts فرم، /reports صفحهٔ خالی و not-found صفحهٔ 404.
بهترین روشها + دامهای رایج
بهترین روشها
- پیش از نوشتن صفحه قالبش را از جدول بالا انتخاب کنید.
- فقط جایگاههای قالب را پر کنید؛ اگر چیزی جا نداشت، DS-GAP ثبت کنید.
- کنترلها و دکمههای داخل قالب را بدون
sizeو کلاس عرض بنویسید.
دامهای رایج
- ساختن صفحه از
PageContainerوPageHeaderدستی، یا قالب درونPageContainer: اینها اجزای سطح پایینیاند که قالبها رویشان ساخته شدهاند؛ صفحهٔ تازه با آنها ساخته نمیشود و قالبِ درونPageContainerدو بار فاصله میگیرد. قاعدهٔparto/page-templateآنها را در کد نشان میدهد و قالب درونPageContainerدر محیط توسعه هشدار میدهد. - اقدام اصلی در سرِ صفحه در حالی که صفحه ردیف فیلتر دارد:
primaryActionرا به قالب بدهید تا جایش را خودش تعیین کند. - اقدام ثانوی
outlineیاghost، یا اقدام اصلی بیonClickو بی پیوند: قاعدهٔparto/page-primary-actionهر دو را میگیرد. EmptyدرemptyStateجدول یا داخل کارت: حالت خالی را باstateبدهید.- دکمهٔ ذخیرهٔ تمامعرض یا دکمههای دستساز پایین فرم: دکمهها را
FormPageوSettingsSectionمیسازند. FormPageبرای تنظیمات گروهگروه، یاSettingsPageبرای یک فرم: شمار دکمههای ذخیره تعیین میکند.
صفحات مرتبط
ProductFrame— قاب محصول که قالبها درونش رندر میشوند.PageState— حالتهای بارگذاری، خطا و خالی.FormRow— فیلد فرم در همهٔ قالبها و دیالوگها.- هندسهٔ صفحه — توکنهایی که قالبها از آنها ساخته شدهاند.
- چیدمان — اجزای سطح پایین صفحه، برای وقتی که قالبی جا نمیشود.