@achareh/ui
Achareh UI
کتابخانهٔ کامپوننت Vue برای Nuxt و Vite. کامپوننتها با پیشوند U میآیند (UButton، UInput، UApp) و باید داخل UApp باشند. React را پوشش نمیدهد.
توکنهای رنگ، فاصله، شعاع، ضخامت border و تایپوگرافی از فیگمای پروژه آمدهاند. همانها را در CSS اپ عوض میکنید. پیشفرض variant هر کامپوننت جداست و از app.config یا گزینهٔ ui() میآید.
نصب
vue نسخهٔ ^3.5 و tailwindcss نسخهٔ ^4 لازم است. در Nuxt، خود Nuxt باید >=4.1.0 باشد.
Nuxt
pnpm add @achareh/ui tailwindcss
export default defineNuxtConfig({
modules: ['@achareh/ui'],
css: ['~/assets/css/main.css']
})
@import "tailwindcss";
@import "@achareh/ui";
<template>
<UApp>
<UButton label="Save" />
</UApp>
</template>
کامپوننت را دستی import نکنید. ماژول پیشوند U را میچسباند. نمونهٔ کامل در playgrounds/nuxt است.
Vue + Vite
pnpm add @achareh/ui tailwindcss
import vue from '@vitejs/plugin-vue'
import ui from '@achareh/ui/vite'
export default {
// `ui()` must come after `vue()` so component resolvers see compiled SFCs.
plugins: [vue(), ui()]
}
import ui from '@achareh/ui/vue-plugin'
app.use(ui)
@import "tailwindcss";
@import "@achareh/ui";
<template>
<UApp>
<UButton label="Save" />
</UApp>
</template>
هر دو قدم لازم است: ui() در Vite تم و auto-import را میسازد، و app.use(ui) پیشفرضهای ui را روی اپ اعمال میکند. ui() را قبل از vue() نگذارید. نمونهٔ کامل در playgrounds/vue است.
برای autocomplete تم، بعد از اولین اجرا این alias را به tsconfig اضافه کنید:
{
"compilerOptions": {
"paths": {
"#build/ui/*": ["./node_modules/.nuxt-ui/ui/*"]
}
}
}
پلاگین Vite فایلهای components.d.ts و auto-imports.d.ts را میسازد. آنها را gitignore کنید و به include تایپاسکریپت اضافه کنید.
کانفیگ
دو جا عوض میشود. رنگ، فاصله، شعاع، ضخامت و تایپ فقط در CSS اپ هستند. پیشفرض variant کامپوننت در کانفیگ اپ است.
| چه چیزی | کجا |
|---|---|
| توکن ظاهری | CSS اپ، بعد از @import "@achareh/ui" |
| پیشفرض variant | Nuxt: app.config.ts. Vue: همان آبجکت به ui({ ui }) |
فایل CSS در Nuxt app/assets/css/main.css است و در Vue src/assets/main.css.
اگر همان کامپوننت هم prop بگیرد هم در کانفیگ پیشفرض داشته باشد، prop صریح برنده است. بعد کانفیگ اپ، و در آخر پیشفرض خود تم. class و ui روی نمونه با کلاس تم ترکیب میشوند.
پیشفرض کامپوننت
پیشفرض را یکبار بنویسید. Nuxt همان آبجکت را در app.config.ts میگذارد. Vue همان را به ui({ ui }) میدهد.
// app/ui.ts در Nuxt، یا src/ui.ts در Vue
export const ui = {
button: {
defaultVariants: {
weight: 'light'
}
}
}
color، size و variant را اینجا میگذارید، نه با مقدار پیشفرض داخل خود کامپوننت. رنگ پالت از این آبجکت عوض نمیشود.
پیشوند نام کامپوننت پیشفرض U است. برای عوض کردنش در Nuxt ui.prefix را در nuxt.config بگذارید و در Vue همان را به ui({ prefix }) بدهید. اگر در Nuxt theme یا prefix گذاشتهاید، همان مقدار را به پلاگین Vite هم بدهید؛ Storybook فایل nuxt.config را خودش نمیخواند.
رنگ
مبنای سیستم رنگ، خروجی فیگمای پروژه است. پالت آمادهٔ Tailwind، مثل قرمز و آبی پیشفرض، اینجا رنگی نمیسازد.
شمارهٔ هر رنگ همان شمارهای است که در فیگما میبینید. صفر تیرهترین است و صد روشنترین. مثلاً primary با شمارهٔ ۰ سیاه است (#000000)، با شمارهٔ ۱۰۰ سفید (#FFFFFF) و با شمارهٔ ۵۰ رنگ میانی (#00DBBF). در عادت Tailwind شمارهٔ ۵۰ روشنترین است؛ اینجا آن معنا را ندارد.
رنگ را به سه شکل مینویسید:
- رنگ کامل یک پله، با خط تیره:
bg-primary-50. برای متن، حاشیه و حلقه هم همین است:text-primary-50،border-primary-50،ring-primary-50. - همان رنگ با پوشش کمتر، با اسلش:
bg-primary-50/high.highیعنی ۸۸٪ همان رنگ دیده شود. اگر درصد را خودتان بدهید، مثلbg-primary-50/50، همان درصد اعمال میشود. - رنگ روی دکمه و سطحها شماره ندارند:
hover:bg-on-primaryوhover:bg-on-primary/high.bg-primaryبدون شماره خودِ رنگ اصلی است. در حالت روشن همان پلهٔ ۴۰ است، نه پلهٔ ۵۰.
برای هماهنگ کردن رنگها با فیگمای پروژهتان، بعد از @import "@achareh/ui" بنویسید:
@import "tailwindcss";
@import "@achareh/ui";
@theme {
--color-primary-40: #0055ff;
--color-primary-80: #99bbff;
--color-primary-100: #ffffff;
--color-primary-20: #003322;
}
دکمه و کلاسهایی که در صفحه مینویسید هر دو همین رنگها را میگیرند. در حالت روشن پسزمینهٔ دکمه پلهٔ ۴۰ است و نوشتهٔ روی آن پلهٔ ۱۰۰. در حالت تیره پسزمینه پلهٔ ۸۰ است و نوشته پلهٔ ۲۰. اگر این چهار خط را ننویسید، پسزمینهٔ روشن #00BFA5 میماند. پلهای را که اینجا عوض کنید، رنگ قبلیاش در فایل نهایی CSS کنار گذاشته میشود.
secondary، tertiary، error، success، warning و info هم با همین شمارهها کار میکنند: ۴۰ و ۱۰۰ در روشن، ۸۰ و ۲۰ در تیره. پسزمینهٔ صفحه از neutral میآید، ۹۸ در روشن و ۶ در تیره. هر شمارهای را که ننویسید، همان رنگی میماند که از فیگما آمده.
اگر بخواهید پوشش high بهجای ۸۸٪ مقدار دیگری باشد:
:root {
--ui-emphasis-high: 80%;
}
رنگ semantic تازه (مثلاً brand) را در گزینهٔ theme.colors ماژول یا ui() اضافه کنید تا دکمه color="brand" بگیرد. خودِ رنگ را هم باید در @theme اپ تعریف کنید، وگرنه کلاسها ساخته میشوند ولی رنگی ندارند:
@theme {
--color-brand: #7c3aed;
--color-on-brand: #ffffff;
--color-brand-container: #ede9fe;
--color-brand-container-hover: #ddd6fe;
--color-brand-container-focused: #c4b5fd;
--color-on-brand-container: #2e1065;
}
فاصله
کلید کلاس همان پلهٔ فیگما است. p-4، gap-4 و w-4 پیشفرض ۸px هستند. شبکهٔ ۴px تیلویند اینجا نیست: p-8 برابر ۱۶px است. کلیدها از 0 تا 53 بهعلاوهٔ px (۱px) هستند.
@import "tailwindcss";
@import "@achareh/ui";
:root {
--ui-spacing-4: 10px;
}
--ui-spacing-4: 10px یعنی p-4، gap-4 و w-4 هر سه ۱۰px میشوند. همان متغیر روی margin، height، size-* و space-* هم اعمال میشود. پلهای که ننویسید روی پیشفرض میماند. واحد px است.
app.config و ui({ ui }) این مقیاس را نمیگیرند. @theme { --spacing-4: ... } هم پله را عوض نمیکند، چون یوتیلیتی مستقیم به --ui-spacing-* وصل است.
شعاع و ضخامت border
نام کلاس همان توکن فیگما است. rounded-sm برابر ۸px است، نه ۴px پیشفرض Tailwind. rounded-xs برابر ۴px، rounded-md برابر ۱۲px، rounded-lg برابر ۱۶px و rounded-full برابر ۹۹۹px است.
ضخامت: border-xs برابر ۱px، border-sm برابر ۲px، border-md برابر ۴px، border-lg برابر ۸px و border-none برابر ۰ است. کلاس border همان ۱px (border-xs) است. کلاسهای عددی Tailwind به همان پلهها وصلاند: border-0 برابر none، border-2 برابر sm، border-4 برابر md و border-8 برابر lg.
border-none علاوه بر ضخامت صفر، border-style: none خودِ Tailwind را هم میگذارد. پس border-none hover:border-xs در hover هم border نشان نمیدهد. اگر قرار است حالتی بعداً border بگیرد، از border-0 استفاده کنید.
:root {
--ui-radius-md: 10px;
--ui-border-width-sm: 2px;
}
--ui-radius-md: 10px یعنی rounded-md ده پیکسل میشود. --ui-border-width-xs روی کلاس border هم اعمال میشود. پلهای که ننویسید روی پیشفرض فیگما میماند. app.config این مقیاس را نمیگیرد. --ui-radius دیگر مقیاس را عوض نمیکند.
تایپوگرافی
نقشهای فیگما (حالت RTL-Fa) کلاسهایی با پیشوند typo- هستند. typo-label-large اندازه، ارتفاع خط، وزن، فاصلهٔ حروف و خانواده را با هم اعمال میکند. پیشفرض این نقش ۱۴px، خط ۲۰px، وزن ۵۰۰ و فاصلهٔ حروف ۰ است. خانوادهٔ مشترک KalamehFaNum است و اپ باید خود فونت را لود کند.
هر ویژگی کلاس جدا دارد و فقط همان ویژگی را عوض میکند:
typo-size-label-largeفقط اندازهtypo-leading-label-largeفقط ارتفاع خطtypo-weight-label-largeفقط وزنtypo-tracking-label-largeفقط فاصلهٔ حروفtypo-family-brandفقط خانواده، برای همهٔ نقشها
text-sm و font-medium مقیاس Tailwind میمانند و این نقشها را عوض نمیکنند.
@import "tailwindcss";
@import "@achareh/ui";
:root {
--ui-font-size-label-large: 18px;
--ui-font-family-brand: KalamehFaNum, sans-serif;
}
--ui-font-size-label-large: 18px یعنی هم typo-size-label-large و هم typo-label-large هجده پیکسل میشوند. ارتفاع خط، وزن و فاصلهٔ حروف همان نقش سر جایشان میمانند. ویژگیای که ننویسید روی پیشفرض فیگما میماند. واحد اندازه، خط و فاصلهٔ حروف px است.
app.config این مقیاس را نمیگیرد. اورراید از متغیر --ui-font-size-*، --ui-leading-*، --ui-font-weight-*، --ui-tracking-* و --ui-font-family-brand است.
استوریبوک
استوریهای منتشرشدهٔ @achareh/ui را با CSS و پیشفرضهای خودتان اجرا میکنید. استوری را کپی نکنید. استوریبوک داخل nuxt dev یا vite روشن نمیشود.
رنگ، فاصله، شعاع، ضخامت و فونت از همان CSS اپ میآیند. پیشفرض variant از همان آبجکت بخش «پیشفرض کامپوننت» میآید. پورت و هاست آرگومان دستور Storybook هستند.
بستهها را devDependency اپ کنید. نسخه را با همین ریپو یکی بگیرید (^10.6.0):
pnpm add -D storybook @storybook/vue3-vite @storybook/addon-docs @storybook/addon-themes storybook-addon-pseudo-states @vitejs/plugin-vue
// .storybook/main.ts
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { mergeConfig } from 'vite'
import type { StorybookConfig } from '@storybook/vue3-vite'
import uiPlugin from '@achareh/ui/vite'
import { ui } from '../app/ui'
const packageDir = dirname(fileURLToPath(import.meta.resolve('@achareh/ui/package.json')))
const componentsDir = join(packageDir, 'dist/runtime/components')
const config: StorybookConfig = {
// Published stories are compiled to `.js`; `.ts` only exists in a linked workspace.
stories: [`${componentsDir}/**/*.stories.@(js|ts)`],
addons: [
'@storybook/addon-docs',
'@storybook/addon-themes',
'storybook-addon-pseudo-states'
],
framework: {
name: '@storybook/vue3-vite',
options: {
docgen: {
plugin: 'vue-component-meta',
tsconfig: 'tsconfig.json'
}
}
},
async viteFinal(viteConfig) {
return mergeConfig(viteConfig, {
plugins: [vue(), ...uiPlugin({ ui, dts: false })]
})
}
}
export default config
اگر در nuxt.config برای ماژول theme یا prefix گذاشتهاید، همان را هم به uiPlugin({ prefix, theme, ui }) بدهید.
preview باید CSS اپ را بیاورد. آن فایل همانجا @import "@achareh/ui" و @theme رنگ و @font-face فونت را دارد.
// .storybook/preview.ts
import type { Preview } from '@storybook/vue3-vite'
import { withThemeByClassName } from '@storybook/addon-themes'
import App from '@achareh/ui/components/App.vue'
import '../app/assets/css/main.css'
import './preview.css'
const preview: Preview = {
parameters: {
backgrounds: { disable: true }
},
decorators: [
withThemeByClassName({
themes: { light: 'light', dark: 'dark' },
defaultTheme: 'light'
}),
() => ({
components: { App },
template: `
<App dir="rtl" class="min-h-0">
<div dir="rtl" :style="{ padding: '1rem', direction: 'rtl', textAlign: 'right' }">
<story />
</div>
</App>
`
})
]
}
export default preview
/* .storybook/preview.css
CSS اپ `:root` و `body` را راستبهچپ میکند. اینجا برگردانده میشود
تا خود استوریبوک نچرخد. فقط قاب `App` راستبهچپ میماند. */
:root,
body {
direction: ltr;
text-align: left;
}
{
"scripts": {
"storybook": "storybook dev -p 6006 --host 127.0.0.1"
}
}
سوییچ تم در نوار Storybook کلاس dark را روی html میگذارد. استوری جدید لازم نیست. استوریها variant را در args صریح نمیفرستند تا defaultVariants شما برنده شود.
توسعهٔ کتابخانه
این بخش برای کسی است که خود مخزن @achareh/ui را عوض میکند. مصرفکننده به نصب و کانفیگ بالا بسنده میکند.
نقشهٔ کار و ترتیب تغییر در AGENTS.md است. قرارداد هر پوشه در README.md همان پوشه است. اگر رفتار Nuxt، Vue، Vite، Vitest یا Tailwind با قرارداد همین ریپو فرق داشت، AGENTS.md و README پوشه برنده است. مرجع ابزارها در .agents/skills/ و ایندکس docs/llms/ است.
| کار | جزئیات |
|---|---|
| لایهها و دو آداپتر | src/README.md |
| فایل تم | src/theme/README.md |
| کامپوننت Vue | src/runtime/components/README.md |
| آپشن ماژول و مقیاس توکن | src/utils/README.md |
| استوریبوک خود ریپو | .storybook/README.md |
| نمونهٔ Nuxt و Vue | playgrounds/README.md |
| بهروزرسانی توکن از JSON فیگما | scripts/README.md و figma/configs/README.md |
| تست | test/README.md |
بعد از clone، یا بعد از عوض کردن src/theme، یک بار pnpm dev:prepare بزنید. بعد pnpm dev پلیگراند Nuxt را بالا میآورد و pnpm dev:vue پلیگراند Vue را. pnpm storybook پالت پیشفرض فیگما را در خود ریپو نشان میدهد و با استوریبوک اپ مصرفکننده یکی نیست. بقیهٔ دستورها در AGENTS.md است.
کامپوننت تازه را با کلید یکسان در تم، Vue، تایپ و هر دو پلیگراند اضافه کنید. چکلیست در AGENTS.md است. کلاس رنگی را رشتهٔ کامل داخل تم بگذارید (bg-primary). Tailwind رشتهٔ ساختهشده با قالب را داخل فایل Vue نمیبیند.
خروجی فیگما را با pnpm tokens:* در ثابتهای کتابخانه مینویسند. اورراید مصرفکننده در CSS اپ است و این دستورها را لازم ندارد. فرم، UTheme، i18n، آیکون، سایت docs و CLI عمداً در این بسته نیستند.
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.1 | ^3.5 | MIT | |
| 0.2.0 | ^3.5 | MIT |