پرتوپرتو

نصب و راه‌اندازی

تنها روش رسمی راه‌اندازی دیزاین سیستم پرتو — Tailwind v4 با یک import در CSS

یک دستور، یک import

راه‌اندازی پرتو یک مسیر رسمی دارد: نصب پکیج و افزودن یک خط import به فایل CSS اصلی اپلیکیشن. همین import به‌تنهایی استایل کامپوننت‌ها، توکن‌های تم، نگاشت نقش‌ها به utilityهای Tailwind (bg-card، border-border، text-foreground/60، bg-brand، …)، و متغیر dark: را با هم و در ترتیب درست فراهم می‌کند.

پیش‌نیازها:

  • Node.js 18 یا بالاتر
  • React 19 یا بالاتر و React DOM 19 (peer dependency)
  • Tailwind CSS v4 (مسیر پیشنهادی؛ برای v3 به بخش سازگاری با Tailwind v3 مراجعه کنید)
  • Next.js 14+ یا هر باندلر مبتنی بر Vite/webpack

گام ۱ — نصب

pnpm add @partodata/ui
# پیر-دپندنسی‌ها (اگر از قبل نصب نیستند):
pnpm add react react-dom
# فقط اگر تعویض تم می‌خواهید:
pnpm add next-themes

پکیج، وابستگی‌های داخلی خود (recharts، @radix-ui/*، @visx/*، …) را همراه دارد؛ نیازی به سیم‌کشی دستی نیست.


گام ۲ — یک import در CSS

در فایل CSS اصلی اپلیکیشن (مثلاً app/globals.css در Next.js یا src/index.css در Vite)، بعد از @import "tailwindcss"; این خط را اضافه کنید:

/* app/globals.css */
@import 'tailwindcss';
@import '@partodata/ui/tailwind.css';

همین. tailwind.css این سه چیز را با ترتیب صحیح می‌آورد:

بخشنقش
استایل کامپوننت‌هاتمام CSS کامپایل‌شده‌ی کامپوننت‌ها به‌همراه توکن‌های تم (styles.css)
نگاشت نقش‌هاtheme.css — همان نقش‌های معنایی را برای کلاس‌هایی که خودتان می‌نویسید به utility تبدیل می‌کند
متغیر dark:@custom-variant dark که به هر دو نشانگر تیره‌ی پرتو ([data-theme='dark'] و .dark) وصل است

چرا یک import و نه چند تا؟

ترتیب import اهمیت دارد و tailwind.css آن را تضمین می‌کند و متغیر dark: را هم اضافه می‌کند. importهای جداگانه (styles.css + theme.css) فقط برای مصرف پیشرفته و کنترل دقیق‌تر لازم است — برای اکثر اپ‌ها همان یک خط کافی است.


گام ۳ — ویژگی‌های <html> (پیش‌فرض تیره)

پرتو تیره-اول است: تم پایه در :root تیره است، و روشن یک opt-in صریح زیر [data-theme='light'] / .light است. راه‌اندازی پیشنهادی برای یک اپ تک‌تم، تثبیت صریح نشانگرهای تیره روی <html> است تا SSR بدون پرش رنگ رندر شود:

// app/layout.tsx (Next.js App Router)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="fa" dir="rtl" className="dark" data-theme="dark" suppressHydrationWarning>
      <body>{children}</body>
    </html>
  )
}
ویژگیچرا لازم است
lang="fa"برای screen readerها و موتورهای جستجو
dir="rtl"برای کارکرد صحیح CSS Logical Properties الزامی است
className="dark" + data-theme="dark"هر دو نشانگر تیره؛ مصرف‌کننده‌ی تیره‌ی صریح دقیقاً مثل قبل رندر می‌شود
suppressHydrationWarningفقط اگر تم را به‌صورت داینامیک عوض می‌کنید (گام ۶) لازم است

اگر هیچ نشانگری نگذارید

یک اپ که هیچ تمی روی &lt;html&gt; نگذارد، حالا تیره رندر می‌شود (پیش‌فرض :root). برای روشن، صریحاً opt-in کنید: &lt;html className="light" data-theme="light"&gt;.


گام ۴ — فونت (یکان بخ)

فونت یکان بخ Variable داخل خود پکیج توزیع می‌شود (فایل woff2 در dist/assets/fonts/ و یک @font-face که با url() نسبی به آن اشاره می‌کند). با همان import گام ۲، فونت به‌صورت خودکار بارگذاری می‌شود — نیازی به self-host کردن ندارید و نباید فونت جداگانه اضافه کنید.

فقط در صورت مشکل (خودمیزبانی به‌عنوان راه‌حل جایگزین)

اگر باندلر شما url() داخل CSS پکیج‌های node_modules را resolve نمی‌کند (نادر — برخی لودرهای CSS سخت‌گیر)، فایل YekanBakh-VF.woff2 را از node_modules/@partodata/ui/dist/assets/fonts/ به فولدر public خود کپی کرده و @font-face خودتان را تعریف کنید. این یک fallback عیب‌یابی است، نه یک گام از نصب.


گام ۵ — Next.js: transpilePackages

فقط برای Next.js: پکیج باید در transpilePackages باشد، وگرنه build با SyntaxError: Cannot use import statement شکست می‌خورد.

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['@partodata/ui'],
}

export default nextConfig

این تنظیم مخصوص Next.js است؛ برای Vite/CRA لازم نیست.


گام ۶ — تعویض تم با next-themes (اختیاری)

اگر اپ شما بین روشن و تاریک جابه‌جا می‌شود، پرتو دو نشانگر برای تم دارد و هر دو باید هم‌گام بمانند: کلاس (.dark/.light) و attribute (data-theme). با next-themes این کار با یک آرایه در attribute انجام می‌شود:

// app/providers.tsx
'use client'

import { ThemeProvider } from 'next-themes'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider
      attribute={['class', 'data-theme']}
      defaultTheme="dark"
      themes={['light', 'dark']}
      enableSystem={false}
    >
      {children}
    </ThemeProvider>
  )
}

نکته‌ی هم‌گامی نشانگرها (ThemeSync)

اگر فقط یک نشانگر را ست کنید (مثلاً تنها attribute="class")، utilityهای dark: و مقادیر توکن ممکن است وسط تعویض با هم اختلاف پیدا کنند. همیشه attribute={['class', 'data-theme']} بدهید تا هر دو با هم آپدیت شوند. سایت مستندات همین کار را با یک کامپوننت ThemeSync انجام می‌دهد.

سوئیچ تم با هوک useTheme:

'use client'

import { useTheme } from 'next-themes'
import { Button } from '@partodata/ui'

export function ThemeSwitcher() {
  const { theme, setTheme } = useTheme()

  return (
    <div className="flex gap-2">
      <Button variant={theme === 'light' ? 'primary' : 'outline'} size="sm" onClick={() => setTheme('light')}>
        روشن
      </Button>
      <Button variant={theme === 'dark' ? 'primary' : 'outline'} size="sm" onClick={() => setTheme('dark')}>
        تاریک
      </Button>
    </div>
  )
}

گام ۷ — تأیید نصب

دو بررسی ساده که ثابت می‌کند فونت، توکن‌ها، تم، و اسکن Tailwind همگی کار می‌کنند:

۱. بررسی فونت — در کنسول مرورگر یا یک کامپوننت کلاینت:

'use client'

import { useEffect } from 'react'

export function FontCheck() {
  useEffect(() => {
    const family = getComputedStyle(document.body).fontFamily
    // باید شامل «Yekan Bakh» باشد
    console.log('font-family:', family)
  }, [])
  return null
}

۲. تست دودی سطوح — این بلوک را در هر صفحه رندر کنید:

<div className="bg-surface-100 text-foreground border border-default rounded-md p-4">
  اگر این کادر روی یک سطح تیره با حاشیه‌ی محو دیده می‌شود، توکن‌ها و تم درست بارگذاری شده‌اند.
</div>

اگر کادر بدون استایل (سفید/بی‌حاشیه) دیده شد، معمولاً import گام ۲ جا افتاده یا (در Tailwind v3) مسیر dist به content اضافه نشده است.


گام ۸ — فعال‌سازی گیت‌های مصرف

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

پلاگین ESLint

// eslint.config.js
import parto from '@partodata/ui/eslint-plugin'
import tsParser from '@typescript-eslint/parser'

export default [
  {
    files: ['src/**/*.{ts,tsx}'],
    // بدون parser مناسب، espreeِ پیش‌فرض ESLint 9 قادر به تحلیل JSX/TS نیست
    // و قوانین هرگز روی سورس واقعی اجرا نمی‌شوند.
    languageOptions: { parser: tsParser },
    plugins: { parto },
    rules: parto.configs.recommended.rules,
  },
]

11 قانون flat-config (9 روی error و 2 روی warn): بدون رنگ hardcode/پالت، بدون متن خارج از مقیاس، بدون CSS فیزیکی، الزام data-slot، جفت‌رنگ WCAG، بدون متن بدنه‌ی محو، و بدون letter-spacing روی فارسی.

اسکنر parto-design-lint

یک CLI بدون وابستگی که رشته‌های className و CSS خام را برای نقض‌هایی که قوانین AST نمی‌بینند اسکن می‌کند (مقادیر دلخواه text-[…]/rounded-[…px]، literalهای hex/rgb()/hsl()، کلاس‌های پالت خام، utilityهای جهت فیزیکی):

// package.json
{
  "scripts": {
    "design-lint": "parto-design-lint src",
    "build": "parto-design-lint src && next build"
  }
}

کدهای خروج: 0 تمیز · 1 نقض · 2 صفر فایل اسکن‌شده (مسیر اشتباه هرگز به‌جای «تمیز» جا نمی‌زند). این جایگزین اسکریپت‌های design-lint.sh است که هر اپ جداگانه نگه می‌داشت.

قرارداد مصرف

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


استفاده اولیه

import { Button, Card, CardContent, CardHeader, CardTitle } from '@partodata/ui'

export default function Page() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>عنوان کارت</CardTitle>
      </CardHeader>
      <CardContent>
        <p className="text-light">محتوای کارت</p>
        <Button className="mt-4">اقدام کنید</Button>
      </CardContent>
    </Card>
  )
}

برای اپ‌های حساس به حجم باندل، هر کامپوننت را از subpath خودش import کنید (مثلاً import { Button } from '@partodata/ui/button') — یک Button از ~۳۴۳KB بارل به ~۱۱KB می‌رسد.


Tailwind v3 (سازگاری)

اگر هنوز روی Tailwind v3 هستید، به‌جای import CSS-first از پیکربندی JS استفاده کنید. این مسیر در محصولات واقعی (مثل panel-new-design) اثبات شده است.

۱. preset + content glob

// tailwind.config.ts
import type { Config } from 'tailwindcss'
import partoConfig from '@partodata/ui/tailwind.config'

const config: Config = {
  presets: [partoConfig],
  darkMode: ['class', '[data-theme="dark"]'],
  content: [
    './app/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    // مسیر dist پکیج تا Tailwind کلاس‌های کامپوننت‌ها را purge نکند:
    './node_modules/@partodata/ui/dist/**/*.{js,cjs}',
  ],
}

export default config

۲. import استایل

در فایل CSS اصلی، استایل‌ها را قبل از دایرکتیوهای Tailwind خود import کنید:

@import '@partodata/ui/styles.css';
@tailwind base;
@tailwind components;
@tailwind utilities;

۳. پل توکن با var() خام

توکن‌های پرتو (v2+) رنگ کامل هستند؛ در Tailwind v3 آن‌ها را با var() خام پل بزنید — هرگز داخل hsl() نپیچید:

// tailwind.config.ts → theme.extend.colors
colors: {
  brand: 'var(--brand-default)',
  background: 'var(--background-default)',
  foreground: 'var(--foreground-default)',
  border: 'var(--border-default)',
  card: 'var(--background-surface-100)',
}
// در کد — رنگ کامل را مستقیم استفاده کنید
className = 'bg-brand text-foreground border-border'
// برای opacity در v3 از color-mix دلخواه استفاده کنید — نه /alpha:
// روی var() خام، Tailwind v3 مدیفایر /alpha را بی‌صدا نادیده می‌گیرد و رنگ را با opacity کامل رندر می‌کند
className =
  'bg-[color-mix(in_srgb,var(--brand-default)_10%,transparent)] text-[color-mix(in_srgb,var(--foreground-default)_60%,transparent)]'

/alpha فقط روی مسیر v4 کار می‌کند

مدیفایر /alpha (مثل text-foreground/60) فقط زمانی کار می‌کند که رنگ از یک ورودی @theme واقعی در Tailwind v4 بیاید (مسیر «گام ۲ — یک import در CSS» در همین صفحه). در Tailwind v3 با پل var() خام (بالا)، /alpha بی‌صدا شکست می‌خورد و رنگ با opacity کامل رندر می‌شود — همیشه color-mix دلخواه را جایگزین کنید.

۴. darkMode با دو انتخابگر

darkMode باید هم کلاس و هم attribute را بگیرد تا هر دو نشانگر تیره‌ی پرتو کار کنند:

darkMode: ['class', '[data-theme="dark"]']

مشکلات رایج و راه‌حل

کامپوننت‌ها بدون استایل نمایش داده می‌شوند

علت: import گام ۲ جا افتاده است.

/* ✅ درست — در app/globals.css */
@import 'tailwindcss';
@import '@partodata/ui/tailwind.css';

خطای build: SyntaxError: Cannot use import statement

علت: پکیج در transpilePackages نیست (فقط Next.js).

// next.config.mjs ✅
const nextConfig = { transpilePackages: ['@partodata/ui'] }

فونت یکان بخ اعمال نمی‌شود

علت: باندلر url() فونت را resolve نکرده. به گام ۴ و راه‌حل خودمیزبانی مراجعه کنید.

کلاس‌های Tailwind کار نمی‌کنند (فقط v3)

علت: مسیر dist به content اضافه نشده و Tailwind کلاس‌ها را purge کرده.

content: ['./node_modules/@partodata/ui/dist/**/*.{js,cjs}' /* ... */]

layout معکوس است (RTL کار نمی‌کند)

علت: dir="rtl" روی <html> نیست. CSS Logical Properties به این attribute نیاز دارند.

تم وسط تعویض با خودش اختلاف دارد

علت: فقط یک نشانگر ست شده. attribute={['class', 'data-theme']} را در ThemeProvider بدهید تا کلاس و data-theme هم‌گام بمانند.