@nons-dev/uikit 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/config/index.ts +76 -0
- package/core/config/parser.ts +112 -0
- package/core/config/types.ts +41 -0
- package/core/config/validator.ts +68 -0
- package/dist/index.css +1 -1
- package/dist/index.js +1214 -1155
- package/dist/src/index.d.ts +2 -2
- package/generated/components.json +54 -54
- package/locales/en.json +326 -0
- package/locales/fa.json +326 -0
- package/package.json +11 -6
- package/readme.md +67 -55
- package/scripts/cli.mjs +77 -139
- package/scripts/vite-plugin-nons-config.ts +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nons-dev/uikit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"license": "BUSL-1.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
|
+
"core/config",
|
|
22
23
|
"generated/components.json",
|
|
24
|
+
"locales",
|
|
23
25
|
"scripts/cli.mjs",
|
|
26
|
+
"scripts/vite-plugin-nons-config.ts",
|
|
24
27
|
"readme.md"
|
|
25
28
|
],
|
|
26
29
|
"exports": {
|
|
@@ -32,13 +35,17 @@
|
|
|
32
35
|
"./style.css": {
|
|
33
36
|
"import": "./dist/index.css",
|
|
34
37
|
"default": "./dist/index.css"
|
|
38
|
+
},
|
|
39
|
+
"./plugin": {
|
|
40
|
+
"types": "./scripts/vite-plugin-nons-config.ts",
|
|
41
|
+
"import": "./scripts/vite-plugin-nons-config.ts",
|
|
42
|
+
"default": "./scripts/vite-plugin-nons-config.ts"
|
|
35
43
|
}
|
|
36
44
|
},
|
|
37
45
|
"scripts": {
|
|
38
46
|
"dev": "vite",
|
|
39
|
-
"build": "
|
|
40
|
-
"preview": "vite preview"
|
|
41
|
-
"generate": "tsx scripts/generate.ts"
|
|
47
|
+
"build": "vue-tsc -b && vite build && node scripts/copy-fonts.mjs",
|
|
48
|
+
"preview": "vite preview"
|
|
42
49
|
},
|
|
43
50
|
"peerDependencies": {
|
|
44
51
|
"vue": "^3.5.0"
|
|
@@ -48,10 +55,8 @@
|
|
|
48
55
|
"js-yaml": "^5.1.0"
|
|
49
56
|
},
|
|
50
57
|
"devDependencies": {
|
|
51
|
-
"@types/js-yaml": "^4.0.9",
|
|
52
58
|
"@types/node": "^26.0.1",
|
|
53
59
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
54
|
-
"tsx": "^4.22.4",
|
|
55
60
|
"typescript": "~5.7.3",
|
|
56
61
|
"vite": "^6.3.3",
|
|
57
62
|
"vite-plugin-dts": "^5.0.3",
|
package/readme.md
CHANGED
|
@@ -2,50 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
Schema-driven UI component library for the Nons platform — Vue 3, TypeScript, token-based design system.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
core/ Schema types, component registry, schema renderer, runtime context
|
|
10
|
-
ui/ Vue 3 components (primitives, composites, layouts, sections)
|
|
11
|
-
generated/ Auto-generated files from nons.config.yaml (tokens.css, theme.css, config.ts, locales/)
|
|
12
|
-
design-system/ CSS reset, global styles (imports generated/ tokens)
|
|
13
|
-
playground/ Demo/explorer app for component development and testing
|
|
14
|
-
plugins/ i18n, direction, permissions
|
|
15
|
-
adapters/ Data provider layer (mock API, storage abstraction)
|
|
16
|
-
locales/ Persian (fa) and English (en) translation YAML files
|
|
17
|
-
scripts/ CLI tools (generate, init, component discovery)
|
|
7
|
+
```bash
|
|
8
|
+
npm install @nons-dev/uikit
|
|
18
9
|
```
|
|
19
10
|
|
|
20
11
|
## Quick Start
|
|
21
12
|
|
|
22
13
|
```bash
|
|
23
|
-
#
|
|
24
|
-
npm install @nons-dev/uikit
|
|
25
|
-
|
|
26
|
-
# Generate config file in your project root
|
|
14
|
+
# Generate config in your project root
|
|
27
15
|
npx @nons-dev/uikit init
|
|
28
16
|
|
|
29
|
-
# Edit nons.config.yaml
|
|
17
|
+
# Edit nons.config.yaml
|
|
18
|
+
```
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
import { bootstrapRegistry
|
|
20
|
+
```ts
|
|
21
|
+
import { bootstrapRegistry } from '@nons-dev/uikit'
|
|
33
22
|
import '@nons-dev/uikit/style.css'
|
|
34
23
|
```
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
For token/theme/font customization, use the Nons Vite plugin in your project:
|
|
37
26
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
```ts
|
|
28
|
+
// vite.config.ts
|
|
29
|
+
import { defineConfig } from 'vite'
|
|
30
|
+
import vue from '@vitejs/plugin-vue'
|
|
31
|
+
import { nonsConfigPlugin } from '@nons-dev/uikit/plugin'
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
plugins: [vue(), nonsConfigPlugin()],
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
nons.config.yaml Single source of truth for theme, tokens, locale, direction, font
|
|
42
|
+
core/ Schema types, component registry, schema renderer, runtime context
|
|
43
|
+
ui/ Vue 3 components (primitives, composites, layouts, sections)
|
|
44
|
+
design-system/ CSS reset, global styles, token defaults (shipped in dist/)
|
|
45
|
+
plugins/ i18n, direction, permissions
|
|
46
|
+
adapters/ Data provider layer (mock API, storage abstraction)
|
|
47
|
+
locales/ Persian (fa) and English (en) translation JSON files
|
|
48
|
+
scripts/ CLI tool (nons) and Vite plugin
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
## Config System
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
`nons.config.yaml` at your project root is the **single source of truth**:
|
|
49
54
|
|
|
50
55
|
```yaml
|
|
51
56
|
theme:
|
|
@@ -72,13 +77,11 @@ tokens:
|
|
|
72
77
|
light:
|
|
73
78
|
--color-primary: '#9FE870'
|
|
74
79
|
--color-danger: '#F31260'
|
|
75
|
-
# ... override any CSS custom property
|
|
76
80
|
dark:
|
|
77
81
|
--color-primary: '#9FE870'
|
|
78
82
|
--color-danger: '#f31260'
|
|
79
83
|
spacing:
|
|
80
84
|
--space-4: 16px
|
|
81
|
-
# ... typography, radius, shadows, z-index, opacity, button, alert
|
|
82
85
|
|
|
83
86
|
theme-mappings:
|
|
84
87
|
light:
|
|
@@ -89,61 +92,70 @@ theme-mappings:
|
|
|
89
92
|
--text-primary: var(--color-text-default)
|
|
90
93
|
```
|
|
91
94
|
|
|
92
|
-
Run `npx @nons-dev/uikit generate` to regenerate CSS variables from your config.
|
|
93
|
-
|
|
94
95
|
## CLI Commands
|
|
95
96
|
|
|
96
97
|
```bash
|
|
97
|
-
npx @nons-dev/uikit init # Create nons.config.yaml in project root
|
|
98
|
-
npx @nons-dev/uikit
|
|
98
|
+
npx @nons-dev/uikit init # Create nons.config.yaml + locales/ in project root
|
|
99
|
+
npx @nons-dev/uikit validate # Validate nons.config.yaml structure
|
|
99
100
|
npx @nons-dev/uikit list # List all components
|
|
100
101
|
npx @nons-dev/uikit show <name> # Show component details (props, events, slots)
|
|
101
102
|
npx @nons-dev/uikit schema <name> # Show schema JSON for a component
|
|
102
103
|
npx @nons-dev/uikit search <query> # Search components by name or description
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
## Customizing
|
|
106
|
+
## Customizing Tokens & Theme
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
npx @nons-dev/uikit generate
|
|
111
|
-
```
|
|
108
|
+
The default design tokens are built into `dist/index.css`. To override them:
|
|
112
109
|
|
|
113
|
-
|
|
110
|
+
1. Edit `nons.config.yaml` with your custom token values
|
|
111
|
+
2. Use the **Nons Vite plugin** to generate custom CSS at build time:
|
|
114
112
|
|
|
115
113
|
```ts
|
|
116
|
-
|
|
117
|
-
import '
|
|
118
|
-
import '
|
|
114
|
+
// vite.config.ts
|
|
115
|
+
import { defineConfig } from 'vite'
|
|
116
|
+
import vue from '@vitejs/plugin-vue'
|
|
117
|
+
import { nonsConfigPlugin } from '@nons-dev/uikit/plugin'
|
|
118
|
+
|
|
119
|
+
export default defineConfig({
|
|
120
|
+
plugins: [vue(), nonsConfigPlugin()],
|
|
121
|
+
})
|
|
119
122
|
```
|
|
120
123
|
|
|
121
|
-
The
|
|
124
|
+
The plugin creates virtual modules (`virtual:nons-config` for JS access, `virtual:nons-config:style` for CSS overrides). Custom token values are injected with the same specificity as defaults.
|
|
122
125
|
|
|
123
|
-
All visual values in the uikit reference CSS custom properties (`var(--color-primary)`), so changing a token
|
|
126
|
+
All visual values in the uikit reference CSS custom properties (`var(--color-primary)`), so changing a token propagates to every component.
|
|
124
127
|
|
|
125
|
-
##
|
|
128
|
+
## Development (UI Kit only)
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
npm run dev # Start Vite dev server (playground app)
|
|
133
|
+
npm run build # Type-check + library build + copy fonts
|
|
134
|
+
npm run preview # Preview production playground build
|
|
135
|
+
```
|
|
131
136
|
|
|
132
137
|
## Key Conventions
|
|
133
138
|
|
|
134
|
-
- **Zero-hardcoding** — all visual values via CSS custom properties
|
|
139
|
+
- **Zero-hardcoding** — all visual values via CSS custom properties
|
|
135
140
|
- **Config-driven** — theme, tokens, direction, locale all from `nons.config.yaml`
|
|
136
141
|
- **RTL-first** — `<html dir="rtl">`, logical properties (`inset-inline-start`, `margin-inline`)
|
|
137
|
-
- **Schema-driven** — every component has a registry key, contract,
|
|
142
|
+
- **Schema-driven** — every component has a registry key, contract, and metadata (53 components)
|
|
138
143
|
- **modelValue** — all form inputs use `modelValue` prop + `update:modelValue` emit
|
|
139
144
|
- **No external margins** — component spacing via parent layout `gap`
|
|
140
|
-
- **Flat UI** — no elevation shadows; separation via themed borders
|
|
145
|
+
- **Flat UI** — no elevation shadows; separation via themed borders
|
|
141
146
|
- **TypeScript strict** — `strict: true`, `noUncheckedIndexedAccess`, `noUnusedLocals`
|
|
142
147
|
|
|
143
148
|
## Dependencies
|
|
144
149
|
|
|
145
|
-
- Vue 3.5 (Composition API
|
|
150
|
+
- Vue 3.5 (Composition API)
|
|
146
151
|
- `@lucide/vue` icons
|
|
147
152
|
- Tabler Icons (CDN)
|
|
148
153
|
- Plain CSS only (no SCSS/Sass)
|
|
149
|
-
- Font files bundled in `dist/font/` (IRANSansX, Satoshi)
|
|
154
|
+
- Font files bundled in `dist/font/` (IRANSansX, Satoshi)
|
|
155
|
+
|
|
156
|
+
## Documentation
|
|
157
|
+
|
|
158
|
+
| Resource | Description |
|
|
159
|
+
|----------|-------------|
|
|
160
|
+
| [Style Guide](docs/STYLE-GUIDE.md) | Token-driven design, layout isolation, flat UI, component conventions |
|
|
161
|
+
| [Component Contracts](docs/component-contracts/) | Single source of truth for all component APIs, schemas, and states |
|
package/scripts/cli.mjs
CHANGED
|
@@ -40,17 +40,6 @@ font:
|
|
|
40
40
|
family: IRANSansX
|
|
41
41
|
en:
|
|
42
42
|
family: Satoshi
|
|
43
|
-
# Faces loaded automatically from @nons-dev/uikit/dist/font/
|
|
44
|
-
# Uncomment and customize src to use your own font files:
|
|
45
|
-
# faces:
|
|
46
|
-
# - family: Satoshi
|
|
47
|
-
# weight: 400
|
|
48
|
-
# src: fonts/Satoshi-Regular.woff2
|
|
49
|
-
# format: woff2
|
|
50
|
-
# - family: IRANSansX
|
|
51
|
-
# weight: 400
|
|
52
|
-
# src: fonts/IRANSansX-Regular.woff
|
|
53
|
-
# format: woff
|
|
54
43
|
|
|
55
44
|
tokens:
|
|
56
45
|
colors:
|
|
@@ -204,6 +193,20 @@ theme-mappings:
|
|
|
204
193
|
--overlay-bg: rgba(0, 0, 0, 0.6)
|
|
205
194
|
`
|
|
206
195
|
|
|
196
|
+
const LOCALE_FA = JSON.stringify({
|
|
197
|
+
app: { name: 'نونز', footer: 'پنل مدیریت' },
|
|
198
|
+
nav: { dashboard: 'داشبورد', users: 'کاربران', stores: 'فروشگاهها', products: 'محصولات', orders: 'سفارشات', chat: 'گفتگو', reports: 'گزارشات', stats: 'آمار', components: 'کامپوننتها' },
|
|
199
|
+
table: { empty: 'هیچ دادهای یافت نشد.' },
|
|
200
|
+
loading: { text: 'در حال بارگذاری...' },
|
|
201
|
+
}, null, 2)
|
|
202
|
+
|
|
203
|
+
const LOCALE_EN = JSON.stringify({
|
|
204
|
+
app: { name: 'Nons', footer: 'Admin Panel' },
|
|
205
|
+
nav: { dashboard: 'Dashboard', users: 'Users', stores: 'Stores', products: 'Products', orders: 'Orders', chat: 'Chat', reports: 'Reports', stats: 'Stats', components: 'Components' },
|
|
206
|
+
table: { empty: 'No data found.' },
|
|
207
|
+
loading: { text: 'Loading...' },
|
|
208
|
+
}, null, 2)
|
|
209
|
+
|
|
207
210
|
// ── Data ──
|
|
208
211
|
|
|
209
212
|
function loadComponents() {
|
|
@@ -231,147 +234,82 @@ function searchComponents(data, query) {
|
|
|
231
234
|
)
|
|
232
235
|
}
|
|
233
236
|
|
|
234
|
-
// ──
|
|
235
|
-
|
|
236
|
-
function writeTokenBlock(tokens, indent) {
|
|
237
|
-
return Object.entries(tokens || {}).map(([k, v]) => `${indent}${k}: ${v};`).join('\n')
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function generateFontCSS(config) {
|
|
241
|
-
const faces = config.font?.faces
|
|
242
|
-
if (!faces || !faces.length) return ''
|
|
243
|
-
const lines = []
|
|
244
|
-
for (const face of faces) {
|
|
245
|
-
lines.push('@font-face {')
|
|
246
|
-
lines.push(` font-family: '${face.family}';`)
|
|
247
|
-
lines.push(` src: url('./${face.src}') format('${face.format}');`)
|
|
248
|
-
lines.push(` font-weight: ${face.weight};`)
|
|
249
|
-
lines.push(' font-style: normal;')
|
|
250
|
-
lines.push(' font-display: swap;')
|
|
251
|
-
lines.push('}')
|
|
252
|
-
lines.push('')
|
|
253
|
-
}
|
|
254
|
-
return lines.join('\n')
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function generateTokensCSS(config) {
|
|
258
|
-
const lines = []
|
|
259
|
-
const fonts = generateFontCSS(config)
|
|
260
|
-
if (fonts) lines.push(fonts)
|
|
261
|
-
|
|
262
|
-
const t = config.tokens || {}
|
|
263
|
-
|
|
264
|
-
// Light & dark color schemes
|
|
265
|
-
const hasLight = t.colors?.light && Object.keys(t.colors.light).length
|
|
266
|
-
const hasDark = t.colors?.dark && Object.keys(t.colors.dark).length
|
|
237
|
+
// ── Commands ──
|
|
267
238
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
lines.push('')
|
|
239
|
+
function cmdInit() {
|
|
240
|
+
const configDest = path.join(CWD, 'nons.config.yaml')
|
|
241
|
+
if (fs.existsSync(configDest)) {
|
|
242
|
+
console.log(`${YELLOW}⚠${RESET} nons.config.yaml already exists — skipping.`)
|
|
243
|
+
return
|
|
274
244
|
}
|
|
245
|
+
fs.writeFileSync(configDest, DEFAULT_CONFIG, 'utf-8')
|
|
246
|
+
console.log(`${GREEN}✓${RESET} nons.config.yaml created`)
|
|
275
247
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
lines.push('}')
|
|
281
|
-
lines.push('')
|
|
248
|
+
const localesDir = path.join(CWD, 'locales')
|
|
249
|
+
if (!fs.existsSync(localesDir)) {
|
|
250
|
+
fs.mkdirSync(localesDir, { recursive: true })
|
|
251
|
+
console.log(`${GREEN}✓${RESET} locales/ directory created`)
|
|
282
252
|
}
|
|
283
253
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
for (const [section, label] of Object.entries(flatSections)) {
|
|
290
|
-
const tokens = t[section]
|
|
291
|
-
if (!tokens || !Object.keys(tokens).length) continue
|
|
292
|
-
lines.push(`/* ${label} */`)
|
|
293
|
-
lines.push(':root {')
|
|
294
|
-
lines.push(writeTokenBlock(tokens, ' '))
|
|
295
|
-
lines.push('}')
|
|
296
|
-
lines.push('')
|
|
254
|
+
const faPath = path.join(localesDir, 'fa.json')
|
|
255
|
+
if (!fs.existsSync(faPath)) {
|
|
256
|
+
fs.writeFileSync(faPath, LOCALE_FA, 'utf-8')
|
|
257
|
+
console.log(`${GREEN}✓${RESET} locales/fa.json created`)
|
|
297
258
|
}
|
|
298
259
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
lines.push(':root {')
|
|
304
|
-
lines.push(' color-scheme: light;')
|
|
305
|
-
lines.push(writeTokenBlock(t.shadows.light, ' '))
|
|
306
|
-
lines.push('}')
|
|
307
|
-
lines.push('')
|
|
308
|
-
}
|
|
309
|
-
if (hasShadowDark) {
|
|
310
|
-
lines.push('[data-theme="dark"] {')
|
|
311
|
-
lines.push(' color-scheme: dark;')
|
|
312
|
-
lines.push(writeTokenBlock(t.shadows.dark, ' '))
|
|
313
|
-
lines.push('}')
|
|
314
|
-
lines.push('')
|
|
260
|
+
const enPath = path.join(localesDir, 'en.json')
|
|
261
|
+
if (!fs.existsSync(enPath)) {
|
|
262
|
+
fs.writeFileSync(enPath, LOCALE_EN, 'utf-8')
|
|
263
|
+
console.log(`${GREEN}✓${RESET} locales/en.json created`)
|
|
315
264
|
}
|
|
316
265
|
|
|
317
|
-
|
|
266
|
+
console.log(`\n${YELLOW}ℹ${RESET} Install a Vite plugin or adapter to load config automatically:`)
|
|
267
|
+
console.log(` npm install @nons-dev/vite-plugin\n`)
|
|
318
268
|
}
|
|
319
269
|
|
|
320
|
-
function
|
|
321
|
-
const m = config['theme-mappings']
|
|
322
|
-
if (!m) return ''
|
|
323
|
-
const lines = []
|
|
324
|
-
|
|
325
|
-
lines.push(':root {')
|
|
326
|
-
lines.push(' color-scheme: light;')
|
|
327
|
-
if (m.light) lines.push(writeTokenBlock(m.light, ' '))
|
|
328
|
-
lines.push('}')
|
|
329
|
-
|
|
330
|
-
lines.push('')
|
|
331
|
-
lines.push('[data-theme="dark"] {')
|
|
332
|
-
lines.push(' color-scheme: dark;')
|
|
333
|
-
if (m.dark) lines.push(writeTokenBlock(m.dark, ' '))
|
|
334
|
-
lines.push('}')
|
|
335
|
-
|
|
336
|
-
return lines.join('\n')
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// ── Commands ──
|
|
340
|
-
|
|
341
|
-
function cmdInit() {
|
|
342
|
-
const dest = path.join(CWD, 'nons.config.yaml')
|
|
343
|
-
if (fs.existsSync(dest)) {
|
|
344
|
-
console.log(`${YELLOW}⚠${RESET} nons.config.yaml already exists — skipping.`)
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
fs.writeFileSync(dest, DEFAULT_CONFIG, 'utf-8')
|
|
348
|
-
console.log(`${GREEN}✓${RESET} nons.config.yaml created in`, CWD)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function cmdGenerate() {
|
|
270
|
+
function cmdValidate() {
|
|
352
271
|
const configPath = path.join(CWD, 'nons.config.yaml')
|
|
353
272
|
if (!fs.existsSync(configPath)) {
|
|
354
273
|
console.error(`${RED}✗${RESET} nons.config.yaml not found. Run \`nons init\` first.`)
|
|
355
274
|
process.exit(1)
|
|
356
275
|
}
|
|
357
|
-
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
358
|
-
const config = yaml.load(raw)
|
|
359
|
-
const generatedDir = path.join(CWD, 'generated')
|
|
360
|
-
if (!fs.existsSync(generatedDir)) fs.mkdirSync(generatedDir, { recursive: true })
|
|
361
|
-
|
|
362
|
-
// tokens.css
|
|
363
|
-
const tokensCSS = generateTokensCSS(config)
|
|
364
|
-
fs.writeFileSync(path.join(generatedDir, 'tokens.css'), tokensCSS, 'utf-8')
|
|
365
|
-
console.log(`${GREEN}✓${RESET} generated/tokens.css`)
|
|
366
|
-
|
|
367
|
-
// theme.css
|
|
368
|
-
const themeCSS = generateThemeCSS(config)
|
|
369
|
-
if (themeCSS) {
|
|
370
|
-
fs.writeFileSync(path.join(generatedDir, 'theme.css'), themeCSS, 'utf-8')
|
|
371
|
-
console.log(`${GREEN}✓${RESET} generated/theme.css`)
|
|
372
|
-
}
|
|
373
276
|
|
|
374
|
-
|
|
277
|
+
try {
|
|
278
|
+
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
279
|
+
const config = yaml.load(raw)
|
|
280
|
+
|
|
281
|
+
const errors = []
|
|
282
|
+
const warnings = []
|
|
283
|
+
|
|
284
|
+
if (!config || typeof config !== 'object') {
|
|
285
|
+
errors.push('Config must be a non-null object')
|
|
286
|
+
} else {
|
|
287
|
+
const c = config
|
|
288
|
+
if (!c.theme || !c.theme.preset) errors.push('Missing theme.preset')
|
|
289
|
+
if (!c.brand || !c.brand.name) errors.push('Missing brand.name')
|
|
290
|
+
if (!c.direction || (c.direction.default !== 'rtl' && c.direction.default !== 'ltr')) errors.push('direction.default must be "rtl" or "ltr"')
|
|
291
|
+
if (!c.locale || !c.locale.default) errors.push('Missing locale.default')
|
|
292
|
+
if (!Array.isArray(c.locale?.supported) || c.locale.supported.length === 0) errors.push('locale.supported must be a non-empty array')
|
|
293
|
+
if (!c.tokens || typeof c.tokens !== 'object') warnings.push('No tokens section found — using defaults')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (errors.length === 0) {
|
|
297
|
+
console.log(`\n${GREEN}✓${RESET} Config is valid\n`)
|
|
298
|
+
} else {
|
|
299
|
+
console.log(`\n${RED}✗${RESET} Config has errors:\n`)
|
|
300
|
+
for (const e of errors) console.log(` ${RED}•${RESET} ${e}`)
|
|
301
|
+
console.log('')
|
|
302
|
+
}
|
|
303
|
+
if (warnings.length) {
|
|
304
|
+
for (const w of warnings) console.log(` ${YELLOW}•${RESET} ${w}`)
|
|
305
|
+
console.log('')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (errors.length) process.exit(1)
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(`\n${RED}✗${RESET} Invalid YAML: ${err.message}\n`)
|
|
311
|
+
process.exit(1)
|
|
312
|
+
}
|
|
375
313
|
}
|
|
376
314
|
|
|
377
315
|
function cmdList() {
|
|
@@ -493,8 +431,8 @@ function cmdSearch(query) {
|
|
|
493
431
|
function printHelp() {
|
|
494
432
|
console.log(`\n${BOLD}${CYAN}Nons UIKit CLI${RESET}\n`)
|
|
495
433
|
console.log(`${BOLD}Commands:${RESET}\n`)
|
|
496
|
-
console.log(` ${GREEN}init${RESET} Create nons.config.yaml in current directory`)
|
|
497
|
-
console.log(` ${GREEN}
|
|
434
|
+
console.log(` ${GREEN}init${RESET} Create nons.config.yaml + locales/ in current directory`)
|
|
435
|
+
console.log(` ${GREEN}validate${RESET} Validate nons.config.yaml`)
|
|
498
436
|
console.log(` ${GREEN}list${RESET} List all components`)
|
|
499
437
|
console.log(` ${GREEN}show${RESET} ${DIM}<name>${RESET} Show full detail for a component`)
|
|
500
438
|
console.log(` ${GREEN}schema${RESET} ${DIM}<name>${RESET} Show schema JSON for a component`)
|
|
@@ -502,7 +440,7 @@ function printHelp() {
|
|
|
502
440
|
console.log(` ${GREEN}help${RESET} Show this help\n`)
|
|
503
441
|
console.log(`${BOLD}Usage:${RESET}\n`)
|
|
504
442
|
console.log(` ${DIM}npx @nons-dev/uikit init${RESET}`)
|
|
505
|
-
console.log(` ${DIM}npx @nons-dev/uikit
|
|
443
|
+
console.log(` ${DIM}npx @nons-dev/uikit validate${RESET}`)
|
|
506
444
|
console.log(` ${DIM}npx @nons-dev/uikit list${RESET}`)
|
|
507
445
|
console.log(` ${DIM}npx @nons-dev/uikit show Button${RESET}`)
|
|
508
446
|
console.log(` ${DIM}npx @nons-dev/uikit schema Input${RESET}`)
|
|
@@ -516,7 +454,7 @@ const param = process.argv[3]
|
|
|
516
454
|
|
|
517
455
|
switch (command) {
|
|
518
456
|
case 'init': cmdInit(); break
|
|
519
|
-
case '
|
|
457
|
+
case 'validate': cmdValidate(); break
|
|
520
458
|
case 'list': cmdList(); break
|
|
521
459
|
case 'show': param ? cmdShow(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
|
|
522
460
|
case 'schema': param ? cmdSchema(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import * as yaml from 'js-yaml'
|
|
4
|
+
import type { Plugin, ResolvedConfig } from 'vite'
|
|
5
|
+
import type { RawConfig } from '../core/config/types'
|
|
6
|
+
import { generateFullCSS } from '../core/config/parser'
|
|
7
|
+
|
|
8
|
+
const CONFIG_VIRTUAL = 'virtual:nons-config'
|
|
9
|
+
const CONFIG_CSS_VIRTUAL = 'virtual:nons-config:style'
|
|
10
|
+
const RESOLVED_CONFIG_VIRTUAL = '\0' + CONFIG_VIRTUAL
|
|
11
|
+
const RESOLVED_CONFIG_CSS_VIRTUAL = '\0' + CONFIG_CSS_VIRTUAL
|
|
12
|
+
|
|
13
|
+
let loadedConfig: RawConfig | null = null
|
|
14
|
+
|
|
15
|
+
export function loadConfigFromYaml(filePath: string): RawConfig {
|
|
16
|
+
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
17
|
+
const config = yaml.load(raw) as RawConfig
|
|
18
|
+
loadedConfig = config
|
|
19
|
+
return config
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getLoadedConfig(): RawConfig | null {
|
|
23
|
+
return loadedConfig
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function nonsConfigPlugin(configPath?: string): Plugin {
|
|
27
|
+
const resolvedPath = configPath || path.resolve(process.cwd(), 'nons.config.yaml')
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name: 'nons-config',
|
|
31
|
+
|
|
32
|
+
configResolved(_config: ResolvedConfig) {
|
|
33
|
+
if (fs.existsSync(resolvedPath)) {
|
|
34
|
+
loadConfigFromYaml(resolvedPath)
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
resolveId(id: string) {
|
|
39
|
+
if (id === CONFIG_VIRTUAL) return RESOLVED_CONFIG_VIRTUAL
|
|
40
|
+
if (id === CONFIG_CSS_VIRTUAL) return RESOLVED_CONFIG_CSS_VIRTUAL
|
|
41
|
+
return null
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
load(id: string) {
|
|
45
|
+
if (id === RESOLVED_CONFIG_VIRTUAL) {
|
|
46
|
+
if (!loadedConfig) {
|
|
47
|
+
this.error('nons.config.yaml not found at ' + resolvedPath)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
const runtimeConfig = {
|
|
51
|
+
theme: loadedConfig.theme,
|
|
52
|
+
brand: loadedConfig.brand,
|
|
53
|
+
direction: loadedConfig.direction,
|
|
54
|
+
locale: loadedConfig.locale,
|
|
55
|
+
font: {
|
|
56
|
+
fa: loadedConfig.font?.fa,
|
|
57
|
+
en: loadedConfig.font?.en,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
return `export const nonsConfig = ${JSON.stringify(runtimeConfig, null, 2)}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (id === RESOLVED_CONFIG_CSS_VIRTUAL) {
|
|
64
|
+
if (!loadedConfig) {
|
|
65
|
+
this.error('nons.config.yaml not found at ' + resolvedPath)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
return generateFullCSS(loadedConfig)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|