Skip to content

سیستم طراحی

زبان طراحیای که ranui از آن ساخته شده، و فهرست کامل توکن‌هایی که آن را بیان می‌کنند: هر ویژگی سفارشی سراسری --ran-* که کتابخانه اعلام می‌کند، با مقدارش در هر دو پوسته. کامپوننت‌ها به‌جای نوشتن مقدار ثابت این توکن‌ها را می‌خوانند، پس بازنویسی یک توکن، ظاهر هر چیزی را که آن را مصرف می‌کند عوض می‌کند.

سه صفحه به سه پرسش متفاوت پاسخ می‌دهند و عمداً از هم جدا مانده‌اند:

صفحهپاسخ می‌دهد به
سیستم طراحی (همین صفحه)توکن‌ها چیستند: واژگان
راهنمای طراحیهنگام ساختن یک صفحه، چگونه میانشان انتخاب کنیم
پوسته‌بندیچگونه در زمان اجرا عوض و بازنویسی‌شان کنیم

کجا به کار می‌آید: وقتی نام یا مقدار یک توکن را لازم دارید (نقش یک رنگ، پله‌ای از فاصله، اندازه یک آیکن، رده‌ای از سایه، منحنی شتاب) یا می‌خواهید بدانید چرا مقیاس‌ها همین شکل را دارند.

زبان: Geist

توکن‌های ranui بر پایه Geist است، سیستم طراحی متن‌باز Vercel. هر مقیاس رنگ یک نردبان از وظیفه‌های ثابت است، برای هر پله یکی، نه مجموعه‌ای از سایه‌ها که از میانشان انتخاب کنید: پله ۲۰۰ «خاکستریِ کمی تیره‌تر» نیست، «پس‌زمینه هاور» است. وقتی وظیفه یک پله تثبیت شد، انتخاب رنگ برای یک حالت تعاملی به‌جای داوری، به یک جست‌وجو تبدیل می‌شود.

ranui همان نردبان را به‌شکل مقیاس‌های --ran-* خود برمی‌دارد، توکن‌های معنایی را رویش می‌گذارد، و Geist Sans / Geist Mono را به‌عنوان قلم‌های پیش‌فرض همراه می‌آورد.

دو لایه

لایه ۱: پالت پایه. همان مقیاس‌های خام پایین. به‌ندرت مستقیم مصرف می‌شوند.

لایه ۲: توکن‌های معنایی. --ran-color-* و هم‌خانواده‌هایش، که روی لایه ۱ نگاشت شده‌اند. همین لایه را مصرف کنید. حالت تیره تنها لایه ۱ را از نو تعریف می‌کند، پس هر توکن معنایی از راه var() می‌چرخد و هیچ‌جای کتابخانه بازنویسیِ تیرهٔ کامپوننت‌به‌کامپوننت وجود ندارد.

--ran-gray-1000        →  #171717 (روشن) / #ededed (تیره)        ← لایه ۱، می‌چرخد
--ran-color-text       →  var(--ran-gray-1000)                    ← لایه ۲، دنبال می‌کند
--ran-btn-color        →  var(--ran-color-text, …)                ← توکن کامپوننت

همین زنجیره تمام معماری است: یک پله پایه را عوض کنید و همه‌جا منتشر می‌شود؛ یک توکن معنایی را عوض کنید و یک نقش عوض می‌شود؛ یک توکن کامپوننت را عوض کنید و یک عنصر عوض می‌شود.

رنگ

نردبان

هر مقیاس رنگ از 100 تا 1000 می‌رود و هر پله یک وظیفه ثابت دارد:

پلهنقشپلهنقش
100پس‌زمینه پیش‌فرض600کادر در حالت فشرده
200پس‌زمینه هاور700پرشدگی توپر (دکمه/نشان)
300پس‌زمینه حالت فشرده800پرشدگی توپر (هاور)
400کادر پیش‌فرض900متن و آیکن ثانویه
500کادر هاور1000متن و آیکن اصلی

پس‌زمینه‌ها

توکنروشنتیرهکاربرد
--ran-background-100 #ffffff #000000پس‌زمینه صفحه
--ran-background-200 #fafafa #000000نواحی ملایم صفحه

خاکستری — --ran-gray-100..1000

مقیاسی که پشت متن و کادرها و سطح‌ها ایستاده است.

پلهروشنتیره
100 #f2f2f2 #1a1a1a
200 #ebebeb #1f1f1f
300 #e6e6e6 #292929
400 #eaeaea #2e2e2e
500 #c9c9c9 #454545
600 #a8a8a8 #878787
700 #8f8f8f #8f8f8f
800 #7d7d7d #7d7d7d
900 #4d4d4d #a0a0a0
1000 #171717 #ededed

خاکستری آلفا — --ran-gray-alpha-100..1000

نیمه‌شفاف است، پس روی هر سطحی می‌نشیند: انتخاب درست برای یک پرده، یک شست‌وشوی هاور، یا جداکننده‌ای که باید روی محتوای ناشناخته بنشیند.

پلهروشنتیره
100 #0000000d #ffffff12
200 #00000015 #ffffff17
300 #0000001a #ffffff21
400 #00000014 #ffffff24
500 #00000036 #ffffff3d
600 #0000003d #ffffff82
700 #00000070 #ffffff8a
800 #00000082 #ffffff78
900 #000000b3 #ffffff9c
1000 #000000e8 #ffffffeb

آبی — --ran-blue-100..1000

برای پیوندها و حلقه فوکوس کنار گذاشته شده است.

پلهروشنتیره
100 #f0f7ff #06193a
200 #e9f4ff #022248
300 #dfefff #002f62
400 #cae7ff #003674
500 #94ccff #00418b
600 #48aeff #0090ff
700 #006bff #006efe
800 #0059ec #005be7
900 #005ff2 #47a8ff
1000 #002359 #eaf6ff

قرمز — --ran-red-100..1000

خطر و ارور.

پلهروشنتیره
100 #ffeeef #330a11
200 #ffe8ea #440d13
300 #ffe3e4 #5d0e17
400 #ffd7d6 #6f101b
500 #ffb1b3 #88151f
600 #ff676d #f32e40
700 #fc0035 #f13242
800 #ea001d #e2162a
900 #d8001b #ff565f
1000 #47000c #ffe9ed

کهربایی — --ran-amber-100..1000

هشدارها.

پلهروشنتیره
100 #fff6de #2a1700
200 #fff4cf #361900
300 #fff1c1 #502800
400 #ffdc73 #5b3000
500 #ffc543 #703e00
600 #ffa600 #ed9a00
700 #ffae00 #ffae00
800 #ff9300 #ff9300
900 #aa4d00 #ff9300
1000 #561900 #fff3d5

سبز — --ran-green-100..1000

موفقیت.

پلهروشنتیره
100 #ecfdec #002608
200 #e5fce7 #00320b
300 #d3fad1 #003a0e
400 #b9f5bc #004615
500 #82eb8d #006717
600 #4ce15e #00952d
700 #28a948 #00ac3a
800 #279141 #009432
900 #107d32 #00ca50
1000 #003a00 #d8ffe4

توکن‌های معنایی رنگ

لایه‌ای که کامپوننت‌ها واقعاً می‌خوانند. هرچه اینجاست از راه مقیاس‌های بالا حل می‌شود، پس خودش با پوسته می‌چرخد.

توکنحل می‌شود بهنقش
--ran-color-bg--ran-background-100پس‌زمینه صفحه
--ran-color-bg-subtle--ran-background-200نواحی ملایم صفحه
--ran-color-bg-elevated--ran-background-100 · gray-100 (تیره)کارت‌ها، سطح‌ها
--ran-color-bg-muted--ran-gray-100پرشدگی فرورفته یا خفه
--ran-color-bg-hover--ran-gray-200سطح هاور
--ran-color-bg-active--ran-gray-300سطح حالت فشرده
--ran-color-text--ran-gray-1000متن اصلی
--ran-color-text-secondary--ran-gray-900متن ثانویه
--ran-color-text-disabled--ran-gray-700متن غیرفعال
--ran-color-border--ran-gray-400کادر پیش‌فرض
--ran-color-border-secondary--ran-gray-300کادر ملایم‌تر
--ran-color-border-hover--ran-gray-500کادر هاور
--ran-color-border-active--ran-gray-600کادر حالت فشرده
--ran-color-primary--ran-gray-1000کنش اصلی (تک‌رنگ)
--ran-color-primary-hover #383838 · #cccccc (تیره)هاورِ primary
--ran-color-primary-active #4d4d4d · #b3b3b3 (تیره)فشردنِ primary
--ran-color-primary-text--ran-background-100جوهری که روی سطح primary می‌نشیند
--ran-color-success--ran-green-700موفقیت
--ran-color-warning--ran-amber-700هشدار
--ran-color-danger--ran-red-700خطر / ارور
--ran-color-link--ran-blue-700پیوندها

--ran-color-primary-hover / -active دو مقدار ثابت لایه معنایی‌اند: به‌جای حرکت در طول یک مقیاس، به‌سوی پس‌زمینه صفحه گام برمی‌دارند، پس حالت تیره مستقیم از نو تعریفشان می‌کند.

هر رنگ تأکیدی چه معنایی دارد

  • primary تک‌رنگ است: در روشن سیاه روی سفید، در تیره سفید روی سیاه (لحن برند Geist، <r-button type="primary">). متن و آیکن‌های رویش --ran-color-primary-text را به کار می‌برند که همراهش می‌چرخد. توکن جداگانه‌ای برای «کنتراست» نیست: primary خودش پرکنتراست‌ترین کنش است.
  • آبی کنار گذاشته شده برای پیوندها (--ran-color-link) و حلقه فوکوس. جایگزینی برای primary نیست.
  • سبز = موفقیت · کهربایی = هشدار · قرمز = خطر. هرکدام یک معنا.

چیزی به نام --ran-color-error وجود ندارد؛ توکن --ran-color-danger است. var()ی که ویژگی اعلام‌نشده‌ای را نام ببرد به هیچ حل نمی‌شود و کل اعلان بی‌صدا دور ریخته می‌شود؛ به همین دلیل ارزش دارد نام مشکوک را به‌جای حدس زدن با همین جدول بسنجید.

فاصله

شکاف میان چیزها: padding، margin، gap. واحد پایه ۴ پیکسل با نه مقدار، نه بیشتر:

توکنمقدارتوکنمقدار
--ran-space-14px--ran-space-832px
--ran-space-28px--ran-space-1040px
--ran-space-312px--ran-space-1664px
--ran-space-416px--ran-space-2496px
--ran-space-624px

عدد، ضریب ۴ پیکسل است، پس مقیاس می‌پرد: --ran-space-5 وجود ندارد. نکته همین است: مجموعه‌ای محدود همان چیزی است که ریتم یک صفحه را می‌سازد.

اندازه

ابعاد خودِ عنصر: اندازه آیکن، ارتفاع کنترل، کنترل‌های کوچک مربعی یا مستطیلی.

توکنمقدارمعمولاً
--ran-size-116pxجعبه چک‌باکس، آیکن کوچک درون‌خطی
--ran-size-218px
--ran-size-320pxآیکن درون یک کنترل
--ran-size-424pxدکمه آیکنی در نوار ابزار
--ran-size-528pxارتفاع کنترل فشرده
--ran-size-630px
--ran-size-732pxارتفاع پیش‌فرض کنترل

این عمداً مقیاسی جدا از فاصله است و قاطی کردنشان خطایی است که ماشین می‌گیرد (sizing-scale). این دو دامنه و پیشرَوی متفاوتی دارند (مقیاس فاصله‌ای که از ۴ پیکسل دوبرابر می‌شود، برای اندازه آیکن و کنترل مقدارهای ناجوری می‌دهد) و مصرف‌کننده باید بتواند یکی را کوک کند بی‌آنکه دیگری را به هم بزند: بزرگ‌تر شدن یک آیکن نباید هر شکافی را هم که اتفاقاً همان مقدار پیکسلی را دارد پهن کند. اگر پله‌ای عددی با پله‌ای از فاصله یکی درآید (--ran-size-4 و --ran-space-6 هر دو ۲۴ پیکسل‌اند) این تصادف است، نه هم‌نامی.

بُعدی که واقعاً یک‌باره است و هیچ کامپوننت دیگری در آن شریک نیست (مثلاً min-width یک منو) به‌جای اینکه به زور در یک پله جا داده شود، همان توکن سادهٔ کامپوننت با مقدار جایگزین ثابت خودش می‌ماند.

تایپوگرافی

توکنمقدار
--ran-font-familyGeist / Geist Sans، و سپس پشته رابط سیستم
--ran-font-monoGeist Mono، و سپس ui-monospace، SF Mono، Menlo، Consolas، …
--ran-font-size14px (اندازه پایه)
--ran-line-height1.5715

متن بر پایه نقش سامان می‌یابد و نقش، قلم و اندازه و وزن و ارتفاع خط را با هم تعیین می‌کند:

نقشکاربردتوکن وزنتوکن‌های اندازه
headingعنوان‌ها--ran-text-heading-weight (600)--ran-text-heading-1..4 (32/24/20/16px)
labelتک‌خطی، برای مرور سریع--ran-text-label-weight (500)--ran-text-label-1..3 (14/13/12px)
copyبدنه چندخطی--ran-text-copy-weight (400)--ran-text-copy-1..2 (16/14px)
buttonمتن دکمه--ran-text-button-weight (500)--ran-text-button-size (14px)
monoکد، داده، پیش‌عنوان--ran-text-mono-weight-regular (400) / --ran-text-mono-weight-medium (500)اندازه‌های label / copy را قرض می‌گیرد

دو توکن فقط برای اینکه یک نقش درست بنشیند وجود دارند:

توکنمقدارچرا
--ran-text-heading-tracking-0.03emعنوان‌ها در اندازه‌های بزرگ به فاصله حروفِ تنگ‌تر نیاز دارند.
--ran-text-button-line-height1وسط‌چینی عمودیِ تمیز درون کنترلی با ارتفاع ثابت.

Geist سقف وزن را روی ۶۰۰ (سمی‌بولد) می‌گذارد. تأکید از اندازه و فاصله می‌آید نه از قلمی سنگین‌تر. --ran-text-copy-3 وجود ندارد: پله ۱۲ پیکسل همان --ran-text-label-3 است.

قلم‌ها

ranui هر دو قلم را خودش میزبانی می‌کند (وزن متغیر ۱۰۰ تا ۹۰۰، SIL OFL 1.1)، پس یک import آن‌ها را بدون وابستگی به CDN بار می‌کند:

js
import 'ranui/fonts'; // برای باندلرها
html
<link rel="stylesheet" href="…/ranui/dist/fonts/fonts.css" />

بدون آن، توکن‌ها به پشته‌های قلم سیستم برمی‌گردند؛ همه‌چیز باز هم کار می‌کند، فقط بدون قلم‌های Geist.

شعاع گوشه

توکنمقدارکاربرد
--ran-radius-sm6pxکنترل‌ها: دکمه، ورودی، انتخابگر
--ran-radius-md12pxکارت‌ها، دیالوگ‌ها
--ran-radius-lg16pxسطح‌های بزرگ
--ran-radius-full9999pxقرص‌ها، آواتارها

ارتفاع سایه

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

توکنکاربردروشنتیره
--ran-shadow-elevatedسطح‌هایی که در جریان صفحه‌اند و کادر هم دارند: r-card، r-section0 1px 2px rgba(0,0,0,.04), 0 2px 4px -2px rgba(0,0,0,.05)0 1px 2px rgba(0,0,0,.16)
--ran-shadow-menuلایه‌های گذرا روی محتوا: منوی کشویی، انتخابگر، پاپ‌اور، اعلان0 2px 4px rgba(0,0,0,.05), 0 8px 24px -6px rgba(0,0,0,.14)0 1px 1px rgba(0,0,0,.2), 0 4px 8px -4px rgba(0,0,0,.4), 0 16px 24px -8px rgba(0,0,0,.5)
--ran-shadow-modalدیالوگ‌هایی که راه را می‌بندند: r-modal0 4px 12px rgba(0,0,0,.08), 0 20px 48px -12px rgba(0,0,0,.22)0 1px 1px rgba(0,0,0,.2), 0 8px 16px -4px rgba(0,0,0,.4), 0 24px 32px -8px rgba(0,0,0,.5)

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

چیدمان لایه‌ها

لایه‌های شناور به <body> پرتال می‌شوند، پس به رده‌ای صریح نیاز دارند:

توکنپیش‌فرضکاربرد
--ran-z-modal1000دیالوگ‌هایی که راه را می‌بندند و ماسکشان
--ran-z-dropdown1100منوی کشویی / انتخابگر / پاپ‌اور: بالای مودال، تا انتخابگرِ درون یک دیالوگ دیده شود
--ran-z-message1200اعلان‌ها و آگاهی‌ها: همیشه بالاتر از همه

نردبان از ۱۰۰۰ آغاز می‌شود تا از قاب معمول صفحه بگذرد (نوارهای ناوبری و پرده‌ها معمولاً در دهگان‌اند). یک رده را روی :root بازنویسی کنید، یا به تفکیک کامپوننت (--ran-dropdown-host-z-index، --ran-modal-root-z-index، --ran-message-z-index)، اما هرگز با !important.

حرکت

توکنمقدارکاربرد
--ran-motion-duration-fast0.15sگذارهای حالت هاور و فشرده
--ran-motion-duration-base0.2sپاپ‌اورها، منوها
--ran-motion-duration-slow0.35sنمایان‌شدن‌های بزرگ‌تر
توکن شتابمنحنیخصلت
--ran-motion-ease-standardcubic-bezier(0.645,0.045,0.355,1)رفت و برگشت، همه‌کاره
--ran-motion-ease-snappycubic-bezier(0.33,0,0.15,1)سریع و بدون رد شدن از هدف: کلیدها
--ran-motion-ease-springcubic-bezier(0.34,1.26,0.5,1)رد شدن اندک از هدف: دکمه‌ها، کارت‌ها
--ran-motion-ease-bouncycubic-bezier(0.34,1.56,0.64,1)رد شدن بازیگوشانه: لایک، افزودن به سبد
--ran-motion-ease-smoothcubic-bezier(0.4,0,0.2,1)آرام و بدون رد شدن: نمایان‌شدن، چیدمان

خانواده spring از فنرهای کوک‌شده SwiftUI تقطیر شده است (response/damping به یک بزیهٔ تک‌گذر از هدف فرو کاسته شده).

این‌ها را تنها با ویژگی‌های حرکتی جفت کنید: transform، opacity، هندسه جعبه. ویژگی‌های پالت (background-color، color، border-color، box-shadow، fill، stroke) عمداً هیچ گذار پیش‌فرضی ندارند، چون CSS نمی‌تواند تعامل را از چرخش پوسته تشخیص دهد: هر محوی که به یک رنگ بیفزایید، هنگام تعویض روشن↔تیره هم شلیک می‌شود. با این حال هر کامپوننت یک قلاب --ran-*-transition دارد، اگر خواستید دوباره روشنش کنید.

فوکوس

توکنمقداربرای
--ran-focus-ring0 0 0 2px var(--ran-background-100), 0 0 0 4px var(--ran-blue-700)حلقه استاندارد، به‌شکل box-shadow
--ran-focus-ring-inverse-color#fffرنگ حلقه برای سطحی که در هر دو پوسته تیره است

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

--ran-focus-ring-inverse-color عمداً در حالت تیره از نو تعریف نمی‌شود: برای کامپوننتی هست که سطح خودش صرف‌نظر از پوسته صفحه همیشه تیره است (نوار کنترل r-player روی هر ویدیویی) و آن سطح با تغییر صفحه عوض نمی‌شود.

ابتدایی‌های پوسته

همان چند مقدار ساختاری که کامپوننت‌ها مشترک دارند و رنگ و اندازه و متن نیستند. عمداً کمینه نگه داشته شده‌اند: این لایه زمانی خیلی بزرگ‌تر بود و بیشترش همراه بسته‌های پوسته برداشته شد.

توکنمقداربرای
--ran-skin-border-width1pxضخامت کادری که کامپوننت‌ها می‌کشند
--ran-skin-border-stylesolidسبک کادری که کامپوننت‌ها می‌کشند
--ran-skin-border-image-width4pxتورفتگی border-image-slice که button/checkbox/input/modal/message در آن شریک‌اند
--ran-skin-raised-shadowvar(--ran-shadow-elevated)سایه سطح برجسته، با یک واسطه تا پوسته بتواند عوضش کند
--ran-skin-font-familyvar(--ran-font-family)قلمی که کامپوننت‌ها به کار می‌برند، با همان واسطه

حالت تیره چه چیزی را از نو تعریف می‌کند

data-ran-theme="dark" روی <html> (یا روی هر زیردرختی، پوسته‌بندی را ببینید) فقط پالت پایه را از نو تعریف می‌کند، با سه استثنا که از راه یک مقیاس حل نمی‌شوند:

  • تمام لایه ۱: هر پله خاکستری، خاکستری آلفا، آبی، قرمز، کهربایی و سبز، و هر دو پس‌زمینه؛
  • --ran-color-bg-elevated که در تیره به --ran-gray-100 اشاره می‌کند تا کارت از صفحه سیاه بالا بیاید نه اینکه در آن گم شود؛
  • --ran-color-primary-hover / -active که مقدار ثابت‌اند نه ارجاع به مقیاس؛
  • هر سه رده سایه، که برای زمینه تیره از نو کوک شده‌اند.

باقی همه‌چیز (هر توکن معنایی دیگر، هر اندازه، هر مدت) یک بار تعریف می‌شود.

توکن‌های کامپوننت

پایین‌تر از لایه معنایی، هر کامپوننت قلاب‌های خودش را با این نام‌گذاری در اختیار می‌گذارد:

--ran-{component}-{element}[-{state}]-{property}

مثلاً --ran-btn-hover-background، --ran-select-search-active-border-width. این‌ها به‌طور پیش‌فرض به توکن‌های معنایی برمی‌گردند: var(--ran-btn-background, var(--ran-color-primary, #171717))؛ پس بازنویسی یک توکن معنایی به همه‌شان می‌رسد و بازنویسی یک توکن کامپوننت، تغییر را به یک عنصر محدود می‌کند.

فهرست کاملِ تولیدشده در style-tokens-public.md در مخزن است؛ API به‌تفکیک عنصر اینجاست. برای شیوه اعمالشان پوسته‌بندی را ببینید.

به‌کارگیری توکن‌ها در CSS خودتان

css
.panel {
  background: var(--ran-color-bg-elevated);
  color: var(--ran-color-text);
  border: var(--ran-skin-border-width) var(--ran-skin-border-style) var(--ran-color-border);
  border-radius: var(--ran-radius-md);
  padding: var(--ran-space-4);
  box-shadow: var(--ran-shadow-elevated);
}

سه قاعده آن را در حالت تیره امن نگه می‌دارد:

  1. برای هرچه باید از پوسته پیروی کند هیچ کد شانزده‌شانزدهی خامی ننویسید.
  2. مقدار جایگزین باید توکنی را نام ببرد که می‌چرخد: var(--ran-color-text, var(--ran-gray-1000))، نه var(--ran-color-text, #171717).
  3. مقدار جایگزین باید توکنی موجود را نام ببرد، وگرنه اعلان دور ریخته می‌شود و عنصر بی‌صدا همان چیزی را که به ارث برده نگه می‌دارد.

هر توکن سراسری‌ای که کتابخانه اعلام می‌کند در همین صفحه فهرست شده، و اگر توکنی بدون مستندسازی در اینجا افزوده شود یک تست واحد شکست می‌خورد. توکن‌های محدود به کامپوننت جداگانه تولید می‌شوند، در style-tokens-public.md.

منتشرشده تحت مجوز MIT.