@nons-dev/uikit 0.1.4 → 0.1.6

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.
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import * as yaml from 'js-yaml'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+ const COMPONENTS_JSON = path.join(__dirname, '..', 'generated', 'components.json')
10
+ const CWD = process.cwd()
11
+
12
+ const RESET = '\x1b[0m'
13
+ const BOLD = '\x1b[1m'
14
+ const DIM = '\x1b[2m'
15
+ const GREEN = '\x1b[32m'
16
+ const CYAN = '\x1b[36m'
17
+ const YELLOW = '\x1b[33m'
18
+ const MAGENTA = '\x1b[35m'
19
+ const GRAY = '\x1b[90m'
20
+ const RED = '\x1b[31m'
21
+ const BLUE = '\x1b[34m'
22
+
23
+ const DEFAULT_CONFIG = `theme:
24
+ preset: nons
25
+
26
+ brand:
27
+ name: Nons
28
+
29
+ direction:
30
+ default: rtl
31
+
32
+ locale:
33
+ default: fa
34
+ supported:
35
+ - fa
36
+ - en
37
+
38
+ font:
39
+ fa:
40
+ family: IRANSansX
41
+ en:
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
+
55
+ tokens:
56
+ colors:
57
+ light:
58
+ --color-text-default: '#37352F'
59
+ --color-text-grey: '#787774'
60
+ --color-ui-window: '#FFFFFF'
61
+ --color-ui-sidebar: '#F7F6F3'
62
+ --color-ui-hover: rgba(55, 53, 47, 0.08)
63
+ --color-primary: '#9FE870'
64
+ --color-primary-bg: rgba(159, 232, 112, 0.15)
65
+ --color-primary-dark: '#163300'
66
+ --color-primary-text: var(--color-primary-dark)
67
+ --color-primary-selected-bg: rgba(159, 232, 112, 0.08)
68
+ --color-danger: '#F31260'
69
+ --color-danger-bg: '#FEE7EF'
70
+ --color-success: '#17C964'
71
+ --color-success-bg: '#E8FAF0'
72
+ --color-warning: '#F5A524'
73
+ --color-warning-bg: '#FEFCE8'
74
+ --color-secondary: '#7828C8'
75
+ --color-secondary-bg: '#F2EAFA'
76
+ --color-info: '#006FEE'
77
+ --color-info-bg: '#E7F3F8'
78
+ --color-default: '#11181C'
79
+ --color-default-bg: '#F4F4F5'
80
+ --color-border: '#D5D5D2'
81
+ --color-text-secondary: var(--color-text-grey)
82
+ --color-white: '#FFFFFF'
83
+ dark:
84
+ --color-text-default: '#FFFFFF'
85
+ --color-text-grey: '#979A9B'
86
+ --color-ui-window: '#111111'
87
+ --color-ui-sidebar: '#000000'
88
+ --color-ui-hover: rgba(255, 255, 255, 0.08)
89
+ --color-primary: '#9FE870'
90
+ --color-primary-bg: rgba(159, 232, 112, 0.15)
91
+ --color-primary-dark: '#163300'
92
+ --color-primary-text: var(--color-primary-dark)
93
+ --color-primary-selected-bg: rgba(159, 232, 112, 0.08)
94
+ --color-danger: '#f31260'
95
+ --color-danger-bg: '#310413'
96
+ --color-success: '#17c964'
97
+ --color-success-bg: '#052814'
98
+ --color-warning: '#f5a524'
99
+ --color-warning-bg: '#312107'
100
+ --color-secondary: '#9353d3'
101
+ --color-secondary-bg: '#180828'
102
+ --color-info: '#006fee'
103
+ --color-info-bg: '#001731'
104
+ --color-default: '#006fee'
105
+ --color-default-bg: '#001731'
106
+ --color-border: '#1D1D1D'
107
+ --color-text-secondary: var(--color-text-grey)
108
+ --color-white: '#FFFFFF'
109
+
110
+ spacing:
111
+ --space-0-5: 2px
112
+ --space-1: 4px
113
+ --space-2: 8px
114
+ --space-2-5: 10px
115
+ --space-3: 12px
116
+ --space-4: 16px
117
+ --space-5: 24px
118
+ --space-6: 32px
119
+ --space-8: 48px
120
+ --space-10: 64px
121
+ --space-3-5: 14px
122
+ --space-4-5: 18px
123
+ --border-width: 1px
124
+ --border-width-lg: 2px
125
+
126
+ typography:
127
+ --font-family: "'IRANSansX', 'Satoshi', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif"
128
+ --font-family-mono: "'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace"
129
+ --font-size-xs: 12px
130
+ --font-size-sm: 12px
131
+ --font-size-md: 14px
132
+ --font-size-lg: 16px
133
+ --font-size-xl: 18px
134
+ --font-size-2xl: 24px
135
+ --font-weight-normal: 400
136
+ --font-weight-medium: 500
137
+ --font-weight-bold: 700
138
+ --line-height-sm: 18px
139
+ --line-height-md: 22px
140
+ --line-height-lg: 30px
141
+
142
+ radius:
143
+ --radius-sm: 4px
144
+ --radius-md: 8px
145
+ --radius-lg: 12px
146
+ --radius-xl: 16px
147
+ --radius-full: 9999px
148
+
149
+ shadows:
150
+ light:
151
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06)
152
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.08)
153
+ --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1)
154
+ dark:
155
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3)
156
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.35)
157
+ --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.4)
158
+
159
+ z-index:
160
+ --z-dropdown: 100
161
+ --z-popover: 90
162
+ --z-tooltip: 150
163
+ --z-modal: 1000
164
+ --z-panel: 200
165
+
166
+ opacity:
167
+ --opacity-disabled: 0.5
168
+ --opacity-secondary: 0.6
169
+ --opacity-placeholder: 0.6
170
+ --opacity-hint: 0.8
171
+ --opacity-hover: 0.8
172
+
173
+ button:
174
+ --btn-border-radius: 8px
175
+ --btn-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05)
176
+ --btn-padding: 10px 16px
177
+ --btn-font-size: 14px
178
+ --btn-font-weight: 500
179
+ --btn-font-family: "'IRANSansX', sans-serif"
180
+
181
+ alert:
182
+ --alert-border-radius: 16px
183
+ --alert-padding: 12px
184
+ --alert-gap: 16px
185
+
186
+ theme-mappings:
187
+ light:
188
+ --bg-primary: var(--color-ui-window)
189
+ --bg-sidebar: var(--color-ui-sidebar)
190
+ --bg-hover: var(--color-ui-hover)
191
+ --bg-card: var(--color-ui-window)
192
+ --text-primary: var(--color-text-default)
193
+ --text-secondary: var(--color-text-secondary)
194
+ --border-primary: var(--color-border)
195
+ --overlay-bg: rgba(0, 0, 0, 0.4)
196
+ dark:
197
+ --bg-primary: var(--color-ui-window)
198
+ --bg-sidebar: var(--color-ui-sidebar)
199
+ --bg-hover: var(--color-ui-hover)
200
+ --bg-card: '#161816'
201
+ --text-primary: var(--color-text-default)
202
+ --text-secondary: var(--color-text-grey)
203
+ --border-primary: var(--color-border)
204
+ --overlay-bg: rgba(0, 0, 0, 0.6)
205
+ `
206
+
207
+ // ── Data ──
208
+
209
+ function loadComponents() {
210
+ if (!fs.existsSync(COMPONENTS_JSON)) {
211
+ console.error(`${RED}✗${RESET} Component metadata not found.`)
212
+ console.error(` Expected: ${COMPONENTS_JSON}`)
213
+ process.exit(1)
214
+ }
215
+ return JSON.parse(fs.readFileSync(COMPONENTS_JSON, 'utf-8'))
216
+ }
217
+
218
+ function findComponent(data, query) {
219
+ const lower = query.toLowerCase()
220
+ return data.components.find(c =>
221
+ c.name.toLowerCase() === lower || c.registryKey === lower
222
+ )
223
+ }
224
+
225
+ function searchComponents(data, query) {
226
+ const lower = query.toLowerCase()
227
+ return data.components.filter(c =>
228
+ c.name.toLowerCase().includes(lower) ||
229
+ c.registryKey.includes(lower) ||
230
+ (c.description || '').toLowerCase().includes(lower)
231
+ )
232
+ }
233
+
234
+ // ── Generate helpers ──
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
267
+
268
+ if (hasLight) {
269
+ lines.push(':root {')
270
+ lines.push(' color-scheme: light;')
271
+ lines.push(writeTokenBlock(t.colors.light, ' '))
272
+ lines.push('}')
273
+ lines.push('')
274
+ }
275
+
276
+ if (hasDark) {
277
+ lines.push('[data-theme="dark"] {')
278
+ lines.push(' color-scheme: dark;')
279
+ lines.push(writeTokenBlock(t.colors.dark, ' '))
280
+ lines.push('}')
281
+ lines.push('')
282
+ }
283
+
284
+ // Flat token sections (no light/dark split)
285
+ const flatSections = {
286
+ spacing: 'Spacing', typography: 'Typography', radius: 'Border Radius',
287
+ 'z-index': 'Z-Index', opacity: 'Opacity', button: 'Button', alert: 'Alert',
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('')
297
+ }
298
+
299
+ // Shadows (light + dark)
300
+ const hasShadowLight = t.shadows?.light && Object.keys(t.shadows.light).length
301
+ const hasShadowDark = t.shadows?.dark && Object.keys(t.shadows.dark).length
302
+ if (hasShadowLight) {
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('')
315
+ }
316
+
317
+ return lines.join('\n')
318
+ }
319
+
320
+ function generateThemeCSS(config) {
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() {
352
+ const configPath = path.join(CWD, 'nons.config.yaml')
353
+ if (!fs.existsSync(configPath)) {
354
+ console.error(`${RED}✗${RESET} nons.config.yaml not found. Run \`nons init\` first.`)
355
+ process.exit(1)
356
+ }
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
+
374
+ console.log(`${YELLOW}ℹ${RESET} Import in your app:\n import './generated/tokens.css'\n import './generated/theme.css'`)
375
+ }
376
+
377
+ function cmdList() {
378
+ const data = loadComponents()
379
+ const grouped = {}
380
+ for (const c of data.components) {
381
+ if (!grouped[c.category]) grouped[c.category] = []
382
+ grouped[c.category].push(c)
383
+ }
384
+
385
+ console.log(`\n${BOLD}${CYAN}Components (${data.components.length})${RESET}\n`)
386
+ const order = ['primitive', 'layout', 'component', 'section']
387
+ const labels = { primitive: 'Primitive', layout: 'Layout', component: 'Component', section: 'Section' }
388
+ const colors = { primitive: GREEN, layout: BLUE, component: CYAN, section: MAGENTA }
389
+
390
+ for (const cat of order) {
391
+ const items = grouped[cat]
392
+ if (!items || !items.length) continue
393
+ const color = colors[cat]
394
+ console.log(` ${color}${BOLD}── ${labels[cat]} (${items.length})${RESET}\n`)
395
+ for (const c of items) {
396
+ const desc = c.description ? ` ${GRAY}${c.description.substring(0, 50)}${c.description.length > 50 ? '…' : ''}${RESET}` : ''
397
+ console.log(` ${BOLD}${c.name}${RESET} ${DIM}[${c.registryKey}]${RESET}${desc}`)
398
+ }
399
+ console.log('')
400
+ }
401
+ }
402
+
403
+ function cmdShow(name) {
404
+ const data = loadComponents()
405
+ const comp = findComponent(data, name)
406
+ if (!comp) {
407
+ console.log(`\n ${RED}✗ Component "${name}" not found.${RESET}\n`)
408
+ const similar = searchComponents(data, name).filter(c => c.name !== name)
409
+ if (similar.length) {
410
+ console.log(` ${YELLOW}Did you mean:${RESET}`)
411
+ for (const s of similar) console.log(` • ${s.name} ${DIM}[${s.registryKey}]${RESET}`)
412
+ console.log('')
413
+ }
414
+ process.exit(1)
415
+ }
416
+
417
+ const color = { primitive: GREEN, layout: BLUE, component: CYAN, section: MAGENTA }[comp.category] || CYAN
418
+ const label = { primitive: 'Primitive', layout: 'Layout', component: 'Component', section: 'Section' }[comp.category] || comp.category
419
+
420
+ console.log(`\n${BOLD}${CYAN}${comp.name}${RESET}\n`)
421
+ console.log(` ${DIM}Category:${RESET} ${color}${BOLD}${label}${RESET}`)
422
+ console.log(` ${DIM}Registry Key:${RESET} ${comp.registryKey}`)
423
+ console.log(` ${DIM}File:${RESET} ${comp.filePath}`)
424
+ if (comp.description) console.log(`\n ${comp.description}\n`)
425
+
426
+ if (comp.props.length) {
427
+ console.log(` ${BOLD}Props:${RESET}\n`)
428
+ for (const p of comp.props) {
429
+ const req = p.required ? `${YELLOW}required${RESET}` : `${DIM}optional${RESET}`
430
+ console.log(` ${BOLD}${p.name}${RESET} ${req}`)
431
+ console.log(` ${DIM}type:${RESET} ${CYAN}${p.type}${RESET}`)
432
+ if (p.allowedValues?.length) console.log(` ${DIM}allowed:${RESET} ${p.allowedValues.map(v => `${GREEN}'${v}'${RESET}`).join(' | ')}`)
433
+ if (p.default !== undefined) console.log(` ${DIM}default:${RESET} ${MAGENTA}${p.default}${RESET}`)
434
+ if (p.description) console.log(` ${DIM}desc:${RESET} ${p.description}`)
435
+ console.log('')
436
+ }
437
+ }
438
+
439
+ if (comp.events.length) {
440
+ console.log(` ${BOLD}Events:${RESET}\n`)
441
+ for (const e of comp.events) {
442
+ const payload = e.payload ? ` ${DIM}→ ${e.payload}${RESET}` : ''
443
+ console.log(` ${YELLOW}@${e.name}${RESET}${payload}`)
444
+ }
445
+ console.log('')
446
+ }
447
+
448
+ if (comp.slots.length) {
449
+ console.log(` ${BOLD}Slots:${RESET}\n`)
450
+ for (const s of comp.slots) console.log(` ${MAGENTA}#${s.name}${RESET}`)
451
+ console.log('')
452
+ }
453
+
454
+ if (comp.schemaExample) {
455
+ console.log(` ${BOLD}Schema Example:${RESET}\n`)
456
+ for (const line of comp.schemaExample.split('\n')) console.log(` ${GRAY}${line}${RESET}`)
457
+ console.log('')
458
+ }
459
+ }
460
+
461
+ function cmdSchema(name) {
462
+ const data = loadComponents()
463
+ const comp = findComponent(data, name)
464
+ if (!comp) {
465
+ console.log(`\n ${RED}✗ Component "${name}" not found.${RESET}\n`)
466
+ process.exit(1)
467
+ }
468
+ console.log(`\n${BOLD}${CYAN}Schema: ${comp.name}${RESET}\n`)
469
+ console.log(comp.schemaExample || JSON.stringify({ type: comp.registryKey, props: {} }, null, 2))
470
+ console.log('')
471
+ }
472
+
473
+ function cmdSearch(query) {
474
+ const data = loadComponents()
475
+ const matches = searchComponents(data, query)
476
+ if (!matches.length) {
477
+ console.log(`\n ${YELLOW}No components matched "${query}"${RESET}\n`)
478
+ return
479
+ }
480
+ const colors = { primitive: GREEN, layout: BLUE, component: CYAN, section: MAGENTA }
481
+ const labels = { primitive: 'Primitive', layout: 'Layout', component: 'Component', section: 'Section' }
482
+ console.log(`\n${BOLD}${CYAN}Search: "${query}"${RESET} ${DIM}(${matches.length} found)${RESET}\n`)
483
+ for (const c of matches) {
484
+ const color = colors[c.category] || CYAN
485
+ console.log(` ${BOLD}${c.name}${RESET} ${color}${labels[c.category] || c.category}${RESET} ${DIM}[${c.registryKey}]${RESET}`)
486
+ if (c.description) console.log(` ${GRAY}${c.description.substring(0, 70)}${c.description.length > 70 ? '…' : ''}${RESET}`)
487
+ }
488
+ console.log('')
489
+ }
490
+
491
+ // ── Help ──
492
+
493
+ function printHelp() {
494
+ console.log(`\n${BOLD}${CYAN}Nons UIKit CLI${RESET}\n`)
495
+ console.log(`${BOLD}Commands:${RESET}\n`)
496
+ console.log(` ${GREEN}init${RESET} Create nons.config.yaml in current directory`)
497
+ console.log(` ${GREEN}generate${RESET} Generate tokens.css + theme.css from config`)
498
+ console.log(` ${GREEN}list${RESET} List all components`)
499
+ console.log(` ${GREEN}show${RESET} ${DIM}<name>${RESET} Show full detail for a component`)
500
+ console.log(` ${GREEN}schema${RESET} ${DIM}<name>${RESET} Show schema JSON for a component`)
501
+ console.log(` ${GREEN}search${RESET} ${DIM}<query>${RESET} Search components by name/description`)
502
+ console.log(` ${GREEN}help${RESET} Show this help\n`)
503
+ console.log(`${BOLD}Usage:${RESET}\n`)
504
+ console.log(` ${DIM}npx @nons-dev/uikit init${RESET}`)
505
+ console.log(` ${DIM}npx @nons-dev/uikit generate${RESET}`)
506
+ console.log(` ${DIM}npx @nons-dev/uikit list${RESET}`)
507
+ console.log(` ${DIM}npx @nons-dev/uikit show Button${RESET}`)
508
+ console.log(` ${DIM}npx @nons-dev/uikit schema Input${RESET}`)
509
+ console.log(` ${DIM}npx @nons-dev/uikit search table${RESET}\n`)
510
+ }
511
+
512
+ // ── Main ──
513
+
514
+ const command = process.argv[2]
515
+ const param = process.argv[3]
516
+
517
+ switch (command) {
518
+ case 'init': cmdInit(); break
519
+ case 'generate': cmdGenerate(); break
520
+ case 'list': cmdList(); break
521
+ case 'show': param ? cmdShow(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
522
+ case 'schema': param ? cmdSchema(param) : (console.error(`${RED}✗${RESET} Missing component name.`), process.exit(1)); break
523
+ case 'search': param ? cmdSearch(param) : (console.error(`${RED}✗${RESET} Missing search query.`), process.exit(1)); break
524
+ case '--help': case '-h': case 'help': default: printHelp(); break
525
+ }