Skip to content

Theming

نیمهٔ زمان‌اجرای استایل‌دهی ranui: جابه‌جایی میان حالت روشن، تیره و سیستمی، ماندگار کردن انتخاب، و بازنویسی توکن‌ها همان لحظه.

خودِ توکن‌ها (نامشان و اینکه هرکدام برای چیست) در سیستم طراحی آمده‌اند و قاعدهٔ انتخاب میان آن‌ها در راهنمای طراحی. این صفحه تنها دربارهٔ به‌کار بستن آن‌هاست.

کجا به کارش ببرید: وقتی در برنامه‌ای با ranui به پوستهٔ روشن/تیره نیاز دارید. هنگام بارگذاری یک بار initTheme را صدا بزنید، برای جابه‌جایی setTheme را، و اگر می‌خواهید بدون فرستادن CSS اضافه توکن‌های تکی را بازنویسی کنید setThemeToken(s) را.

دقیقاً دو پوسته وجود دارد: light و dark، به‌علاوهٔ حالت system که از ترجیح سیستم‌عامل پیروی می‌کند. (APIهای پیشین «بستهٔ پوسته» حذف شده‌اند؛ setThemePack و RanThemePackName دیگر وجود ندارند.)

شروع سریع

js
import { initTheme, setTheme, getTheme } from 'ranui/theme';

// بازیابی پوستهٔ ذخیره‌شده ('light' | 'dark' | 'system') از localStorage
initTheme();

// تعویض پوسته — خودکار ذخیره می‌شود
setTheme('dark');
setTheme('system'); // prefers-color-scheme را دنبال می‌کند و زنده به‌روز می‌شود

getTheme(); // → 'light' | 'dark' | 'system' | ''

نقطهٔ ورود اختصاصی ranui/theme تنها موتور پوسته را می‌دهد: import کردنش هیچ عنصر سفارشی‌ای ثبت نمی‌کند، پس صفحه‌ای که فقط توکن و حالت تیره می‌خواهد هرگز کتابخانهٔ کامپوننت‌ها را با خود نمی‌آورد. همین توابع از بستهٔ سطح‌بالای ranui هم دوباره صادر می‌شوند.

setTheme ویژگی data-ran-theme (و ویژگی قدیمی theme) را روی <html> می‌نویسد و استایل همهٔ کامپوننت‌ها به آن واکنش نشان می‌دهد. انتخاب زیر کلید ran-theme در localStorage ذخیره می‌شود.

اگر رابط آمادهٔ تعویض می‌خواهید از <r-theme-switch> استفاده کنید: کنترلی بخش‌بندی‌شده برای سیستم / روشن / تیره که از پیش به همین API وصل است، میان نمونه‌ها همگام می‌ماند و متاهای theme-color را هم به‌روز می‌کند.

API

تابعامضاتوضیح
initTheme(target?: ThemeTarget) => voidپوستهٔ ذخیره‌شده در localStorage را برمی‌گرداند. یک بار هنگام بارگذاری صدا بزنید. در SSR بی‌اثر است.
setTheme(name: RanThemeName, target?: ThemeTarget) => void'light' | 'dark' | 'system' را اعمال و ذخیره می‌کند. 'system' سیستم را زنده دنبال می‌کند.
getTheme(target?: ThemeTarget) => RanThemeName | ''پوستهٔ فعال را می‌خواند. در حالت سیستمی 'system' و اگر چیزی تعیین نشده باشد '' برمی‌گرداند.
setThemeToken(name: string, value: string | number, target?: HTMLElement) => voidدر زمان اجرا یک توکن را بازنویسی می‌کند (استایل درون‌خطی روی هدف).
setThemeTokens(tokens: ThemeTokenMap, target?: HTMLElement) => voidچند توکن را یک‌جا بازنویسی می‌کند. مقدار null / undefined آن توکن را پاک می‌کند.
clearThemeToken(name: string, target?: HTMLElement) => voidبازنویسی زمان‌اجرای یک توکن را برمی‌دارد.

تایپ‌ها

ts
type RanThemeName = 'light' | 'dark' | 'system';
type ThemeTarget = HTMLElement | Document; // پیش‌فرض document.documentElement
type ThemeTokenMap = Record<string, string | number | null | undefined>;

target: همهٔ توابع به‌طور پیش‌فرض <html> (document.documentElement) را هدف می‌گیرند. با دادن یک عنصر می‌توانید پوسته یا بازنویسی توکن را به‌جای کل صفحه به یک زیردرخت محدود کنید.

امن در SSR: هر دسترسی به document، localStorage و matchMedia محافظت‌شده است، پس این توابع هنگام رندر سمت سرور بی‌اثرند (و استثنا هم نمی‌اندازند).

حالت تیره چگونه کار می‌کند

setTheme('dark') روی <html> مقدار data-ran-theme="dark" را می‌گذارد. آنگاه شیوه‌نامه تنها پالت پایه را برای حالت تیره از یک مرجع یگانه از نو تعریف می‌کند؛ هر توکن معنایی --ran-color-* از راه var() به همان پالت ارجاع می‌دهد، پس خودبه‌خود برمی‌گردد و هیچ کامپوننتی بازنویسی حالت تیرهٔ خودش را حمل نمی‌کند.

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

  • CSS خودتان هم اگر توکن‌های معنایی را مصرف کند، حالت تیره را رایگان می‌گیرد — و اگر رنگی را سفت‌کد کند یا مقدار پشتیبانِ فقط‌روشن بنویسد، آن را نادرست می‌گیرد. استفاده از توکن‌ها در CSS خودتان را ببینید.
  • هنگام تعویض پوسته هیچ چیز نباید گذار داشته باشد. CSS نمی‌داند چرا رنگی عوض شده است، پس اگر روی یک ویژگی پالت transition بگذارید، با تعویض پوسته هر عنصر با شتاب خودش محو و ظاهر می‌شود. کامپوننت‌های ranui عمداً چنین نمی‌کنند؛ مال شما هم نباید بکند.

سفارشی‌سازی توکن‌ها

در زمان اجرا (JS)

js
import { setThemeToken, setThemeTokens, clearThemeToken } from 'ranui/theme';

// یک توکن، روی <html> (روی همه‌چیز اثر می‌گذارد)
setThemeToken('--ran-color-primary', '#7c3aed');

// چند تا با هم
setThemeTokens({
  '--ran-color-primary': '#7c3aed',
  '--ran-radius-md': '8px',
});

// محدود به یک زیردرخت
setThemeToken('--ran-color-primary', '#e11d48', document.querySelector('#panel'));

// برداشتن بازنویسی
clearThemeToken('--ran-color-primary');

در زمان ساخت (CSS)

توکن‌ها را زیر :root، یا در هر محدوده‌ای که بخواهید، بازنویسی کنید:

css
:root {
  --ran-color-primary: #7c3aed;
  --ran-radius-md: 8px;
}

کدام لایه را بازنویسی کنیم

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

  • برای تغییری که باید در هر دو پوسته یکسان باشد، توکن معنایی (--ran-color-primary) را بازنویسی کنید.
  • برای تغییری که باید همراه پوسته برگردد، پلهٔ مقیاس پایه (--ran-blue-700) را بازنویسی کنید: هر توکن معنایی‌ای که به آن ارجاع دارد دنبالش می‌آید.
  • برای تغییر دقیقاً یک عنصر، توکن کامپوننت (--ran-btn-hover-background) را بازنویسی کنید.

این لایه‌بندی به‌تمامی در صفحهٔ سیستم طراحی شرح داده شده است. توجه کنید که بازنویسی زمان‌اجرا یک استایل درون‌خطی روی هدف است: در آن زیردرخت بر قاعده‌های شیوه‌نامه پیروز می‌شود — همین است که پوسته‌دادن به هر پنل را ممکن می‌کند و همین است که یافتنِ بازنویسی‌ای که پاک کردنش را فراموش کرده‌اید بعداً دشوار می‌سازد.

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

هر تابعی یک هدف می‌پذیرد، پس یک قاب پیش‌نمایش می‌تواند پوسته‌ای متفاوت از صفحهٔ پیرامونش داشته باشد:

js
const preview = document.querySelector('#preview');

setTheme('dark', preview); // فقط همین زیردرخت
getTheme(preview); // → 'dark'

ویژگی به‌جای <html> روی همان عنصر می‌نشیند و باقی را آبشار توکن‌ها انجام می‌دهد.

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