هندسهٔ صفحه

توکن‌های --layout-* — ارتفاع ردیف‌های ثابت، عرض منو، حاشیهٔ صفحه، ریتم عمودی و نردبان عرض محتوا

اصل

هندسهٔ صفحه در پرتو یک مجموعه توکن است، نه عددی که هر صفحه یا هر کامپوننت برای خودش بنویسد. ارتفاع نوار بالا، عرض منو، حاشیهٔ کنار صفحه، فاصلهٔ عنوان تا اولین بلوک، فاصلهٔ بخش‌ها و عرض ستون محتوا همه از توکن‌های --layout-* در globals.css می‌آیند، و اجزای چیدمان همین توکن‌ها را می‌خوانند: ProductFrame، PageContainer، PageHeader، PageHeaderRoot، PageSection، PageToolbar، FilterBar، NavRail، AppSecondary، SiteHeader و ردیف‌های PageBreadcrumbs و PageNav.

نتیجه این است که کد صفحه عدد چیدمان نمی‌نویسد. صفحه اجزای صفحه را به کار می‌برد و هندسه را از آن‌ها می‌گیرد. اگر روزی یکی از این اعداد عوض شود، در یک جا عوض می‌شود و همهٔ محصولات با هم تغییر می‌کنند.

مقادیر همان هندسهٔ Supabase Studio هستند: ارتفاع 48 برای ردیف‌های ثابت، نردبان عرض 768 / 1200 / 1600، حاشیهٔ 16 / 24 / 40 و ریتم 48 / 24. برای راست‌به‌چپ آینه شده‌اند: منو در سمت شروع خط (راست) قرار می‌گیرد.

توکنمقدارکاربردکلاس نام‌دار
--layout-header-height3rem (48)نوار بالای قاب، سرِ منو، ردیف‌های PageBreadcrumbs و PageNavh-layout-header-height
--layout-rail-width3rem (48)نوار آیکونی جمع‌شدهw-layout-rail-width
--layout-menu-width16rem (256)منوی برچسب‌دار ثابت و پنل ثانویهw-layout-menu-width
--layout-page-inset1rem ← 1.5rem ← 2.5remحاشیهٔ کنار صفحه: 16، از sm 24، از xl 40px-layout-page-inset
--layout-page-top3rem (48)فاصلهٔ بالای عنوان صفحهpt-layout-page-top
--layout-section-gap3rem (48)سرِ صفحه تا اولین بلوک، و بخش تا بخشgap-layout-section-gap
--layout-block-gap1.5rem (24)بلوک تا بلوک داخل یک بخشgap-layout-block-gap
--layout-toolbar-gap1rem (16)نوارابزار تا محتوایی که کنترلش می‌کندgap-layout-toolbar-gap
--layout-control-gap0.5rem (8)بین کنترل‌های یک ردیفgap-layout-control-gap
--layout-search-width16rem (256)عرض جست‌وجوی نوارابزارw-layout-search-width
--layout-search-width-narrow12rem (192)جست‌وجوی باریک (searchWidth="narrow")w-layout-search-width-narrow
--layout-search-width-wide24rem (384)جست‌وجوی پهن (searchWidth="wide")w-layout-search-width-wide
--layout-tile-min-width16rem (256)کمترین عرض ستون شبکهٔ کاشی: ردیف شاخص داشبورد، شبکهٔ کارت‌هافرمول ستون‌های شبکه (زیر جدول)
--layout-state-min-height15rem (240)کمترین ارتفاع بلوک در حالت خطا و خالی (PageState)min-h-layout-state-min-height
--layout-chart-min-width28rem (448)کمترین عرض ستون شبکهٔ نمودارهای داشبورد (DashboardSection)فرمول ستون‌های شبکه (زیر جدول)
--layout-chart-height18rem (288)ارتفاع بدنهٔ کارت نمودار داشبورد (DashboardChart)، با حاشیهٔ داخلیh-layout-chart-height
--layout-aside-width20rem (320)ستون کناری: DetailPage aside، کنار فید (ListPage aside)، پنل کناری PagePanew-layout-aside-width
--layout-auth-width25rem (400)کارت صفحهٔ ورود (AuthPage)w-layout-auth-width
--layout-feed-width42.5rem (680)ستون فید: ردیف و کارت EntityCollection و Postmax-w-layout-feed-width
--layout-content-narrow48rem (768)فرم، تنظیمات، جریان چندمرحله‌ای، صفحه‌های کمکیmax-w-layout-content-narrow
--layout-content-default75rem (1200)فهرست جدولی و جزئیاتmax-w-layout-content-default
--layout-content-wide100rem (1600)داشبورد، تحلیل، شبکهٔ کارت‌هاmax-w-layout-content-wide
--layout-content-feed42.5rem (680)اندازهٔ خواندن فید تک‌ستونی (پست، نظر، گزارش پخش)؛ ستون فید هرگز پهن‌تر نمی‌شودmax-w-layout-content-feed

--layout-tile-min-width و --layout-chart-min-width عرض کمینهٔ ستون‌اند، نه عرض خود کاشی: کاشی با min-w-… از ظرفی باریک‌تر از خودش بیرون می‌زند. شبکه آن‌ها را در فرمول ستون‌ها می‌خواند و هیچ ستونی پهن‌تر از ردیف نمی‌شود: grid-cols-[repeat(auto-fill,minmax(min(var(--layout-tile-min-width),100%),1fr))] (شبکهٔ کارت؛ ردیف شاخص و شبکهٔ نمودار با auto-fit). قالب‌های صفحه همین را خودشان دارند.

عددها پیکسل CSS هستند. --layout-page-inset تنها توکن واکنش‌گراست: مقدارش در نقطه‌های شکست sm (40rem) و xl (80rem) روی :root عوض می‌شود. در کد محصول حاشیه را PageContainer می‌دهد، و جایی که ظرف خودتان را می‌سازید px-layout-page-inset؛ کلاس page-inset جزء داخلی خود اجزای صفحه است.

نام هر کلاس از نام توکنش ساخته می‌شود: پیشوند ویژگی در Tailwind + نام توکن بدون -- (--layout-section-gap ← gap-layout-section-gap). هر توکن یک کلاس دارد و کلاس دیگری در کار نیست، پس نامی را حدس نزنید؛ نامی که در این جدول نیست هیچ قاعدهٔ CSS نمی‌سازد و خطایی هم نمی‌دهد. هر توکن را فقط با پیشوندی که ستون آخر نشان می‌دهد به کار ببرید: ارتفاع‌ها با h- (یا min-h-)، عرض‌ها با w-، فاصله‌ها با gap-، حاشیه با px-، بالای عنوان با pt-، و سه عرض محتوا فقط با max-w-. ترکیب دیگری مثل gap-layout-header-height هم کامپایل می‌شود ولی معنایی ندارد. در CSS خودتان همان توکن را با var(--layout-…) بخوانید.

ارتفاع نوارابزار از این توکن‌ها نمی‌آید: ردیف PageToolbar به بلندی کنترل‌هایش است (34، اندازهٔ sm در اندازه و چگالی)، نه 48.


نمونه بصری

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

نوار بالای قاب — --layout-header-height
نوار آیکونی
--layout-page-top
سرِ صفحه (عنوان h1، توضیح، اقدام)
--layout-section-gap
نوارابزار (جست‌وجو، فیلترها، اقدام اصلی)
--layout-toolbar-gap
جدول یا فهرست
--layout-block-gap
بلوک بعدی همان بخش
--layout-section-gap
بخش بعدی
انتهای صفحه
توکنکاربردمقدار در همین صفحه
--layout-header-heightردیف‌های ثابت (قاب، منو، ناوبری صفحه)…
--layout-rail-widthنوار آیکونی…
--layout-menu-widthمنوی برچسب‌دار…
--layout-page-insetحاشیهٔ صفحه…
--layout-page-topبالای عنوان…
--layout-section-gapبین بخش‌ها…
--layout-block-gapبین بلوک‌ها…
--layout-toolbar-gapنوارابزار تا محتوا…
--layout-control-gapبین کنترل‌ها…
--layout-content-narrowعرض باریک…
--layout-content-defaultعرض پیش‌فرض…
--layout-content-wideعرض پهن…

قوانین اجباری

کد صفحه عدد چیدمان نمی‌نویسد

عرض، حاشیه و فاصلهٔ عمودی صفحه از قالب صفحه می‌آید. نمونهٔ زیر صفحه‌ای داخل ProductFrame است، جایی که هر صفحهٔ محصول رندر می‌شود: قالب عنوان را --layout-page-top زیر نوار بالای قاب می‌گذارد و بخش‌ها را --layout-section-gap از هم. قالب‌ها روی PageContainer، PageHeader و PageSection ساخته شده‌اند؛ همان اجزا فقط داخل محتوای یک CustomPage مستقیم به کار می‌روند.

// ✅ درست — عرض، حاشیه و ریتم از قالب صفحه
<DetailPage title="گزارش هفتگی" back={{ href: '/reports', label: 'گزارش‌ها' }}>
  <DetailSection title="تحلیل">
    <Card>تحلیل گفت‌وگوهای کمپین</Card>
  </DetailSection>
</DetailPage>

// ❌ غلط — عرض، حاشیه و فاصله با عدد؛ هر صفحه جواب دیگری می‌دهد
<div className="mx-auto max-w-[1180px] space-y-5 p-6">
  <PageHeader title="گزارش‌ها" />
  <Card>تحلیل گفت‌وگوهای کمپین</Card>
</div>

گزارش هفتگی

تحلیل گفت‌وگوها
درست — حاشیه و فاصله‌ها از توکن‌های هندسهٔ صفحه (در قالب‌ها خودکار)

گزارش هفتگی

تحلیل گفت‌وگوها
نادرست — عددهای دستی؛ هر صفحه جواب دیگری می‌دهد

عرض از نوع صفحه می‌آید

نوع صفحهعرضقالب (نام عرض)در PageContainer
فرم، تنظیمات، صفحه‌های کمکی (404، 403، اولین استفاده)768FormPage، SettingsPage، UtilityPagesize="small"
فهرست (جدول تا 8 ستون، فید ردیفی یا کارتی، کارت‌ها) و جزئیات1200ListPage، DetailPagesize="default"
داشبورد، تحلیل، جدول بیش از 8 ستون، فیدی که کاشی نشان می‌دهد1600DashboardPage؛ ListPage width="wide"size="large"
لاگ و جدولی که افقی پیمایش می‌شودتمام‌عرضListPage width="full"size="full"

هشدار تازه

درست — فرم در عرض 768 (max-w-layout-content-narrow) روی صفحهٔ 1440

هشدار تازه

نادرست — فرم تمام‌عرض؛ چشم برای خواندن یک فیلد کل صفحه را می‌پیماید

ردیف ثابت سفارشی با همان ارتفاع

اگر ردیف ثابتی می‌سازید که باید با نوار بالا و ردیف‌های ناوبری هم‌تراز باشد، ارتفاعش توکن است.

// ✅ درست
<div className="flex h-layout-header-height items-center border-t border-default">…</div>

// ❌ غلط — 52 پیکسل، کنار ردیف‌های 48 پیکسلی
<div className="flex h-[52px] items-center border-t border-default">…</div>
نوار بالا
ردیف شما
درست — h-layout-header-height؛ هم‌تراز نوار بالا
نوار بالا
ردیف شما
نادرست — h-[58px] کنار ردیف 48 پیکسلی

موارد استفاده رایج

  • ریتم عمودی یک صفحه، از بالا به پایین: نوار بالای قاب --layout-header-height (48) · بالای عنوان --layout-page-top (48) · سرِ صفحه تا اولین بلوک و بخش تا بخش --layout-section-gap (48) · بلوک تا بلوک --layout-block-gap (24) · نوارابزار تا جدول --layout-toolbar-gap (16) · بین کنترل‌های یک ردیف --layout-control-gap (8).
  • صفحهٔ فهرست کوتاه‌تر است: نوارابزار فهرست بخشی از سرِ صفحه است، پس --layout-block-gap (24) زیر سرِ صفحه می‌نشیند نه 48؛ توضیح صفحه یک خط است؛ و نوارابزار و سرِ جدول هنگام پیمایش بالای ناحیهٔ محتوا می‌چسبند. اندازه‌گیری‌شده در 1440 (عنوان، یک خط توضیح، نوارابزار): از زیر نوار قاب تا ردیف سرِ جدول 205 ← 181 پیکسل.
  • عرض از محتوا می‌آید: جدول با نام (default 1200، wide 1600)، فید تک‌ستونی در اندازهٔ خواندن (--layout-content-feed، 680 — ستون خبرخوان فیسبوک؛ X 600 متن‌محور است و اینستاگرام 470 رسانه‌محور؛ با حاشیه و ستون آواتار کارت، متن فارسی در 14 پیکسل حدود 600 پیکسل می‌شود، 12 تا 14 واژه در هر خط)، کنار آن ستون کناری دست‌کم --layout-aside-width که باقی صفحهٔ 1200 را می‌گیرد، و شبکهٔ کارت پهن (1600). نوارابزار و سرِ جدول از 48rem محتوا می‌چسبند؛ پوستهٔ برنامه‌ای با سرِ ثابت خودش (بیرون از ProductFrame) --page-sticky-offset را به ارتفاع آن می‌دهد.
  • CSS خودتان: مقدار را با var(--layout-header-height) بخوانید، نه با عدد.
  • رابطه با مقیاس فضابندی: مقیاس 4 پیکسلی برای فاصله‌های داخل کامپوننت‌ها و بلوک‌هاست؛ فاصله‌ها و اندازه‌های بین قاب، سرِ صفحه، بخش‌ها و بلوک‌ها فقط از این توکن‌ها می‌آید. ارتفاع کنترل‌ها موضوع جداگانه‌ای است: اندازه و چگالی.

چه نکنیم

نکنید

  • عرض و فاصلهٔ صفحه را با عدد ننویسید: max-w-[1180px]، p-6 روی ریشهٔ صفحه، space-y-4 یا mt-6 بین بخش‌ها.
  • به ناحیهٔ محتوای ProductFrame padding یا سقف عرض اضافه نکنید؛ حاشیه و عرض را قالب صفحه یک بار می‌دهد.
  • توکن‌ها را در یک صفحه یا یک محصول بازتعریف نکنید. تغییر این اعداد تصمیم سیستم طراحی است و در globals.css انجام می‌شود؛ مقادیر با آزمون src/test/layout-tokens.test.ts ثابت نگه داشته می‌شوند.
  • کلاس‌های نام‌دار (gap-layout-section-gap و …) را جای اجزای صفحه به کار نبرید. آن‌ها برای ردیف‌ها و قاب‌های سفارشی‌ای هستند که باید با هندسهٔ سیستم طراحی هم‌تراز شوند، نه برای بازسازی PageSection در هر صفحه.