@nice2dev/design-tokens 1.3.17 → 1.4.0

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/dist/cli/index.js CHANGED
@@ -1,18 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- createGenerator
4
- } from "../chunk-PF2R7I6P.js";
5
2
  import {
6
3
  __commonJS,
7
4
  __require
8
- } from "../chunk-6OYMWYZS.js";
5
+ } from "./chunk-6OYMWYZS.js";
9
6
 
10
7
  // package.json
11
8
  var require_package = __commonJS({
12
9
  "package.json"(exports, module) {
13
10
  module.exports = {
14
11
  name: "@nice2dev/design-tokens",
15
- version: "1.3.17",
12
+ version: "1.4.0",
16
13
  description: "W3C Design Tokens for Nice2Dev UI with CLI generator",
17
14
  type: "module",
18
15
  main: "./dist/index.cjs",
@@ -36,7 +33,7 @@ var require_package = __commonJS({
36
33
  "tokens"
37
34
  ],
38
35
  scripts: {
39
- build: "tsup src/index.ts src/cli/index.ts --format esm,cjs --dts --clean",
36
+ build: "tsup src/index.ts --format esm,cjs --dts --clean && tsup src/cli/index.ts --format esm --dts --out-dir dist/cli",
40
37
  dev: "tsup src/index.ts src/cli/index.ts --format esm,cjs --dts --watch",
41
38
  generate: "node dist/cli/index.js generate",
42
39
  test: "vitest run --passWithNoTests",
@@ -73,9 +70,917 @@ var require_package = __commonJS({
73
70
  });
74
71
 
75
72
  // src/cli/index.ts
76
- import * as fs from "fs";
77
- import * as path from "path";
73
+ import * as fs3 from "fs";
74
+ import * as path2 from "path";
78
75
  import { Command } from "commander";
76
+
77
+ // src/generator.ts
78
+ import * as fs2 from "fs";
79
+ import * as path from "path";
80
+
81
+ // src/parser.ts
82
+ import * as fs from "fs";
83
+ function isDesignToken(obj) {
84
+ return typeof obj === "object" && obj !== null && "$value" in obj && obj.$value !== void 0;
85
+ }
86
+ function isTokenGroup(obj) {
87
+ if (typeof obj !== "object" || obj === null) {
88
+ return false;
89
+ }
90
+ if (isDesignToken(obj)) {
91
+ return false;
92
+ }
93
+ return Object.keys(obj).some((key) => !key.startsWith("$"));
94
+ }
95
+ function isReference(value) {
96
+ if (typeof value !== "string") {
97
+ return false;
98
+ }
99
+ return /^\{[\w.]+\}$/.test(value);
100
+ }
101
+ function extractReferencePath(ref) {
102
+ const match = ref.match(/^\{([\w.]+)\}$/);
103
+ if (!match) {
104
+ throw new Error(`Invalid reference: ${ref}`);
105
+ }
106
+ return match[1].split(".");
107
+ }
108
+ function pathToCssVariable(path3, prefix = "nice") {
109
+ return `--${prefix}-${path3.join("-")}`;
110
+ }
111
+ function pathToString(path3) {
112
+ return path3.join(".");
113
+ }
114
+ function parseTokenFile(filePath) {
115
+ const content = fs.readFileSync(filePath, "utf-8");
116
+ const parsed = JSON.parse(content);
117
+ return parsed;
118
+ }
119
+ function parseTokenFiles(filePaths) {
120
+ const merged = {};
121
+ for (const filePath of filePaths) {
122
+ const tokens = parseTokenFile(filePath);
123
+ mergeTokenFiles(merged, tokens);
124
+ }
125
+ return merged;
126
+ }
127
+ function mergeTokenFiles(target, source) {
128
+ for (const [key, value] of Object.entries(source)) {
129
+ if (key.startsWith("$")) {
130
+ if (!(key in target)) {
131
+ target[key] = value;
132
+ }
133
+ } else if (typeof value === "object" && value !== null) {
134
+ if (!(key in target)) {
135
+ target[key] = {};
136
+ }
137
+ const targetValue = target[key];
138
+ if (typeof targetValue === "object" && targetValue !== null) {
139
+ mergeTokenFiles(targetValue, value);
140
+ }
141
+ } else {
142
+ target[key] = value;
143
+ }
144
+ }
145
+ }
146
+ function flattenTokens(tokenFile, prefix = [], inheritedType) {
147
+ const tokens = [];
148
+ const currentType = tokenFile.$type ?? inheritedType;
149
+ for (const [key, value] of Object.entries(tokenFile)) {
150
+ if (key.startsWith("$")) {
151
+ continue;
152
+ }
153
+ const currentPath = [...prefix, key];
154
+ if (isDesignToken(value)) {
155
+ const token = value;
156
+ const tokenType = token.$type ?? currentType;
157
+ const references = [];
158
+ findReferences(token.$value, references);
159
+ tokens.push({
160
+ path: currentPath,
161
+ name: key,
162
+ value: token.$value,
163
+ resolvedValue: "",
164
+ // Will be populated by resolver
165
+ type: tokenType,
166
+ description: token.$description,
167
+ cssVariable: pathToCssVariable(currentPath),
168
+ references: references.length > 0 ? references : void 0
169
+ });
170
+ } else if (isTokenGroup(value)) {
171
+ const group = value;
172
+ const groupType = group.$type ?? currentType;
173
+ tokens.push(...flattenTokens(group, currentPath, groupType));
174
+ }
175
+ }
176
+ return tokens;
177
+ }
178
+ function findReferences(value, refs) {
179
+ if (typeof value === "string" && isReference(value)) {
180
+ refs.push(extractReferencePath(value).join("."));
181
+ } else if (typeof value === "object" && value !== null) {
182
+ if (Array.isArray(value)) {
183
+ for (const item of value) {
184
+ findReferences(item, refs);
185
+ }
186
+ } else {
187
+ for (const v of Object.values(value)) {
188
+ findReferences(v, refs);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ function createTokenCollection(tokens) {
194
+ const byPath = /* @__PURE__ */ new Map();
195
+ const byName = /* @__PURE__ */ new Map();
196
+ for (const token of tokens) {
197
+ const pathStr = pathToString(token.path);
198
+ byPath.set(pathStr, token);
199
+ const existing = byName.get(token.name);
200
+ if (!existing || token.path.length > existing.path.length) {
201
+ byName.set(token.name, token);
202
+ }
203
+ }
204
+ return { tokens, byPath, byName };
205
+ }
206
+ function getCategories(collection) {
207
+ const categories = /* @__PURE__ */ new Set();
208
+ for (const token of collection.tokens) {
209
+ if (token.path.length > 0) {
210
+ categories.add(token.path[0]);
211
+ }
212
+ }
213
+ return Array.from(categories);
214
+ }
215
+
216
+ // src/resolver.ts
217
+ var MAX_RESOLUTION_DEPTH = 10;
218
+ function resolveTokens(collection) {
219
+ const resolvedTokens = collection.tokens.map((token) => {
220
+ const resolved = { ...token };
221
+ resolved.resolvedValue = resolveTokenValue(token.value, collection, 0);
222
+ return resolved;
223
+ });
224
+ return {
225
+ tokens: resolvedTokens,
226
+ byPath: new Map(resolvedTokens.map((t) => [pathToString(t.path), t])),
227
+ byName: new Map(resolvedTokens.map((t) => [t.name, t]))
228
+ };
229
+ }
230
+ function resolveTokenValue(value, collection, depth) {
231
+ if (depth > MAX_RESOLUTION_DEPTH) {
232
+ throw new Error(`Maximum reference depth exceeded while resolving token`);
233
+ }
234
+ if (typeof value === "string") {
235
+ if (isReference(value)) {
236
+ const refPath = extractReferencePath(value);
237
+ const refPathStr = pathToString(refPath);
238
+ const refToken = collection.byPath.get(refPathStr);
239
+ if (!refToken) {
240
+ console.warn(`Reference not found: ${value}`);
241
+ return value;
242
+ }
243
+ return resolveTokenValue(refToken.value, collection, depth + 1);
244
+ }
245
+ return value;
246
+ }
247
+ if (typeof value === "number") {
248
+ return String(value);
249
+ }
250
+ if (Array.isArray(value)) {
251
+ return value.map((v) => {
252
+ if (typeof v === "string") {
253
+ return v.includes(" ") ? `"${v}"` : v;
254
+ }
255
+ return String(v);
256
+ }).join(", ");
257
+ }
258
+ if (isShadowValue(value)) {
259
+ return resolveShadowValue(value, collection, depth);
260
+ }
261
+ if (Array.isArray(value) && value.length > 0 && isShadowValue(value[0])) {
262
+ return value.map((s) => resolveShadowValue(s, collection, depth)).join(", ");
263
+ }
264
+ if (isTypographyValue(value)) {
265
+ return resolveTypographyValue(value, collection, depth);
266
+ }
267
+ if (isBorderValue(value)) {
268
+ return resolveBorderValue(value, collection, depth);
269
+ }
270
+ return JSON.stringify(value);
271
+ }
272
+ function isShadowValue(value) {
273
+ if (typeof value !== "object" || value === null) {
274
+ return false;
275
+ }
276
+ return "offsetX" in value && "offsetY" in value && "blur" in value;
277
+ }
278
+ function resolveShadowValue(shadow, collection, depth) {
279
+ const offsetX = resolveTokenValue(shadow.offsetX, collection, depth);
280
+ const offsetY = resolveTokenValue(shadow.offsetY, collection, depth);
281
+ const blur = resolveTokenValue(shadow.blur, collection, depth);
282
+ const spread = resolveTokenValue(shadow.spread, collection, depth);
283
+ const color = resolveTokenValue(shadow.color, collection, depth);
284
+ const inset = shadow.inset ? "inset " : "";
285
+ return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
286
+ }
287
+ function isTypographyValue(value) {
288
+ if (typeof value !== "object" || value === null) {
289
+ return false;
290
+ }
291
+ return "fontFamily" in value && "fontSize" in value;
292
+ }
293
+ function resolveTypographyValue(typography, collection, depth) {
294
+ const fontFamily = resolveTokenValue(typography.fontFamily, collection, depth);
295
+ const fontSize = resolveTokenValue(typography.fontSize, collection, depth);
296
+ const fontWeight = resolveTokenValue(typography.fontWeight, collection, depth);
297
+ const lineHeight = typography.lineHeight ? resolveTokenValue(typography.lineHeight, collection, depth) : "normal";
298
+ const fontStyle = typography.fontStyle || "normal";
299
+ return `${fontStyle} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
300
+ }
301
+ function isBorderValue(value) {
302
+ if (typeof value !== "object" || value === null) {
303
+ return false;
304
+ }
305
+ return "color" in value && "width" in value && "style" in value;
306
+ }
307
+ function resolveBorderValue(border, collection, depth) {
308
+ const width = resolveTokenValue(border.width, collection, depth);
309
+ const style = resolveTokenValue(border.style, collection, depth);
310
+ const color = resolveTokenValue(border.color, collection, depth);
311
+ return `${width} ${style} ${color}`;
312
+ }
313
+
314
+ // src/transforms.ts
315
+ function transformToCss(collection, prefix = "nice") {
316
+ const lines = [];
317
+ for (const token of collection.tokens) {
318
+ const varName = `--${prefix}-${token.path.join("-")}`;
319
+ lines.push(` ${varName}: ${token.resolvedValue};`);
320
+ }
321
+ return `:root {
322
+ ${lines.join("\n")}
323
+ }`;
324
+ }
325
+ function transformToScss(collection, prefix = "nice") {
326
+ const lines = [];
327
+ for (const token of collection.tokens) {
328
+ const varName = `$${prefix}-${token.path.join("-")}`;
329
+ lines.push(`${varName}: ${token.resolvedValue};`);
330
+ }
331
+ return lines.join("\n");
332
+ }
333
+ function transformToJs(collection, variableName = "tokens") {
334
+ const obj = buildTokenObject(collection);
335
+ return `export const ${variableName} = ${JSON.stringify(obj, null, 2)};`;
336
+ }
337
+ function transformToTs(collection, variableName = "tokens") {
338
+ const obj = buildTokenObject(collection);
339
+ const types = generateTokenTypes(collection);
340
+ return `${types}
341
+
342
+ export const ${variableName}: Tokens = ${JSON.stringify(obj, null, 2)};`;
343
+ }
344
+ function buildTokenObject(collection) {
345
+ const result = {};
346
+ for (const token of collection.tokens) {
347
+ let current = result;
348
+ for (let i = 0; i < token.path.length - 1; i++) {
349
+ const key = token.path[i];
350
+ if (!(key in current)) {
351
+ current[key] = {};
352
+ }
353
+ current = current[key];
354
+ }
355
+ const lastKey = token.path[token.path.length - 1];
356
+ current[lastKey] = token.resolvedValue;
357
+ }
358
+ return result;
359
+ }
360
+ function generateTokenTypes(collection) {
361
+ const typeObj = buildTypeStructure(collection);
362
+ return `interface Tokens ${formatTypeStructure(typeObj)}`;
363
+ }
364
+ function buildTypeStructure(collection) {
365
+ const result = {};
366
+ for (const token of collection.tokens) {
367
+ let current = result;
368
+ for (let i = 0; i < token.path.length - 1; i++) {
369
+ const key = token.path[i];
370
+ if (!(key in current)) {
371
+ current[key] = {};
372
+ }
373
+ current = current[key];
374
+ }
375
+ const lastKey = token.path[token.path.length - 1];
376
+ current[lastKey] = "string";
377
+ }
378
+ return result;
379
+ }
380
+ function formatTypeStructure(obj, indent = 0) {
381
+ const spaces = " ".repeat(indent);
382
+ const lines = ["{"];
383
+ for (const [key, value] of Object.entries(obj)) {
384
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
385
+ if (typeof value === "string") {
386
+ lines.push(`${spaces} ${safeKey}: ${value};`);
387
+ } else {
388
+ lines.push(
389
+ `${spaces} ${safeKey}: ${formatTypeStructure(value, indent + 1)};`
390
+ );
391
+ }
392
+ }
393
+ lines.push(`${spaces}}`);
394
+ return lines.join("\n");
395
+ }
396
+ function transformToTailwind(collection) {
397
+ const colors = {};
398
+ const spacing = {};
399
+ const fontSize = {};
400
+ const fontFamily = {};
401
+ const borderRadius = {};
402
+ const boxShadow = {};
403
+ for (const token of collection.tokens) {
404
+ const path3 = token.path;
405
+ const value = token.resolvedValue;
406
+ if (path3[0] === "color" || path3[0] === "colors") {
407
+ setNestedValue(colors, path3.slice(1), value);
408
+ } else if (path3[0] === "spacing") {
409
+ spacing[path3.slice(1).join("-")] = value;
410
+ } else if (path3[0] === "typography" && path3[1] === "fontSize") {
411
+ fontSize[path3[2]] = value;
412
+ } else if (path3[0] === "typography" && path3[1] === "fontFamily") {
413
+ fontFamily[path3[2]] = value.split(",").map((f) => f.trim());
414
+ } else if (path3[0] === "effects" && path3[1] === "radius") {
415
+ borderRadius[path3[2]] = value;
416
+ } else if (path3[0] === "effects" && path3[1] === "shadow") {
417
+ boxShadow[path3[2]] = value;
418
+ }
419
+ }
420
+ const config = {
421
+ theme: {
422
+ extend: {
423
+ colors,
424
+ spacing,
425
+ fontSize,
426
+ fontFamily,
427
+ borderRadius,
428
+ boxShadow
429
+ }
430
+ }
431
+ };
432
+ return `module.exports = ${JSON.stringify(config, null, 2)};`;
433
+ }
434
+ function setNestedValue(obj, path3, value) {
435
+ let current = obj;
436
+ for (let i = 0; i < path3.length - 1; i++) {
437
+ const key = path3[i];
438
+ if (!(key in current)) {
439
+ current[key] = {};
440
+ }
441
+ current = current[key];
442
+ }
443
+ current[path3[path3.length - 1]] = value;
444
+ }
445
+ function applyThemeOverrides(baseCollection, overrides) {
446
+ const mergedTokens = [...baseCollection.tokens];
447
+ for (const override of overrides.tokens) {
448
+ const pathStr = pathToString(override.path);
449
+ const existingIndex = mergedTokens.findIndex((t) => pathToString(t.path) === pathStr);
450
+ if (existingIndex >= 0) {
451
+ mergedTokens[existingIndex] = { ...mergedTokens[existingIndex], ...override };
452
+ } else {
453
+ mergedTokens.push(override);
454
+ }
455
+ }
456
+ return {
457
+ tokens: mergedTokens,
458
+ byPath: new Map(mergedTokens.map((t) => [pathToString(t.path), t])),
459
+ byName: new Map(mergedTokens.map((t) => [t.name, t]))
460
+ };
461
+ }
462
+
463
+ // src/themes.ts
464
+ var defaultThemes = {
465
+ light: {
466
+ name: "light",
467
+ displayName: "Light",
468
+ mode: "light",
469
+ selector: ':root, [data-theme="light"]'
470
+ },
471
+ dark: {
472
+ name: "dark",
473
+ displayName: "Dark",
474
+ mode: "dark",
475
+ selector: '[data-theme="dark"]',
476
+ mediaQuery: "(prefers-color-scheme: dark)",
477
+ extends: "light"
478
+ },
479
+ "high-contrast": {
480
+ name: "high-contrast",
481
+ displayName: "High Contrast",
482
+ mode: "high-contrast",
483
+ selector: '[data-theme="high-contrast"]',
484
+ mediaQuery: "(prefers-contrast: more)",
485
+ extends: "light"
486
+ },
487
+ "high-contrast-dark": {
488
+ name: "high-contrast-dark",
489
+ displayName: "High Contrast Dark",
490
+ mode: "high-contrast-dark",
491
+ selector: '[data-theme="high-contrast-dark"]',
492
+ extends: "dark"
493
+ },
494
+ dim: {
495
+ name: "dim",
496
+ displayName: "Dim",
497
+ mode: "dim",
498
+ selector: '[data-theme="dim"]',
499
+ extends: "dark"
500
+ }
501
+ };
502
+ function createTheme(name, baseTokens, overrideTokens, config) {
503
+ const tokens = overrideTokens ? applyThemeOverrides(baseTokens, overrideTokens) : baseTokens;
504
+ const resolvedTokens = resolveTokens(tokens);
505
+ const themeConfig = { ...defaultThemes.light, ...config, name };
506
+ return {
507
+ name,
508
+ tokens: resolvedTokens,
509
+ extends: themeConfig.extends,
510
+ selector: themeConfig.selector,
511
+ mediaQuery: themeConfig.mediaQuery
512
+ };
513
+ }
514
+ function generateThemesCss(themes, prefix = "nice") {
515
+ const sections = [];
516
+ for (const theme of themes) {
517
+ const vars = generateThemeVariables(theme, prefix);
518
+ sections.push(`${theme.selector} {
519
+ ${vars}
520
+ }`);
521
+ if (theme.mediaQuery) {
522
+ sections.push(
523
+ `@media ${theme.mediaQuery} {
524
+ :root {
525
+ ${vars.split("\n").map((l) => ` ${l}`).join("\n")}
526
+ }
527
+ }`
528
+ );
529
+ }
530
+ }
531
+ return sections.join("\n\n");
532
+ }
533
+ function generateThemeVariables(theme, prefix) {
534
+ const lines = [];
535
+ for (const token of theme.tokens.tokens) {
536
+ const varName = `--${prefix}-${token.path.join("-")}`;
537
+ lines.push(` ${varName}: ${token.resolvedValue};`);
538
+ }
539
+ return lines.join("\n");
540
+ }
541
+ function generateThemeSwitcher() {
542
+ return `
543
+ /**
544
+ * Nice2Dev Theme Switcher
545
+ * Auto-generated by @nice2dev/design-tokens
546
+ */
547
+
548
+ const THEME_KEY = 'nice-theme';
549
+ const THEMES = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
550
+
551
+ /**
552
+ * Get current theme
553
+ */
554
+ function getTheme() {
555
+ return document.documentElement.getAttribute('data-theme') || 'light';
556
+ }
557
+
558
+ /**
559
+ * Set theme
560
+ */
561
+ function setTheme(theme) {
562
+ if (!THEMES.includes(theme)) {
563
+ console.warn(\`Invalid theme: \${theme}\`);
564
+ return;
565
+ }
566
+
567
+ document.documentElement.setAttribute('data-theme', theme);
568
+ localStorage.setItem(THEME_KEY, theme);
569
+
570
+ // Dispatch event
571
+ window.dispatchEvent(new CustomEvent('nice-theme-change', { detail: { theme } }));
572
+ }
573
+
574
+ /**
575
+ * Toggle between light and dark
576
+ */
577
+ function toggleTheme() {
578
+ const current = getTheme();
579
+ const next = current === 'dark' ? 'light' : 'dark';
580
+ setTheme(next);
581
+ return next;
582
+ }
583
+
584
+ /**
585
+ * Use system preference
586
+ */
587
+ function useSystemTheme() {
588
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
589
+ const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
590
+
591
+ let theme = 'light';
592
+ if (prefersDark && prefersHighContrast) {
593
+ theme = 'high-contrast-dark';
594
+ } else if (prefersDark) {
595
+ theme = 'dark';
596
+ } else if (prefersHighContrast) {
597
+ theme = 'high-contrast';
598
+ }
599
+
600
+ setTheme(theme);
601
+ return theme;
602
+ }
603
+
604
+ /**
605
+ * Initialize theme on page load
606
+ */
607
+ function initTheme() {
608
+ const saved = localStorage.getItem(THEME_KEY);
609
+
610
+ if (saved && THEMES.includes(saved)) {
611
+ setTheme(saved);
612
+ } else {
613
+ useSystemTheme();
614
+ }
615
+
616
+ // Listen for system changes
617
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
618
+ if (!localStorage.getItem(THEME_KEY)) {
619
+ useSystemTheme();
620
+ }
621
+ });
622
+ }
623
+
624
+ // Auto-init
625
+ if (typeof document !== 'undefined') {
626
+ if (document.readyState === 'loading') {
627
+ document.addEventListener('DOMContentLoaded', initTheme);
628
+ } else {
629
+ initTheme();
630
+ }
631
+ }
632
+
633
+ export { getTheme, setTheme, toggleTheme, useSystemTheme, initTheme, THEMES };
634
+ `.trim();
635
+ }
636
+ function generateReactThemeContext() {
637
+ return `
638
+ 'use client';
639
+
640
+ import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
641
+
642
+ type Theme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark' | 'dim';
643
+
644
+ interface ThemeContextValue {
645
+ theme: Theme;
646
+ setTheme: (theme: Theme) => void;
647
+ toggleTheme: () => void;
648
+ useSystemTheme: () => void;
649
+ }
650
+
651
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
652
+
653
+ const THEME_KEY = 'nice-theme';
654
+ const THEMES: Theme[] = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
655
+
656
+ interface ThemeProviderProps {
657
+ children: React.ReactNode;
658
+ defaultTheme?: Theme;
659
+ }
660
+
661
+ export function ThemeProvider({ children, defaultTheme = 'light' }: ThemeProviderProps) {
662
+ const [theme, setThemeState] = useState<Theme>(defaultTheme);
663
+
664
+ useEffect(() => {
665
+ const saved = localStorage.getItem(THEME_KEY) as Theme | null;
666
+ if (saved && THEMES.includes(saved)) {
667
+ setThemeState(saved);
668
+ document.documentElement.setAttribute('data-theme', saved);
669
+ } else {
670
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
671
+ const initial = prefersDark ? 'dark' : 'light';
672
+ setThemeState(initial);
673
+ document.documentElement.setAttribute('data-theme', initial);
674
+ }
675
+ }, []);
676
+
677
+ const setTheme = useCallback((newTheme: Theme) => {
678
+ setThemeState(newTheme);
679
+ document.documentElement.setAttribute('data-theme', newTheme);
680
+ localStorage.setItem(THEME_KEY, newTheme);
681
+ }, []);
682
+
683
+ const toggleTheme = useCallback(() => {
684
+ setTheme(theme === 'dark' ? 'light' : 'dark');
685
+ }, [theme, setTheme]);
686
+
687
+ const useSystemTheme = useCallback(() => {
688
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
689
+ const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
690
+
691
+ let systemTheme: Theme = 'light';
692
+ if (prefersDark && prefersHighContrast) {
693
+ systemTheme = 'high-contrast-dark';
694
+ } else if (prefersDark) {
695
+ systemTheme = 'dark';
696
+ } else if (prefersHighContrast) {
697
+ systemTheme = 'high-contrast';
698
+ }
699
+
700
+ setTheme(systemTheme);
701
+ localStorage.removeItem(THEME_KEY);
702
+ }, [setTheme]);
703
+
704
+ return (
705
+ <ThemeContext.Provider value={{ theme, setTheme, toggleTheme, useSystemTheme }}>
706
+ {children}
707
+ </ThemeContext.Provider>
708
+ );
709
+ }
710
+
711
+ export function useTheme(): ThemeContextValue {
712
+ const context = useContext(ThemeContext);
713
+ if (!context) {
714
+ throw new Error('useTheme must be used within a ThemeProvider');
715
+ }
716
+ return context;
717
+ }
718
+
719
+ export { THEMES };
720
+ export type { Theme };
721
+ `.trim();
722
+ }
723
+
724
+ // src/generator.ts
725
+ var defaultOptions = {
726
+ formats: ["css"],
727
+ outputDir: "./dist",
728
+ prefix: "nice",
729
+ includePrimitives: true,
730
+ separateFiles: false,
731
+ includeDarkMode: true,
732
+ includeComponents: true
733
+ };
734
+ var TokenGenerator = class {
735
+ constructor(options = {}) {
736
+ this.plugins = [];
737
+ this.collection = null;
738
+ this.themes = [];
739
+ this.options = { ...defaultOptions, ...options };
740
+ }
741
+ /**
742
+ * Add plugin
743
+ */
744
+ use(plugin) {
745
+ this.plugins.push(plugin);
746
+ return this;
747
+ }
748
+ /**
749
+ * Load tokens from files
750
+ */
751
+ load(files) {
752
+ const tokenFile = parseTokenFiles(files);
753
+ let processedTokens = [tokenFile];
754
+ for (const plugin of this.plugins) {
755
+ if (plugin.beforeResolve) {
756
+ processedTokens = plugin.beforeResolve(processedTokens);
757
+ }
758
+ }
759
+ const flatTokens = flattenTokens(processedTokens[0]);
760
+ let collection = createTokenCollection(flatTokens);
761
+ collection = resolveTokens(collection);
762
+ for (const plugin of this.plugins) {
763
+ if (plugin.afterResolve) {
764
+ collection = plugin.afterResolve(collection);
765
+ }
766
+ }
767
+ this.collection = collection;
768
+ return this;
769
+ }
770
+ /**
771
+ * Load tokens from directory
772
+ */
773
+ loadDirectory(dir, pattern = "**/*.json") {
774
+ const glob = __require("fast-glob");
775
+ const files = glob.sync(path.join(dir, pattern));
776
+ return this.load(files);
777
+ }
778
+ /**
779
+ * Add theme
780
+ */
781
+ addTheme(name, overrideTokens) {
782
+ if (!this.collection) {
783
+ throw new Error("Load tokens first before adding themes");
784
+ }
785
+ const theme = createTheme(name, this.collection, overrideTokens);
786
+ this.themes.push(theme);
787
+ return this;
788
+ }
789
+ /**
790
+ * Generate output files
791
+ */
792
+ async generate() {
793
+ if (!this.collection) {
794
+ throw new Error("No tokens loaded");
795
+ }
796
+ fs2.mkdirSync(this.options.outputDir, { recursive: true });
797
+ for (const format of this.options.formats) {
798
+ await this.generateFormat(format);
799
+ }
800
+ if (this.themes.length > 0) {
801
+ await this.generateThemeFiles();
802
+ }
803
+ }
804
+ /**
805
+ * Generate single format
806
+ */
807
+ async generateFormat(format) {
808
+ if (!this.collection) {
809
+ return;
810
+ }
811
+ for (const plugin of this.plugins) {
812
+ if (plugin.customFormat) {
813
+ const output = plugin.customFormat(this.collection, this.options);
814
+ if (output) {
815
+ const ext = this.getExtension(format);
816
+ fs2.writeFileSync(
817
+ path.join(this.options.outputDir, `tokens.${plugin.name}.${ext}`),
818
+ output
819
+ );
820
+ }
821
+ }
822
+ }
823
+ if (this.options.separateFiles) {
824
+ await this.generateSeparateFiles(format);
825
+ } else {
826
+ await this.generateSingleFile(format);
827
+ }
828
+ }
829
+ /**
830
+ * Generate single output file
831
+ */
832
+ async generateSingleFile(format) {
833
+ if (!this.collection) {
834
+ return;
835
+ }
836
+ const content = this.transform(this.collection, format);
837
+ const filename = `tokens.${this.getExtension(format)}`;
838
+ const filepath = path.join(this.options.outputDir, filename);
839
+ fs2.writeFileSync(filepath, content);
840
+ console.log(`Generated: ${filepath}`);
841
+ }
842
+ /**
843
+ * Generate separate files by category
844
+ */
845
+ async generateSeparateFiles(format) {
846
+ if (!this.collection) {
847
+ return;
848
+ }
849
+ const categories = getCategories(this.collection);
850
+ const ext = this.getExtension(format);
851
+ for (const category of categories) {
852
+ const tokens = this.collection.tokens.filter((t) => t.path[0] === category);
853
+ const catCollection = {
854
+ tokens,
855
+ byPath: new Map(tokens.map((t) => [t.path.join("."), t])),
856
+ byName: new Map(tokens.map((t) => [t.name, t]))
857
+ };
858
+ const content = this.transform(catCollection, format);
859
+ const filename = `${category}.${ext}`;
860
+ const filepath = path.join(this.options.outputDir, filename);
861
+ fs2.writeFileSync(filepath, content);
862
+ console.log(`Generated: ${filepath}`);
863
+ }
864
+ this.generateIndexFile(categories, format);
865
+ }
866
+ /**
867
+ * Generate index file for separated files
868
+ */
869
+ generateIndexFile(categories, format) {
870
+ const ext = this.getExtension(format);
871
+ let content = "";
872
+ switch (format) {
873
+ case "css":
874
+ content = categories.map((c) => `@import './${c}.css';`).join("\n");
875
+ break;
876
+ case "scss":
877
+ content = categories.map((c) => `@import './${c}';`).join("\n");
878
+ break;
879
+ case "js":
880
+ case "ts":
881
+ content = categories.map((c) => `export * from './${c}';`).join("\n");
882
+ break;
883
+ default:
884
+ return;
885
+ }
886
+ const filepath = path.join(this.options.outputDir, `index.${ext}`);
887
+ fs2.writeFileSync(filepath, content);
888
+ console.log(`Generated: ${filepath}`);
889
+ }
890
+ /**
891
+ * Transform collection to format
892
+ */
893
+ transform(collection, format) {
894
+ const prefix = this.options.prefix ?? "nice";
895
+ switch (format) {
896
+ case "css":
897
+ return transformToCss(collection, prefix);
898
+ case "scss":
899
+ return transformToScss(collection, prefix);
900
+ case "js":
901
+ return transformToJs(collection);
902
+ case "ts":
903
+ return transformToTs(collection);
904
+ case "json":
905
+ return JSON.stringify(this.buildNestedObject(collection), null, 2);
906
+ case "tailwind":
907
+ return transformToTailwind(collection);
908
+ default:
909
+ throw new Error(`Unsupported format: ${format}`);
910
+ }
911
+ }
912
+ /**
913
+ * Build nested object from flat tokens
914
+ */
915
+ buildNestedObject(collection) {
916
+ const result = {};
917
+ for (const token of collection.tokens) {
918
+ let current = result;
919
+ for (let i = 0; i < token.path.length - 1; i++) {
920
+ const key = token.path[i];
921
+ if (!(key in current)) {
922
+ current[key] = {};
923
+ }
924
+ current = current[key];
925
+ }
926
+ current[token.path[token.path.length - 1]] = token.resolvedValue;
927
+ }
928
+ return result;
929
+ }
930
+ /**
931
+ * Get file extension for format
932
+ */
933
+ getExtension(format) {
934
+ switch (format) {
935
+ case "css":
936
+ return "css";
937
+ case "scss":
938
+ return "scss";
939
+ case "js":
940
+ return "js";
941
+ case "ts":
942
+ return "ts";
943
+ case "json":
944
+ return "json";
945
+ case "tailwind":
946
+ return "tailwind.js";
947
+ default:
948
+ return "txt";
949
+ }
950
+ }
951
+ /**
952
+ * Generate theme-related files
953
+ */
954
+ async generateThemeFiles() {
955
+ const prefix = this.options.prefix ?? "nice";
956
+ const themesCss = generateThemesCss(this.themes, prefix);
957
+ fs2.writeFileSync(path.join(this.options.outputDir, "themes.css"), themesCss);
958
+ console.log(`Generated: themes.css`);
959
+ const switcherJs = generateThemeSwitcher();
960
+ fs2.writeFileSync(path.join(this.options.outputDir, "theme-switcher.js"), switcherJs);
961
+ console.log(`Generated: theme-switcher.js`);
962
+ const reactContext = generateReactThemeContext();
963
+ fs2.writeFileSync(path.join(this.options.outputDir, "ThemeContext.tsx"), reactContext);
964
+ console.log(`Generated: ThemeContext.tsx`);
965
+ }
966
+ /**
967
+ * Get generated token collection
968
+ */
969
+ getCollection() {
970
+ return this.collection;
971
+ }
972
+ /**
973
+ * Get generated themes
974
+ */
975
+ getThemes() {
976
+ return this.themes;
977
+ }
978
+ };
979
+ function createGenerator(options) {
980
+ return new TokenGenerator(options);
981
+ }
982
+
983
+ // src/cli/index.ts
79
984
  var pkg = require_package();
80
985
  var program = new Command();
81
986
  program.name("nice-tokens").description("Generate design tokens in various formats").version(pkg.version);
@@ -86,8 +991,8 @@ program.command("build").description("Build design tokens from JSON source files
86
991
  ).option("-p, --prefix <prefix>", "CSS variable prefix", "nice").option("--separate", "Generate separate files by category").option("--no-dark", "Exclude dark mode tokens").option("--no-components", "Exclude component tokens").option("--watch", "Watch for changes and rebuild").action(async (options) => {
87
992
  const formats = options.formats.split(",").map((f) => f.trim());
88
993
  console.log("Nice2Dev Design Tokens Generator\n");
89
- console.log(`Input: ${path.resolve(options.input)}`);
90
- console.log(`Output: ${path.resolve(options.output)}`);
994
+ console.log(`Input: ${path2.resolve(options.input)}`);
995
+ console.log(`Output: ${path2.resolve(options.output)}`);
91
996
  console.log(`Formats: ${formats.join(", ")}`);
92
997
  console.log("");
93
998
  try {
@@ -104,13 +1009,13 @@ program.command("build").description("Build design tokens from JSON source files
104
1009
  console.log("\nBuild complete!");
105
1010
  if (options.watch) {
106
1011
  console.log("\nWatching for changes...");
107
- const chokidar = await import("../esm-LPVAZ4IA.js");
108
- const watcher = chokidar.watch(path.join(options.input, "**/*.json"), {
1012
+ const chokidar = await import("./esm-LPVAZ4IA.js");
1013
+ const watcher = chokidar.watch(path2.join(options.input, "**/*.json"), {
109
1014
  ignoreInitial: true
110
1015
  });
111
1016
  watcher.on("change", async (file) => {
112
1017
  console.log(`
113
- File changed: ${path.basename(file)}`);
1018
+ File changed: ${path2.basename(file)}`);
114
1019
  try {
115
1020
  const gen = createGenerator({
116
1021
  outputDir: options.output,
@@ -134,11 +1039,11 @@ File changed: ${path.basename(file)}`);
134
1039
  }
135
1040
  });
136
1041
  program.command("init").description("Initialize a new design tokens project").option("-d, --dir <dir>", "Directory to initialize", ".").action(async (options) => {
137
- const dir = path.resolve(options.dir);
138
- const tokensDir = path.join(dir, "tokens");
1042
+ const dir = path2.resolve(options.dir);
1043
+ const tokensDir = path2.join(dir, "tokens");
139
1044
  console.log("Initializing Nice2Dev Design Tokens\n");
140
- fs.mkdirSync(tokensDir, { recursive: true });
141
- fs.mkdirSync(path.join(dir, "dist"), { recursive: true });
1045
+ fs3.mkdirSync(tokensDir, { recursive: true });
1046
+ fs3.mkdirSync(path2.join(dir, "dist"), { recursive: true });
142
1047
  const colorsJson = {
143
1048
  color: {
144
1049
  $type: "color",
@@ -159,7 +1064,7 @@ program.command("init").description("Initialize a new design tokens project").op
159
1064
  }
160
1065
  }
161
1066
  };
162
- fs.writeFileSync(path.join(tokensDir, "colors.json"), JSON.stringify(colorsJson, null, 2));
1067
+ fs3.writeFileSync(path2.join(tokensDir, "colors.json"), JSON.stringify(colorsJson, null, 2));
163
1068
  const typographyJson = {
164
1069
  typography: {
165
1070
  fontFamily: {
@@ -177,8 +1082,8 @@ program.command("init").description("Initialize a new design tokens project").op
177
1082
  }
178
1083
  }
179
1084
  };
180
- fs.writeFileSync(
181
- path.join(tokensDir, "typography.json"),
1085
+ fs3.writeFileSync(
1086
+ path2.join(tokensDir, "typography.json"),
182
1087
  JSON.stringify(typographyJson, null, 2)
183
1088
  );
184
1089
  const spacingJson = {
@@ -192,7 +1097,7 @@ program.command("init").description("Initialize a new design tokens project").op
192
1097
  "8": { $value: "2rem" }
193
1098
  }
194
1099
  };
195
- fs.writeFileSync(path.join(tokensDir, "spacing.json"), JSON.stringify(spacingJson, null, 2));
1100
+ fs3.writeFileSync(path2.join(tokensDir, "spacing.json"), JSON.stringify(spacingJson, null, 2));
196
1101
  console.log("Created token files in ./tokens/");
197
1102
  console.log("");
198
1103
  console.log("Next steps:");
@@ -204,7 +1109,7 @@ program.command("validate").description("Validate design token files").argument(
204
1109
  const glob = __require("fast-glob");
205
1110
  let tokenFiles = files;
206
1111
  if (options.dir) {
207
- tokenFiles = await glob(path.join(options.dir, "**/*.json"));
1112
+ tokenFiles = await glob(path2.join(options.dir, "**/*.json"));
208
1113
  }
209
1114
  if (tokenFiles.length === 0) {
210
1115
  console.log("No token files specified");
@@ -214,11 +1119,11 @@ program.command("validate").description("Validate design token files").argument(
214
1119
  let hasErrors = false;
215
1120
  for (const file of tokenFiles) {
216
1121
  try {
217
- const content = fs.readFileSync(file, "utf-8");
1122
+ const content = fs3.readFileSync(file, "utf-8");
218
1123
  JSON.parse(content);
219
- console.log(`OK ${path.basename(file)}`);
1124
+ console.log(`OK ${path2.basename(file)}`);
220
1125
  } catch (err) {
221
- console.log(`FAIL ${path.basename(file)}: ${err.message}`);
1126
+ console.log(`FAIL ${path2.basename(file)}: ${err.message}`);
222
1127
  hasErrors = true;
223
1128
  }
224
1129
  }
@@ -236,9 +1141,9 @@ program.command("convert").description("Convert between token formats").argument
236
1141
  try {
237
1142
  const generator = createGenerator({
238
1143
  formats: [format],
239
- outputDir: path.dirname(output)
1144
+ outputDir: path2.dirname(output)
240
1145
  });
241
- const inputStat = fs.statSync(input);
1146
+ const inputStat = fs3.statSync(input);
242
1147
  if (inputStat.isDirectory()) {
243
1148
  generator.loadDirectory(input);
244
1149
  } else {
@@ -253,9 +1158,9 @@ program.command("convert").description("Convert between token formats").argument
253
1158
  });
254
1159
  program.command("info").description("Show information about token files").argument("<dir>", "Directory containing token files").action(async (dir) => {
255
1160
  const glob = __require("fast-glob");
256
- const files = await glob(path.join(dir, "**/*.json"));
1161
+ const files = await glob(path2.join(dir, "**/*.json"));
257
1162
  console.log("Token Information\n");
258
- console.log(`Directory: ${path.resolve(dir)}`);
1163
+ console.log(`Directory: ${path2.resolve(dir)}`);
259
1164
  console.log(`Files: ${files.length}`);
260
1165
  console.log("");
261
1166
  const generator = createGenerator();
@@ -286,8 +1191,8 @@ program.command("diff").description("Compare two token files or directories").ar
286
1191
  console.log("Comparing tokens\n");
287
1192
  const generatorA = createGenerator();
288
1193
  const generatorB = createGenerator();
289
- const statA = fs.statSync(source);
290
- const statB = fs.statSync(target);
1194
+ const statA = fs3.statSync(source);
1195
+ const statB = fs3.statSync(target);
291
1196
  if (statA.isDirectory()) {
292
1197
  generatorA.loadDirectory(source);
293
1198
  } else {
@@ -309,21 +1214,21 @@ program.command("diff").description("Compare two token files or directories").ar
309
1214
  const added = [];
310
1215
  const removed = [];
311
1216
  const changed = [];
312
- for (const path2 of pathsB) {
313
- if (!pathsA.has(path2)) {
314
- added.push(path2);
1217
+ for (const path3 of pathsB) {
1218
+ if (!pathsA.has(path3)) {
1219
+ added.push(path3);
315
1220
  }
316
1221
  }
317
- for (const path2 of pathsA) {
318
- if (!pathsB.has(path2)) {
319
- removed.push(path2);
1222
+ for (const path3 of pathsA) {
1223
+ if (!pathsB.has(path3)) {
1224
+ removed.push(path3);
320
1225
  }
321
1226
  }
322
1227
  for (const tokenA of collectionA.tokens) {
323
- const path2 = tokenA.path.join(".");
324
- const tokenB = collectionB.byPath.get(path2);
1228
+ const path3 = tokenA.path.join(".");
1229
+ const tokenB = collectionB.byPath.get(path3);
325
1230
  if (tokenB && tokenA.resolvedValue !== tokenB.resolvedValue) {
326
- changed.push(`${path2}: ${tokenA.resolvedValue} \u2192 ${tokenB.resolvedValue}`);
1231
+ changed.push(`${path3}: ${tokenA.resolvedValue} \u2192 ${tokenB.resolvedValue}`);
327
1232
  }
328
1233
  }
329
1234
  if (added.length === 0 && removed.length === 0 && changed.length === 0) {