nili/ui

مستندات

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

نصب

پکیج روی یک اسکوپ خصوصی npm است، پس npm پیش از دیدنش به یک توکن فقط‌خواندنی نیاز دارد. توکن را در ‎.npmrc بگذارید — هم لوکال هم روی CI — و از آن‌جا به بعد نصب معمولی است. ری‌اکت ۱۸ و ۱۹ هر دو پشتیبانی می‌شوند؛ آیکون‌ست یک peer dependency است تا نسخه‌اش دست اپ شما باشد.

# .npmrc — locally and in CI
@nili:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${NILI_TOKEN}

npm i @nili/ui @remixicon/react

پرووایدر

NiliProvider دقیقاً مالک دو تصمیم سراسری است: چه تمی رنگ می‌شود و صفحه از کدام سمت خوانده می‌شود. عمداً مالک ترجمه نیست — کامپوننت‌ها رشته‌هایشان را به‌صورت پراپ می‌گیرند، پس کتابخانه بدون رانتایم i18n منتشر می‌شود و شما آزادید از i18next، react-intl، روتینگ نکست یا هیچ‌کدام استفاده کنید.

import { NiliProvider } from '@nili/ui';
import '@nili/ui/styles.css';

export default function App({ children }) {
  return (
    <NiliProvider locale="fa" defaultTheme="system">
      {children}
    </NiliProvider>
  );
}

جهت

هیچ پراپ جهتی وجود ندارد و موقع رندر هیچ‌جا document.dir خوانده نمی‌شود. کامپوننت‌ها با پراپرتی‌های منطقی نوشته شده‌اند، پس جهت از CSS ارث می‌رسد — به همین دلیل یک زیردرخت راست‌به‌چپ داخل صفحه‌ی چپ‌به‌راست کار می‌کند و رندر سمت سرور در همان پاس اول مارکاپ درست را می‌دهد.

// Every component is written this way — logical, not physical.
<div className="ms-4 ps-2 border-s">…</div>

// So a right-to-left island inside a left-to-right page just works,
// with no prop threaded down to make it happen.
<section dir="rtl" lang="fa">
  <Button endIcon={<RiArrowRightLine />}>ادامه</Button>
</section>

اعداد

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

import { formatNumber, parseLocalizedNumber } from '@nili/ui';

formatNumber(1234567, { locale: 'fa-IR' });  // ۱٬۲۳۴٬۵۶۷
formatNumber(1234567, { locale: 'ar-EG' });  // ١٬٢٣٤٬٥٦٧
parseLocalizedNumber('۱٬۲۳۴٬۵۶۷');           // 1234567

// And the escape hatch, for a value that must stay Latin:
<TextInput label="IBAN" latin defaultValue="IR82 0540 …" />

توکن و تم

کامپوننت‌ها هیچ‌وقت واریانت ‎dark:‎ نمی‌نویسند. هرکدام یک توکن معنایی را صدا می‌زنند و مقدار زیر پایشان عوض می‌شود، بر پایه‌ی اتریبیوت data-theme نه یک کلاس. اتریبیوت باز است، پس یک تم تازه — کنتراست بالا یا برند یک مشتری — یک استایل‌شیت است نه بازنویسی.

--nili-blue-500: #335CFF;        /* primitive  */
--nili-primary-base: …;          /* semantic   */
--color-primary-base: …;         /* @theme     */
class="bg-primary-base"          /* component  */

/* Dark mode is an attribute, not a class — so a third theme
   is [data-theme='high-contrast'] and nothing else changes. */
<html data-theme="dark">

دسترس‌پذیری

هرچیزی که لیبل دیدنی ندارد، در تایپ‌هایش یکی می‌خواهد و کنترل بی‌نام را قبول نمی‌کند — دکمه‌ی فقط‌آیکون به aria-label نیاز دارد و دکمه‌ی بستن به dismissLabel. قاعده این است که کنترلِ بی‌لیبل را سیستم تایپ بگیرد، نه کامنت رویو.

// The type system refuses an unlabelled control.
<Button iconOnly aria-label="Close" startIcon={<RiCloseLine />} />
<Tag onDismiss={remove} dismissLabel="Remove tag">Design</Tag>
<Alert title="Saved" urgency="polite" />

بعدش کامپوننت‌ها را مرور کنید — هرکدام با همان نمای کلی‌ای باز می‌شود که استوری‌بوک نشان می‌دهد و سورسش کنارش است. کامپوننت‌ها