فضابندی

سیستم فضابندی پرتو — مقیاس 4px پیش‌فرض Tailwind، spacing tokens، و اصول فاصله‌گذاری

اصول فضابندی

پرتو از مقیاس فضابندی پیش‌فرض Tailwind استفاده می‌کند — واحد پایه 4px است (space-1) و هر پله مضربی از همان 4px است، نه صرفاً مضرب 8. این با کاربرد واقعی خودِ Supabase Studio هم‌خوان است: شمارش مستقیم کلاس‌های gap-*/p-* در apps/studio/components/{ui,layouts} نشان می‌دهد gap-2/p-2 (8px) پرکاربردترین مقدار است، اما gap-1/p-1 (4px) و gap-3/p-3 (12px) هم به‌همان اندازه در ردیف اول کاربرد قرار دارند — نه یک استثنای نادر. در عمل، پرتو بین این پله‌ها انتخاب می‌کند:

4px  → space-1
8px  → space-2
12px → space-3
16px → space-4
24px → space-6
32px → space-8
40px → space-10
48px → space-12
64px → space-16

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

قوانین زیر چکیدهٔ لازم‌الاجرای این صفحه هستند؛ جزئیات و استدلال کامل هر قانون در بخش‌های بعدی همین صفحه آمده است.

فقط پله‌های مقیاس — هرگز مقدار دلخواه

// ✅ درست — پلهٔ استاندارد مقیاس 4px
<div className="p-4">بازخورد بسته‌بندی</div>

// ❌ غلط — مقدار دلخواه خارج از مقیاس
<div className="p-[13px]">بازخورد بسته‌بندی</div>
بازخورد بسته‌بندی
رونمایی محصول جدید
درست — هر دو p-4؛ لبهٔ متن‌ها روی یک خط
بازخورد بسته‌بندی
رونمایی محصول جدید
نادرست — p-[22px] کنار p-4؛ متن‌ها یک خط نیستند

مقادیر خارج از مقیاس یکنواختی بصری را می‌شکنند و در چگالی‌های مختلف صفحه‌نمایش (1x/2x/3x DPI) روی پیکسل کامل نمی‌نشینند.

فضابندی جهت‌دار فقط با CSS Logical Properties

// ✅ درست — RTL-safe؛ در هر دو جهت صفحه درست رندر می‌شود
<div className="ps-4 pe-6">رونمایی محصول جدید</div>

// ❌ غلط — physical؛ در RTL جای start و end برعکس می‌شود
<div className="pl-4 pr-6">رونمایی محصول جدید</div>
رونمایی محصول جدید
درست — ps و border-s؛ نوار در ابتدای خط (راست در RTL)
رونمایی محصول جدید
نادرست — pl و border-l؛ در RTL نوار به انتهای خط می‌رود

پرتو RTL-first است؛ کلاس‌های فیزیکی (pl/pr/ml/mr) در چیدمان راست‌به‌چپ وارونه عمل می‌کنند و قانون ESLint parto/no-physical-css-properties آن‌ها را در زمان build خطا می‌دهد.

gap متناسب با سطح سلسله‌مراتب

// ✅ درست — inline با gap-2، فیلدهای فرم با gap-4، گروه‌های بزرگ‌تر یک کارت با gap-8
<div className="flex gap-2">{/* badge ها */}</div>
<div className="flex flex-col gap-4">{/* فیلدهای فرم */}</div>
<div className="flex flex-col gap-8">{/* گروه‌های محتوای یک کارت */}</div>

// ❌ غلط — سلسله‌مراتب وارونه؛ badge ها پراکنده و گروه‌ها چسبیده
<div className="flex gap-8">{/* badge ها */}</div>
<div className="flex flex-col gap-2">{/* گروه‌های محتوای یک کارت */}</div>
اینستاگرامتلگرام
مثبتمنفی
درست — درون گروه gap-2، میان گروه‌ها gap-6
اینستاگرامتلگرام
مثبتمنفی
نادرست — درون گروه دورتر از میان گروه‌ها؛ گروه‌بندی گم می‌شود

فاصلهٔ بزرگ‌تر یعنی ارتباط کمتر؛ وقتی پله‌های gap با سطح سلسله‌مراتب هم‌خوان نباشند، چشم گروه‌بندی محتوا را اشتباه می‌خواند. فاصلهٔ سرِ صفحه، بخش‌ها و بلوک‌های صفحه از این مقیاس انتخاب نمی‌شود: از توکن‌های هندسهٔ صفحه می‌آید و PageSection خودش اعمالش می‌کند.

padding کامپوننت‌ها در محدودهٔ استاندارد

// ✅ درست — padding کارت در محدودهٔ استاندارد p-4 تا p-6
<Card className="p-6">تحلیل گفت‌وگوهای کمپین</Card>

// ❌ غلط — خارج از محدودهٔ استاندارد؛ چگالی صفحه را ناهماهنگ می‌کند
<Card className="p-12">تحلیل گفت‌وگوهای کمپین</Card>
تحلیل گفت‌وگوهای کمپین
درست — padding کارت در بازهٔ p-4 تا p-6
تحلیل گفت‌وگوهای کمپین
نادرست — p-12؛ کارت از کارت‌های کنارش پف‌کرده‌تر است

اندازه‌های استاندارد (جدول «اندازه‌های استاندارد کامپوننت» در ادامهٔ همین صفحه) چگالی یکنواختی در کل محصول تضمین می‌کنند؛ کارتی با padding دوبرابر، هم‌ردیف‌های خود را نامتوازن نشان می‌دهد.

فاصلهٔ عمودی متن با یک مقدار واحد روی والد

// ✅ درست — یک space-y-4 روی والد، ریتم یکنواخت بین پاراگراف‌ها
<div className="space-y-4">
  <p>خلاصهٔ بازخورد مشتریان دربارهٔ کمپین تخفیف فصلی</p>
  <p>جمع‌بندی نظرات دربارهٔ بسته‌بندی جدید</p>
</div>

// ❌ غلط — margin دستی و متفاوت روی هر پاراگراف
<div>
  <p className="mb-5">خلاصهٔ بازخورد مشتریان دربارهٔ کمپین تخفیف فصلی</p>
  <p className="mb-2">جمع‌بندی نظرات دربارهٔ بسته‌بندی جدید</p>
</div>

خلاصهٔ بازخورد مشتریان دربارهٔ کمپین تخفیف فصلی

جمع‌بندی نظرات دربارهٔ بسته‌بندی جدید

پیشنهاد برای کمپین بعدی

درست — یک space-y-4 روی والد

خلاصهٔ بازخورد مشتریان دربارهٔ کمپین تخفیف فصلی

جمع‌بندی نظرات دربارهٔ بسته‌بندی جدید

پیشنهاد برای کمپین بعدی

نادرست — margin جدا روی هر پاراگراف؛ ریتم ناهمسان

یک مقدار واحد روی والد، ریتم متن را در یک نقطه کنترل می‌کند؛ margin های پراکنده با هر ویرایش از هم فاصله می‌گیرند و یکنواختی را می‌شکنند.


نمونه بصری — مقیاس فضابندی

space-1 / 4px
فاصله خیلی کوچک — بین آیکون و متن
space-2 / 8px
فاصله پایه — gap بین badge ها
space-3 / 12px
فاصله کوچک — gap بین دکمه‌ها
space-4 / 16px
فاصله متوسط — gap بین form field ها
space-6 / 24px
فاصله بزرگ — بین بخش‌های card
space-8 / 32px
فاصله خیلی بزرگ — بین section ها
space-10 / 40px
فاصله page — padding card
space-12 / 48px
فاصله page padding

چرا این مقیاس؟

  • سازگار با اندازه‌های پیکسلی اکثر صفحه‌نمایش‌ها (1x، 2x، 3x DPI)
  • تصمیم‌گیری فضابندی را ساده می‌کند — همیشه از مقیاس استفاده کنید، نه مقدار دلخواه (p-[13px])
  • یکنواختی بصری در سراسر محصول — و همان مقیاسی که Supabase Studio در عمل استفاده می‌کند

padding و margin

padding داخلی کامپوننت‌ها

// padding کوچک — برای badge، tag
<span className="px-2 py-1">...</span>

// padding متوسط — برای button، input
<button className="px-4 py-2">...</button>

// padding بزرگ — برای card، section
<div className="p-6">...</div>

حاشیهٔ خود صفحه عدد نیست: PageContainer آن را از توکن --layout-page-inset می‌گیرد (16، 24 و 40 پیکسل). هندسهٔ صفحه را ببینید.

فاصله بین اجزا (gap)

// gap کوچک — بین badge ها و chip ها
<div className="flex gap-2">...</div>

// gap متوسط — بین button ها
<div className="flex gap-3">...</div>

// gap استاندارد — بین form field ها
<div className="flex flex-col gap-4">...</div>

// gap بزرگ — بین گروه‌های یک کارت یا پنل
<div className="flex flex-col gap-8">...</div>

بین بخش‌ها و بلوک‌های صفحه gap نمی‌نویسید: PageSection فاصلهٔ 48 و 24 را از توکن‌ها می‌گیرد.


CSS Logical Properties در فضابندی

چون پرتو RTL-first است، از CSS Logical Properties برای فضابندی جهت‌دار استفاده کنید:

// فضابندی جهت‌دار — RTL-safe
<div className="ps-4 pe-6">     {/* padding-inline-start / end */}
<div className="ms-2 me-4">     {/* margin-inline-start / end */}
<div className="border-s-2">    {/* border-inline-start */}

// نه این
<div className="pl-4 pr-6">    {/* physical — در RTL برعکس می‌شود */}

فضابندی در Layout

چیدمان صفحه (عرض ستون، حاشیهٔ کنار، فاصلهٔ سرِ صفحه تا اولین بلوک، فاصلهٔ بخش‌ها و بلوک‌ها) از توکن‌های --layout-* می‌آید و اجزای صفحه آن را اعمال می‌کنند؛ کد صفحه برای آن‌ها کلاس فاصله نمی‌نویسد. جدول کامل و قواعد: هندسهٔ صفحه.

ساختار صفحه داشبورد

صفحه‌ای داخل ProductFrame یک قالب صفحه است، این‌جا DashboardPage: فاصلهٔ بالای عنوان از قاب، فاصلهٔ سرِ صفحه تا شاخص‌ها، شبکهٔ شاخص‌ها (ستون‌هایی با کمینهٔ --layout-tile-min-width) و فاصلهٔ بخش‌ها و نمودارها را قالب از توکن‌های --layout-* می‌دهد، و صفحه هیچ کلاس فاصله یا شبکه‌ای نمی‌نویسد:

<DashboardPage
  title="داشبورد"
  period={<DateRangePicker value={period} onChange={setPeriod} />}
  kpis={metrics.map((metric) => (
    <MetricCard key={metric.id}>{/* برچسب، مقدار و تغییر شاخص */}</MetricCard>
  ))}
>
  <DashboardSection title="روند گفت‌وگو">
    <DashboardChart title="روند منشن‌ها">{/* نمودار */}</DashboardChart>
  </DashboardSection>
</DashboardPage>

اندازه‌های استاندارد کامپوننت

padding کنترل‌ها از مقیاس اندازه می‌آید، نه از یک جدول توصیه. جدول قبلیِ این بخش برای دکمهٔ sm مقدار px-3 py-1.5 می‌گفت درحالی‌که مقدار واقعی px-3 py-2 است، و «Button default» را px-4 py-2 می‌نامید که در عمل پلهٔ md است نه پیش‌فرض پروژه. مقیاس کامل و به‌روز در اندازه و چگالی.

آنچه به مقیاسِ کنترل مربوط نیست و اینجا می‌ماند — فاصلهٔ داخلی ظرف‌ها:

ظرفpadding
Cardp-4 تا p-6
Modalp-6
Page sectionاز توکن‌ها (PageSection)

فضابندی متن

برای خوانایی بهتر متن فارسی:

// line-height مناسب برای فارسی
<p className="leading-relaxed">متن با فاصله خط مناسب</p>

// فاصله بین پاراگراف‌ها
<div className="space-y-4">
  <p>پاراگراف اول</p>
  <p>پاراگراف دوم</p>
</div>

// heading با فاصله مناسب از محتوا
<div>
  <h2 className="text-heading mb-3">عنوان</h2>
  <p className="text-light">محتوا</p>
</div>

خلاصه قوانین

قانونمقدار
واحد پایه (اتمی)4px (space-1)
پرکاربردترین پله‌های عملی8px (space-2) و 12px (space-3)
gap بین inline elementsgap-2 یا gap-3
gap بین block elementsgap-4
gap بین section ها--layout-section-gap (48) — PageSection
padding کارتp-4 تا p-6
padding page--layout-page-inset (16 / 24 / 40) — PageContainer