@nice2dev/design-tokens 1.0.28

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,1242 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-MCKGQKYU.js";
4
+
5
+ // src/parser.ts
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ function isDesignToken(obj) {
9
+ return typeof obj === "object" && obj !== null && "$value" in obj && obj.$value !== void 0;
10
+ }
11
+ function isTokenGroup(obj) {
12
+ if (typeof obj !== "object" || obj === null) {
13
+ return false;
14
+ }
15
+ if (isDesignToken(obj)) {
16
+ return false;
17
+ }
18
+ return Object.keys(obj).some((key) => !key.startsWith("$"));
19
+ }
20
+ function isReference(value) {
21
+ if (typeof value !== "string") {
22
+ return false;
23
+ }
24
+ return /^\{[\w.]+\}$/.test(value);
25
+ }
26
+ function extractReferencePath(ref) {
27
+ const match = ref.match(/^\{([\w.]+)\}$/);
28
+ if (!match) {
29
+ throw new Error(`Invalid reference: ${ref}`);
30
+ }
31
+ return match[1].split(".");
32
+ }
33
+ function pathToCssVariable(path3, prefix = "nice") {
34
+ return `--${prefix}-${path3.join("-")}`;
35
+ }
36
+ function pathToString(path3) {
37
+ return path3.join(".");
38
+ }
39
+ function parseTokenFile(filePath) {
40
+ const content = fs.readFileSync(filePath, "utf-8");
41
+ const parsed = JSON.parse(content);
42
+ return parsed;
43
+ }
44
+ function parseTokenFiles(filePaths) {
45
+ const merged = {};
46
+ for (const filePath of filePaths) {
47
+ const tokens = parseTokenFile(filePath);
48
+ mergeTokenFiles(merged, tokens);
49
+ }
50
+ return merged;
51
+ }
52
+ function mergeTokenFiles(target, source) {
53
+ for (const [key, value] of Object.entries(source)) {
54
+ if (key.startsWith("$")) {
55
+ if (!(key in target)) {
56
+ target[key] = value;
57
+ }
58
+ } else if (typeof value === "object" && value !== null) {
59
+ if (!(key in target)) {
60
+ target[key] = {};
61
+ }
62
+ const targetValue = target[key];
63
+ if (typeof targetValue === "object" && targetValue !== null) {
64
+ mergeTokenFiles(targetValue, value);
65
+ }
66
+ } else {
67
+ target[key] = value;
68
+ }
69
+ }
70
+ }
71
+ function flattenTokens(tokenFile, prefix = [], inheritedType) {
72
+ const tokens = [];
73
+ const currentType = tokenFile.$type ?? inheritedType;
74
+ for (const [key, value] of Object.entries(tokenFile)) {
75
+ if (key.startsWith("$")) {
76
+ continue;
77
+ }
78
+ const currentPath = [...prefix, key];
79
+ if (isDesignToken(value)) {
80
+ const token = value;
81
+ const tokenType = token.$type ?? currentType;
82
+ const references = [];
83
+ findReferences(token.$value, references);
84
+ tokens.push({
85
+ path: currentPath,
86
+ name: key,
87
+ value: token.$value,
88
+ resolvedValue: "",
89
+ // Will be populated by resolver
90
+ type: tokenType,
91
+ description: token.$description,
92
+ cssVariable: pathToCssVariable(currentPath),
93
+ references: references.length > 0 ? references : void 0
94
+ });
95
+ } else if (isTokenGroup(value)) {
96
+ const group = value;
97
+ const groupType = group.$type ?? currentType;
98
+ tokens.push(...flattenTokens(group, currentPath, groupType));
99
+ }
100
+ }
101
+ return tokens;
102
+ }
103
+ function findReferences(value, refs) {
104
+ if (typeof value === "string" && isReference(value)) {
105
+ refs.push(extractReferencePath(value).join("."));
106
+ } else if (typeof value === "object" && value !== null) {
107
+ if (Array.isArray(value)) {
108
+ for (const item of value) {
109
+ findReferences(item, refs);
110
+ }
111
+ } else {
112
+ for (const v of Object.values(value)) {
113
+ findReferences(v, refs);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ function createTokenCollection(tokens) {
119
+ const byPath = /* @__PURE__ */ new Map();
120
+ const byName = /* @__PURE__ */ new Map();
121
+ for (const token of tokens) {
122
+ const pathStr = pathToString(token.path);
123
+ byPath.set(pathStr, token);
124
+ const existing = byName.get(token.name);
125
+ if (!existing || token.path.length > existing.path.length) {
126
+ byName.set(token.name, token);
127
+ }
128
+ }
129
+ return { tokens, byPath, byName };
130
+ }
131
+ function parseTokenDirectory(dirPath, pattern = "*.json") {
132
+ const glob = __require("fast-glob");
133
+ const files = glob.sync(path.join(dirPath, pattern));
134
+ const tokenFile = parseTokenFiles(files);
135
+ const flatTokens = flattenTokens(tokenFile);
136
+ return createTokenCollection(flatTokens);
137
+ }
138
+ function getTokenByPath(collection, path3) {
139
+ const pathStr = Array.isArray(path3) ? pathToString(path3) : path3;
140
+ return collection.byPath.get(pathStr);
141
+ }
142
+ function filterTokensByType(collection, type) {
143
+ return collection.tokens.filter((token) => token.type === type);
144
+ }
145
+ function filterTokensByPath(collection, prefix) {
146
+ return collection.tokens.filter((token) => pathToString(token.path).startsWith(prefix));
147
+ }
148
+ function getCategories(collection) {
149
+ const categories = /* @__PURE__ */ new Set();
150
+ for (const token of collection.tokens) {
151
+ if (token.path.length > 0) {
152
+ categories.add(token.path[0]);
153
+ }
154
+ }
155
+ return Array.from(categories);
156
+ }
157
+
158
+ // src/resolver.ts
159
+ var MAX_RESOLUTION_DEPTH = 10;
160
+ function resolveTokens(collection) {
161
+ const resolvedTokens = collection.tokens.map((token) => {
162
+ const resolved = { ...token };
163
+ resolved.resolvedValue = resolveTokenValue(token.value, collection, 0);
164
+ return resolved;
165
+ });
166
+ return {
167
+ tokens: resolvedTokens,
168
+ byPath: new Map(resolvedTokens.map((t) => [pathToString(t.path), t])),
169
+ byName: new Map(resolvedTokens.map((t) => [t.name, t]))
170
+ };
171
+ }
172
+ function resolveTokenValue(value, collection, depth) {
173
+ if (depth > MAX_RESOLUTION_DEPTH) {
174
+ throw new Error(`Maximum reference depth exceeded while resolving token`);
175
+ }
176
+ if (typeof value === "string") {
177
+ if (isReference(value)) {
178
+ const refPath = extractReferencePath(value);
179
+ const refPathStr = pathToString(refPath);
180
+ const refToken = collection.byPath.get(refPathStr);
181
+ if (!refToken) {
182
+ console.warn(`Reference not found: ${value}`);
183
+ return value;
184
+ }
185
+ return resolveTokenValue(refToken.value, collection, depth + 1);
186
+ }
187
+ return value;
188
+ }
189
+ if (typeof value === "number") {
190
+ return String(value);
191
+ }
192
+ if (Array.isArray(value)) {
193
+ return value.map((v) => {
194
+ if (typeof v === "string") {
195
+ return v.includes(" ") ? `"${v}"` : v;
196
+ }
197
+ return String(v);
198
+ }).join(", ");
199
+ }
200
+ if (isShadowValue(value)) {
201
+ return resolveShadowValue(value, collection, depth);
202
+ }
203
+ if (Array.isArray(value) && value.length > 0 && isShadowValue(value[0])) {
204
+ return value.map((s) => resolveShadowValue(s, collection, depth)).join(", ");
205
+ }
206
+ if (isTypographyValue(value)) {
207
+ return resolveTypographyValue(value, collection, depth);
208
+ }
209
+ if (isBorderValue(value)) {
210
+ return resolveBorderValue(value, collection, depth);
211
+ }
212
+ return JSON.stringify(value);
213
+ }
214
+ function isShadowValue(value) {
215
+ if (typeof value !== "object" || value === null) {
216
+ return false;
217
+ }
218
+ return "offsetX" in value && "offsetY" in value && "blur" in value;
219
+ }
220
+ function resolveShadowValue(shadow, collection, depth) {
221
+ const offsetX = resolveTokenValue(shadow.offsetX, collection, depth);
222
+ const offsetY = resolveTokenValue(shadow.offsetY, collection, depth);
223
+ const blur = resolveTokenValue(shadow.blur, collection, depth);
224
+ const spread = resolveTokenValue(shadow.spread, collection, depth);
225
+ const color = resolveTokenValue(shadow.color, collection, depth);
226
+ const inset = shadow.inset ? "inset " : "";
227
+ return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
228
+ }
229
+ function isTypographyValue(value) {
230
+ if (typeof value !== "object" || value === null) {
231
+ return false;
232
+ }
233
+ return "fontFamily" in value && "fontSize" in value;
234
+ }
235
+ function resolveTypographyValue(typography, collection, depth) {
236
+ const fontFamily = resolveTokenValue(typography.fontFamily, collection, depth);
237
+ const fontSize = resolveTokenValue(typography.fontSize, collection, depth);
238
+ const fontWeight = resolveTokenValue(typography.fontWeight, collection, depth);
239
+ const lineHeight = typography.lineHeight ? resolveTokenValue(typography.lineHeight, collection, depth) : "normal";
240
+ const fontStyle = typography.fontStyle || "normal";
241
+ return `${fontStyle} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
242
+ }
243
+ function isBorderValue(value) {
244
+ if (typeof value !== "object" || value === null) {
245
+ return false;
246
+ }
247
+ return "color" in value && "width" in value && "style" in value;
248
+ }
249
+ function resolveBorderValue(border, collection, depth) {
250
+ const width = resolveTokenValue(border.width, collection, depth);
251
+ const style = resolveTokenValue(border.style, collection, depth);
252
+ const color = resolveTokenValue(border.color, collection, depth);
253
+ return `${width} ${style} ${color}`;
254
+ }
255
+ function resolveToVariable(value, collection, _prefix = "nice") {
256
+ if (typeof value === "string" && isReference(value)) {
257
+ const refPath = extractReferencePath(value);
258
+ const refPathStr = pathToString(refPath);
259
+ const refToken = collection.byPath.get(refPathStr);
260
+ if (refToken) {
261
+ return `var(${refToken.cssVariable})`;
262
+ }
263
+ }
264
+ return resolveTokenValue(value, collection, 0);
265
+ }
266
+ function getDependencyGraph(collection) {
267
+ const graph = /* @__PURE__ */ new Map();
268
+ for (const token of collection.tokens) {
269
+ const pathStr = pathToString(token.path);
270
+ const deps = /* @__PURE__ */ new Set();
271
+ if (token.references) {
272
+ for (const ref of token.references) {
273
+ deps.add(ref);
274
+ }
275
+ }
276
+ graph.set(pathStr, deps);
277
+ }
278
+ return graph;
279
+ }
280
+ function sortTokensByDependencies(collection) {
281
+ const graph = getDependencyGraph(collection);
282
+ const visited = /* @__PURE__ */ new Set();
283
+ const sorted = [];
284
+ function visit(pathStr) {
285
+ if (visited.has(pathStr)) {
286
+ return;
287
+ }
288
+ visited.add(pathStr);
289
+ const deps = graph.get(pathStr);
290
+ if (deps) {
291
+ for (const dep of deps) {
292
+ visit(dep);
293
+ }
294
+ }
295
+ const token = collection.byPath.get(pathStr);
296
+ if (token) {
297
+ sorted.push(token);
298
+ }
299
+ }
300
+ for (const token of collection.tokens) {
301
+ visit(pathToString(token.path));
302
+ }
303
+ return sorted;
304
+ }
305
+ function checkCircularReferences(collection) {
306
+ const graph = getDependencyGraph(collection);
307
+ const circles = [];
308
+ const visiting = /* @__PURE__ */ new Set();
309
+ const visited = /* @__PURE__ */ new Set();
310
+ function visit(pathStr, path3) {
311
+ if (visited.has(pathStr)) {
312
+ return false;
313
+ }
314
+ if (visiting.has(pathStr)) {
315
+ const circleStart = path3.indexOf(pathStr);
316
+ circles.push(path3.slice(circleStart));
317
+ return true;
318
+ }
319
+ visiting.add(pathStr);
320
+ path3.push(pathStr);
321
+ const deps = graph.get(pathStr);
322
+ if (deps) {
323
+ for (const dep of deps) {
324
+ if (visit(dep, [...path3])) {
325
+ return true;
326
+ }
327
+ }
328
+ }
329
+ visiting.delete(pathStr);
330
+ visited.add(pathStr);
331
+ return false;
332
+ }
333
+ for (const token of collection.tokens) {
334
+ const pathStr = pathToString(token.path);
335
+ if (!visited.has(pathStr)) {
336
+ visit(pathStr, []);
337
+ }
338
+ }
339
+ return circles;
340
+ }
341
+
342
+ // src/transforms.ts
343
+ var chroma = null;
344
+ async function getChroma() {
345
+ if (!chroma) {
346
+ chroma = (await import("chroma-js")).default;
347
+ }
348
+ return chroma;
349
+ }
350
+ async function hexToRgb(hex) {
351
+ const c = await getChroma();
352
+ const [r, g, b] = c(hex).rgb();
353
+ return `rgb(${r}, ${g}, ${b})`;
354
+ }
355
+ async function hexToHsl(hex) {
356
+ const c = await getChroma();
357
+ const [h, s, l] = c(hex).hsl();
358
+ return `hsl(${Math.round(h)}deg ${Math.round(s * 100)}% ${Math.round(l * 100)}%)`;
359
+ }
360
+ async function hexToOklch(hex) {
361
+ const c = await getChroma();
362
+ const [l, a, b] = c(hex).lab();
363
+ const L = l / 100;
364
+ const C = Math.sqrt(a * a + b * b) / 125;
365
+ const H = Math.atan2(b, a) * 180 / Math.PI;
366
+ return `oklch(${(L * 100).toFixed(1)}% ${C.toFixed(3)} ${H < 0 ? H + 360 : H}deg)`;
367
+ }
368
+ async function lightenColor(hex, amount) {
369
+ const c = await getChroma();
370
+ return c(hex).brighten(amount).hex();
371
+ }
372
+ async function darkenColor(hex, amount) {
373
+ const c = await getChroma();
374
+ return c(hex).darken(amount).hex();
375
+ }
376
+ async function getContrastRatio(fg, bg) {
377
+ const c = await getChroma();
378
+ return c.contrast(fg, bg);
379
+ }
380
+ async function getAccessibleTextColor(bgColor) {
381
+ const c = await getChroma();
382
+ const contrastWhite = c.contrast(bgColor, "white");
383
+ const contrastBlack = c.contrast(bgColor, "black");
384
+ return contrastWhite > contrastBlack ? "#ffffff" : "#000000";
385
+ }
386
+ async function generateColorScale(baseColor, steps = 10) {
387
+ const c = await getChroma();
388
+ const scale = {};
389
+ const labels = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
390
+ const colors = c.scale(["white", baseColor, "black"]).mode("lab").colors(steps + 2);
391
+ for (let i = 0; i < Math.min(labels.length, colors.length - 1); i++) {
392
+ const index = Math.floor(i / (labels.length - 1) * (colors.length - 1));
393
+ scale[labels[i]] = colors[index];
394
+ }
395
+ return scale;
396
+ }
397
+ function pxToRem(px, baseFontSize = 16) {
398
+ const value = typeof px === "string" ? parseFloat(px) : px;
399
+ return `${value / baseFontSize}rem`;
400
+ }
401
+ function remToPx(rem, baseFontSize = 16) {
402
+ const value = typeof rem === "string" ? parseFloat(rem) : rem;
403
+ return `${value * baseFontSize}px`;
404
+ }
405
+ function pxToEm(px, context = 16) {
406
+ const value = typeof px === "string" ? parseFloat(px) : px;
407
+ return `${value / context}em`;
408
+ }
409
+ function fontWeightToNumber(weight) {
410
+ const weights = {
411
+ thin: 100,
412
+ hairline: 100,
413
+ extralight: 200,
414
+ ultralight: 200,
415
+ light: 300,
416
+ normal: 400,
417
+ regular: 400,
418
+ medium: 500,
419
+ semibold: 600,
420
+ demibold: 600,
421
+ bold: 700,
422
+ extrabold: 800,
423
+ ultrabold: 800,
424
+ black: 900,
425
+ heavy: 900
426
+ };
427
+ return (weights[weight.toLowerCase()] ?? parseInt(weight, 10)) || 400;
428
+ }
429
+ function cubicBezierToCss(values) {
430
+ if (values.length !== 4) {
431
+ throw new Error("Cubic bezier must have exactly 4 values");
432
+ }
433
+ return `cubic-bezier(${values.join(", ")})`;
434
+ }
435
+ function durationToMs(duration) {
436
+ if (duration.endsWith("ms")) {
437
+ return parseFloat(duration);
438
+ }
439
+ if (duration.endsWith("s")) {
440
+ return parseFloat(duration) * 1e3;
441
+ }
442
+ return parseFloat(duration);
443
+ }
444
+ function transformToCss(collection, prefix = "nice") {
445
+ const lines = [];
446
+ for (const token of collection.tokens) {
447
+ const varName = `--${prefix}-${token.path.join("-")}`;
448
+ lines.push(` ${varName}: ${token.resolvedValue};`);
449
+ }
450
+ return `:root {
451
+ ${lines.join("\n")}
452
+ }`;
453
+ }
454
+ function transformToScss(collection, prefix = "nice") {
455
+ const lines = [];
456
+ for (const token of collection.tokens) {
457
+ const varName = `$${prefix}-${token.path.join("-")}`;
458
+ lines.push(`${varName}: ${token.resolvedValue};`);
459
+ }
460
+ return lines.join("\n");
461
+ }
462
+ function transformToJs(collection, variableName = "tokens") {
463
+ const obj = buildTokenObject(collection);
464
+ return `export const ${variableName} = ${JSON.stringify(obj, null, 2)};`;
465
+ }
466
+ function transformToTs(collection, variableName = "tokens") {
467
+ const obj = buildTokenObject(collection);
468
+ const types = generateTokenTypes(collection);
469
+ return `${types}
470
+
471
+ export const ${variableName}: Tokens = ${JSON.stringify(obj, null, 2)};`;
472
+ }
473
+ function buildTokenObject(collection) {
474
+ const result = {};
475
+ for (const token of collection.tokens) {
476
+ let current = result;
477
+ for (let i = 0; i < token.path.length - 1; i++) {
478
+ const key = token.path[i];
479
+ if (!(key in current)) {
480
+ current[key] = {};
481
+ }
482
+ current = current[key];
483
+ }
484
+ const lastKey = token.path[token.path.length - 1];
485
+ current[lastKey] = token.resolvedValue;
486
+ }
487
+ return result;
488
+ }
489
+ function generateTokenTypes(collection) {
490
+ const typeObj = buildTypeStructure(collection);
491
+ return `interface Tokens ${formatTypeStructure(typeObj)}`;
492
+ }
493
+ function buildTypeStructure(collection) {
494
+ const result = {};
495
+ for (const token of collection.tokens) {
496
+ let current = result;
497
+ for (let i = 0; i < token.path.length - 1; i++) {
498
+ const key = token.path[i];
499
+ if (!(key in current)) {
500
+ current[key] = {};
501
+ }
502
+ current = current[key];
503
+ }
504
+ const lastKey = token.path[token.path.length - 1];
505
+ current[lastKey] = "string";
506
+ }
507
+ return result;
508
+ }
509
+ function formatTypeStructure(obj, indent = 0) {
510
+ const spaces = " ".repeat(indent);
511
+ const lines = ["{"];
512
+ for (const [key, value] of Object.entries(obj)) {
513
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
514
+ if (typeof value === "string") {
515
+ lines.push(`${spaces} ${safeKey}: ${value};`);
516
+ } else {
517
+ lines.push(
518
+ `${spaces} ${safeKey}: ${formatTypeStructure(value, indent + 1)};`
519
+ );
520
+ }
521
+ }
522
+ lines.push(`${spaces}}`);
523
+ return lines.join("\n");
524
+ }
525
+ function transformToTailwind(collection) {
526
+ const colors = {};
527
+ const spacing = {};
528
+ const fontSize = {};
529
+ const fontFamily = {};
530
+ const borderRadius = {};
531
+ const boxShadow = {};
532
+ for (const token of collection.tokens) {
533
+ const path3 = token.path;
534
+ const value = token.resolvedValue;
535
+ if (path3[0] === "color" || path3[0] === "colors") {
536
+ setNestedValue(colors, path3.slice(1), value);
537
+ } else if (path3[0] === "spacing") {
538
+ spacing[path3.slice(1).join("-")] = value;
539
+ } else if (path3[0] === "typography" && path3[1] === "fontSize") {
540
+ fontSize[path3[2]] = value;
541
+ } else if (path3[0] === "typography" && path3[1] === "fontFamily") {
542
+ fontFamily[path3[2]] = value.split(",").map((f) => f.trim());
543
+ } else if (path3[0] === "effects" && path3[1] === "radius") {
544
+ borderRadius[path3[2]] = value;
545
+ } else if (path3[0] === "effects" && path3[1] === "shadow") {
546
+ boxShadow[path3[2]] = value;
547
+ }
548
+ }
549
+ const config = {
550
+ theme: {
551
+ extend: {
552
+ colors,
553
+ spacing,
554
+ fontSize,
555
+ fontFamily,
556
+ borderRadius,
557
+ boxShadow
558
+ }
559
+ }
560
+ };
561
+ return `module.exports = ${JSON.stringify(config, null, 2)};`;
562
+ }
563
+ function setNestedValue(obj, path3, value) {
564
+ let current = obj;
565
+ for (let i = 0; i < path3.length - 1; i++) {
566
+ const key = path3[i];
567
+ if (!(key in current)) {
568
+ current[key] = {};
569
+ }
570
+ current = current[key];
571
+ }
572
+ current[path3[path3.length - 1]] = value;
573
+ }
574
+ function applyThemeOverrides(baseCollection, overrides) {
575
+ const mergedTokens = [...baseCollection.tokens];
576
+ for (const override of overrides.tokens) {
577
+ const pathStr = pathToString(override.path);
578
+ const existingIndex = mergedTokens.findIndex((t) => pathToString(t.path) === pathStr);
579
+ if (existingIndex >= 0) {
580
+ mergedTokens[existingIndex] = { ...mergedTokens[existingIndex], ...override };
581
+ } else {
582
+ mergedTokens.push(override);
583
+ }
584
+ }
585
+ return {
586
+ tokens: mergedTokens,
587
+ byPath: new Map(mergedTokens.map((t) => [pathToString(t.path), t])),
588
+ byName: new Map(mergedTokens.map((t) => [t.name, t]))
589
+ };
590
+ }
591
+
592
+ // src/themes.ts
593
+ var defaultThemes = {
594
+ light: {
595
+ name: "light",
596
+ displayName: "Light",
597
+ mode: "light",
598
+ selector: ':root, [data-theme="light"]'
599
+ },
600
+ dark: {
601
+ name: "dark",
602
+ displayName: "Dark",
603
+ mode: "dark",
604
+ selector: '[data-theme="dark"]',
605
+ mediaQuery: "(prefers-color-scheme: dark)",
606
+ extends: "light"
607
+ },
608
+ "high-contrast": {
609
+ name: "high-contrast",
610
+ displayName: "High Contrast",
611
+ mode: "high-contrast",
612
+ selector: '[data-theme="high-contrast"]',
613
+ mediaQuery: "(prefers-contrast: more)",
614
+ extends: "light"
615
+ },
616
+ "high-contrast-dark": {
617
+ name: "high-contrast-dark",
618
+ displayName: "High Contrast Dark",
619
+ mode: "high-contrast-dark",
620
+ selector: '[data-theme="high-contrast-dark"]',
621
+ extends: "dark"
622
+ },
623
+ dim: {
624
+ name: "dim",
625
+ displayName: "Dim",
626
+ mode: "dim",
627
+ selector: '[data-theme="dim"]',
628
+ extends: "dark"
629
+ }
630
+ };
631
+ function createTheme(name, baseTokens, overrideTokens, config) {
632
+ const tokens = overrideTokens ? applyThemeOverrides(baseTokens, overrideTokens) : baseTokens;
633
+ const resolvedTokens = resolveTokens(tokens);
634
+ const themeConfig = { ...defaultThemes.light, ...config, name };
635
+ return {
636
+ name,
637
+ tokens: resolvedTokens,
638
+ extends: themeConfig.extends,
639
+ selector: themeConfig.selector,
640
+ mediaQuery: themeConfig.mediaQuery
641
+ };
642
+ }
643
+ function generateThemesCss(themes, prefix = "nice") {
644
+ const sections = [];
645
+ for (const theme of themes) {
646
+ const vars = generateThemeVariables(theme, prefix);
647
+ sections.push(`${theme.selector} {
648
+ ${vars}
649
+ }`);
650
+ if (theme.mediaQuery) {
651
+ sections.push(
652
+ `@media ${theme.mediaQuery} {
653
+ :root {
654
+ ${vars.split("\n").map((l) => ` ${l}`).join("\n")}
655
+ }
656
+ }`
657
+ );
658
+ }
659
+ }
660
+ return sections.join("\n\n");
661
+ }
662
+ function generateThemeVariables(theme, prefix) {
663
+ const lines = [];
664
+ for (const token of theme.tokens.tokens) {
665
+ const varName = `--${prefix}-${token.path.join("-")}`;
666
+ lines.push(` ${varName}: ${token.resolvedValue};`);
667
+ }
668
+ return lines.join("\n");
669
+ }
670
+ function generateThemeSwitcher() {
671
+ return `
672
+ /**
673
+ * Nice2Dev Theme Switcher
674
+ * Auto-generated by @nice2dev/design-tokens
675
+ */
676
+
677
+ const THEME_KEY = 'nice-theme';
678
+ const THEMES = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
679
+
680
+ /**
681
+ * Get current theme
682
+ */
683
+ function getTheme() {
684
+ return document.documentElement.getAttribute('data-theme') || 'light';
685
+ }
686
+
687
+ /**
688
+ * Set theme
689
+ */
690
+ function setTheme(theme) {
691
+ if (!THEMES.includes(theme)) {
692
+ console.warn(\`Invalid theme: \${theme}\`);
693
+ return;
694
+ }
695
+
696
+ document.documentElement.setAttribute('data-theme', theme);
697
+ localStorage.setItem(THEME_KEY, theme);
698
+
699
+ // Dispatch event
700
+ window.dispatchEvent(new CustomEvent('nice-theme-change', { detail: { theme } }));
701
+ }
702
+
703
+ /**
704
+ * Toggle between light and dark
705
+ */
706
+ function toggleTheme() {
707
+ const current = getTheme();
708
+ const next = current === 'dark' ? 'light' : 'dark';
709
+ setTheme(next);
710
+ return next;
711
+ }
712
+
713
+ /**
714
+ * Use system preference
715
+ */
716
+ function useSystemTheme() {
717
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
718
+ const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
719
+
720
+ let theme = 'light';
721
+ if (prefersDark && prefersHighContrast) {
722
+ theme = 'high-contrast-dark';
723
+ } else if (prefersDark) {
724
+ theme = 'dark';
725
+ } else if (prefersHighContrast) {
726
+ theme = 'high-contrast';
727
+ }
728
+
729
+ setTheme(theme);
730
+ return theme;
731
+ }
732
+
733
+ /**
734
+ * Initialize theme on page load
735
+ */
736
+ function initTheme() {
737
+ const saved = localStorage.getItem(THEME_KEY);
738
+
739
+ if (saved && THEMES.includes(saved)) {
740
+ setTheme(saved);
741
+ } else {
742
+ useSystemTheme();
743
+ }
744
+
745
+ // Listen for system changes
746
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
747
+ if (!localStorage.getItem(THEME_KEY)) {
748
+ useSystemTheme();
749
+ }
750
+ });
751
+ }
752
+
753
+ // Auto-init
754
+ if (typeof document !== 'undefined') {
755
+ if (document.readyState === 'loading') {
756
+ document.addEventListener('DOMContentLoaded', initTheme);
757
+ } else {
758
+ initTheme();
759
+ }
760
+ }
761
+
762
+ export { getTheme, setTheme, toggleTheme, useSystemTheme, initTheme, THEMES };
763
+ `.trim();
764
+ }
765
+ function generateReactThemeContext() {
766
+ return `
767
+ 'use client';
768
+
769
+ import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
770
+
771
+ type Theme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark' | 'dim';
772
+
773
+ interface ThemeContextValue {
774
+ theme: Theme;
775
+ setTheme: (theme: Theme) => void;
776
+ toggleTheme: () => void;
777
+ useSystemTheme: () => void;
778
+ }
779
+
780
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
781
+
782
+ const THEME_KEY = 'nice-theme';
783
+ const THEMES: Theme[] = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
784
+
785
+ interface ThemeProviderProps {
786
+ children: React.ReactNode;
787
+ defaultTheme?: Theme;
788
+ }
789
+
790
+ export function ThemeProvider({ children, defaultTheme = 'light' }: ThemeProviderProps) {
791
+ const [theme, setThemeState] = useState<Theme>(defaultTheme);
792
+
793
+ useEffect(() => {
794
+ const saved = localStorage.getItem(THEME_KEY) as Theme | null;
795
+ if (saved && THEMES.includes(saved)) {
796
+ setThemeState(saved);
797
+ document.documentElement.setAttribute('data-theme', saved);
798
+ } else {
799
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
800
+ const initial = prefersDark ? 'dark' : 'light';
801
+ setThemeState(initial);
802
+ document.documentElement.setAttribute('data-theme', initial);
803
+ }
804
+ }, []);
805
+
806
+ const setTheme = useCallback((newTheme: Theme) => {
807
+ setThemeState(newTheme);
808
+ document.documentElement.setAttribute('data-theme', newTheme);
809
+ localStorage.setItem(THEME_KEY, newTheme);
810
+ }, []);
811
+
812
+ const toggleTheme = useCallback(() => {
813
+ setTheme(theme === 'dark' ? 'light' : 'dark');
814
+ }, [theme, setTheme]);
815
+
816
+ const useSystemTheme = useCallback(() => {
817
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
818
+ const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
819
+
820
+ let systemTheme: Theme = 'light';
821
+ if (prefersDark && prefersHighContrast) {
822
+ systemTheme = 'high-contrast-dark';
823
+ } else if (prefersDark) {
824
+ systemTheme = 'dark';
825
+ } else if (prefersHighContrast) {
826
+ systemTheme = 'high-contrast';
827
+ }
828
+
829
+ setTheme(systemTheme);
830
+ localStorage.removeItem(THEME_KEY);
831
+ }, [setTheme]);
832
+
833
+ return (
834
+ <ThemeContext.Provider value={{ theme, setTheme, toggleTheme, useSystemTheme }}>
835
+ {children}
836
+ </ThemeContext.Provider>
837
+ );
838
+ }
839
+
840
+ export function useTheme(): ThemeContextValue {
841
+ const context = useContext(ThemeContext);
842
+ if (!context) {
843
+ throw new Error('useTheme must be used within a ThemeProvider');
844
+ }
845
+ return context;
846
+ }
847
+
848
+ export { THEMES };
849
+ export type { Theme };
850
+ `.trim();
851
+ }
852
+ async function generateDarkModeTokens(lightTokens) {
853
+ const darkTokens = [];
854
+ const chroma2 = (await import("chroma-js")).default;
855
+ for (const token of lightTokens.tokens) {
856
+ if (token.type !== "color" || !token.path.includes("semantic")) {
857
+ continue;
858
+ }
859
+ const pathStr = pathToString(token.path);
860
+ if (pathStr.includes("dark")) {
861
+ continue;
862
+ }
863
+ try {
864
+ const color = chroma2(token.resolvedValue);
865
+ let darkValue;
866
+ if (token.name.includes("background") || token.name.includes("bg")) {
867
+ const [h, s, l] = color.hsl();
868
+ darkValue = chroma2.hsl(h, s, 1 - l).hex();
869
+ } else if (token.name.includes("text") || token.name.includes("foreground")) {
870
+ const [h, s, l] = color.hsl();
871
+ darkValue = chroma2.hsl(h, s, 1 - l).hex();
872
+ } else if (token.name.includes("border")) {
873
+ darkValue = color.brighten(0.5).hex();
874
+ } else {
875
+ const [h, s, l] = color.hsl();
876
+ darkValue = chroma2.hsl(h, Math.min(s * 1.1, 1), Math.min(l * 1.2, 0.9)).hex();
877
+ }
878
+ darkTokens.push({
879
+ ...token,
880
+ value: darkValue,
881
+ resolvedValue: darkValue
882
+ });
883
+ } catch {
884
+ }
885
+ }
886
+ return darkTokens;
887
+ }
888
+ async function generateHighContrastTokens(baseTokens, _minContrast = 7) {
889
+ const contrastTokens = [];
890
+ const chroma2 = (await import("chroma-js")).default;
891
+ for (const token of baseTokens.tokens) {
892
+ if (token.type !== "color") {
893
+ continue;
894
+ }
895
+ try {
896
+ const color = chroma2(token.resolvedValue);
897
+ const [, s, l] = color.hsl();
898
+ let newL = l;
899
+ if (l > 0.5) {
900
+ newL = Math.min(l * 1.2, 1);
901
+ } else {
902
+ newL = Math.max(l * 0.8, 0);
903
+ }
904
+ const newS = Math.min(s * 1.3, 1);
905
+ const newColor = chroma2.hsl(color.get("hsl.h"), newS, newL);
906
+ contrastTokens.push({
907
+ ...token,
908
+ value: newColor.hex(),
909
+ resolvedValue: newColor.hex()
910
+ });
911
+ } catch {
912
+ }
913
+ }
914
+ return contrastTokens;
915
+ }
916
+
917
+ // src/generator.ts
918
+ import * as fs2 from "fs";
919
+ import * as path2 from "path";
920
+ var defaultOptions = {
921
+ formats: ["css"],
922
+ outputDir: "./dist",
923
+ prefix: "nice",
924
+ includePrimitives: true,
925
+ separateFiles: false,
926
+ includeDarkMode: true,
927
+ includeComponents: true
928
+ };
929
+ var TokenGenerator = class {
930
+ constructor(options = {}) {
931
+ this.plugins = [];
932
+ this.collection = null;
933
+ this.themes = [];
934
+ this.options = { ...defaultOptions, ...options };
935
+ }
936
+ /**
937
+ * Add plugin
938
+ */
939
+ use(plugin) {
940
+ this.plugins.push(plugin);
941
+ return this;
942
+ }
943
+ /**
944
+ * Load tokens from files
945
+ */
946
+ load(files) {
947
+ const tokenFile = parseTokenFiles(files);
948
+ let processedTokens = [tokenFile];
949
+ for (const plugin of this.plugins) {
950
+ if (plugin.beforeResolve) {
951
+ processedTokens = plugin.beforeResolve(processedTokens);
952
+ }
953
+ }
954
+ const flatTokens = flattenTokens(processedTokens[0]);
955
+ let collection = createTokenCollection(flatTokens);
956
+ collection = resolveTokens(collection);
957
+ for (const plugin of this.plugins) {
958
+ if (plugin.afterResolve) {
959
+ collection = plugin.afterResolve(collection);
960
+ }
961
+ }
962
+ this.collection = collection;
963
+ return this;
964
+ }
965
+ /**
966
+ * Load tokens from directory
967
+ */
968
+ loadDirectory(dir, pattern = "**/*.json") {
969
+ const glob = __require("fast-glob");
970
+ const files = glob.sync(path2.join(dir, pattern));
971
+ return this.load(files);
972
+ }
973
+ /**
974
+ * Add theme
975
+ */
976
+ addTheme(name, overrideTokens) {
977
+ if (!this.collection) {
978
+ throw new Error("Load tokens first before adding themes");
979
+ }
980
+ const theme = createTheme(name, this.collection, overrideTokens);
981
+ this.themes.push(theme);
982
+ return this;
983
+ }
984
+ /**
985
+ * Generate output files
986
+ */
987
+ async generate() {
988
+ if (!this.collection) {
989
+ throw new Error("No tokens loaded");
990
+ }
991
+ fs2.mkdirSync(this.options.outputDir, { recursive: true });
992
+ for (const format of this.options.formats) {
993
+ await this.generateFormat(format);
994
+ }
995
+ if (this.themes.length > 0) {
996
+ await this.generateThemeFiles();
997
+ }
998
+ }
999
+ /**
1000
+ * Generate single format
1001
+ */
1002
+ async generateFormat(format) {
1003
+ if (!this.collection) {
1004
+ return;
1005
+ }
1006
+ for (const plugin of this.plugins) {
1007
+ if (plugin.customFormat) {
1008
+ const output = plugin.customFormat(this.collection, this.options);
1009
+ if (output) {
1010
+ const ext = this.getExtension(format);
1011
+ fs2.writeFileSync(
1012
+ path2.join(this.options.outputDir, `tokens.${plugin.name}.${ext}`),
1013
+ output
1014
+ );
1015
+ }
1016
+ }
1017
+ }
1018
+ if (this.options.separateFiles) {
1019
+ await this.generateSeparateFiles(format);
1020
+ } else {
1021
+ await this.generateSingleFile(format);
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Generate single output file
1026
+ */
1027
+ async generateSingleFile(format) {
1028
+ if (!this.collection) {
1029
+ return;
1030
+ }
1031
+ const content = this.transform(this.collection, format);
1032
+ const filename = `tokens.${this.getExtension(format)}`;
1033
+ const filepath = path2.join(this.options.outputDir, filename);
1034
+ fs2.writeFileSync(filepath, content);
1035
+ console.log(`Generated: ${filepath}`);
1036
+ }
1037
+ /**
1038
+ * Generate separate files by category
1039
+ */
1040
+ async generateSeparateFiles(format) {
1041
+ if (!this.collection) {
1042
+ return;
1043
+ }
1044
+ const categories = getCategories(this.collection);
1045
+ const ext = this.getExtension(format);
1046
+ for (const category of categories) {
1047
+ const tokens = this.collection.tokens.filter((t) => t.path[0] === category);
1048
+ const catCollection = {
1049
+ tokens,
1050
+ byPath: new Map(tokens.map((t) => [t.path.join("."), t])),
1051
+ byName: new Map(tokens.map((t) => [t.name, t]))
1052
+ };
1053
+ const content = this.transform(catCollection, format);
1054
+ const filename = `${category}.${ext}`;
1055
+ const filepath = path2.join(this.options.outputDir, filename);
1056
+ fs2.writeFileSync(filepath, content);
1057
+ console.log(`Generated: ${filepath}`);
1058
+ }
1059
+ this.generateIndexFile(categories, format);
1060
+ }
1061
+ /**
1062
+ * Generate index file for separated files
1063
+ */
1064
+ generateIndexFile(categories, format) {
1065
+ const ext = this.getExtension(format);
1066
+ let content = "";
1067
+ switch (format) {
1068
+ case "css":
1069
+ content = categories.map((c) => `@import './${c}.css';`).join("\n");
1070
+ break;
1071
+ case "scss":
1072
+ content = categories.map((c) => `@import './${c}';`).join("\n");
1073
+ break;
1074
+ case "js":
1075
+ case "ts":
1076
+ content = categories.map((c) => `export * from './${c}';`).join("\n");
1077
+ break;
1078
+ default:
1079
+ return;
1080
+ }
1081
+ const filepath = path2.join(this.options.outputDir, `index.${ext}`);
1082
+ fs2.writeFileSync(filepath, content);
1083
+ console.log(`Generated: ${filepath}`);
1084
+ }
1085
+ /**
1086
+ * Transform collection to format
1087
+ */
1088
+ transform(collection, format) {
1089
+ const prefix = this.options.prefix ?? "nice";
1090
+ switch (format) {
1091
+ case "css":
1092
+ return transformToCss(collection, prefix);
1093
+ case "scss":
1094
+ return transformToScss(collection, prefix);
1095
+ case "js":
1096
+ return transformToJs(collection);
1097
+ case "ts":
1098
+ return transformToTs(collection);
1099
+ case "json":
1100
+ return JSON.stringify(this.buildNestedObject(collection), null, 2);
1101
+ case "tailwind":
1102
+ return transformToTailwind(collection);
1103
+ default:
1104
+ throw new Error(`Unsupported format: ${format}`);
1105
+ }
1106
+ }
1107
+ /**
1108
+ * Build nested object from flat tokens
1109
+ */
1110
+ buildNestedObject(collection) {
1111
+ const result = {};
1112
+ for (const token of collection.tokens) {
1113
+ let current = result;
1114
+ for (let i = 0; i < token.path.length - 1; i++) {
1115
+ const key = token.path[i];
1116
+ if (!(key in current)) {
1117
+ current[key] = {};
1118
+ }
1119
+ current = current[key];
1120
+ }
1121
+ current[token.path[token.path.length - 1]] = token.resolvedValue;
1122
+ }
1123
+ return result;
1124
+ }
1125
+ /**
1126
+ * Get file extension for format
1127
+ */
1128
+ getExtension(format) {
1129
+ switch (format) {
1130
+ case "css":
1131
+ return "css";
1132
+ case "scss":
1133
+ return "scss";
1134
+ case "js":
1135
+ return "js";
1136
+ case "ts":
1137
+ return "ts";
1138
+ case "json":
1139
+ return "json";
1140
+ case "tailwind":
1141
+ return "tailwind.js";
1142
+ default:
1143
+ return "txt";
1144
+ }
1145
+ }
1146
+ /**
1147
+ * Generate theme-related files
1148
+ */
1149
+ async generateThemeFiles() {
1150
+ const prefix = this.options.prefix ?? "nice";
1151
+ const themesCss = generateThemesCss(this.themes, prefix);
1152
+ fs2.writeFileSync(path2.join(this.options.outputDir, "themes.css"), themesCss);
1153
+ console.log(`Generated: themes.css`);
1154
+ const switcherJs = generateThemeSwitcher();
1155
+ fs2.writeFileSync(path2.join(this.options.outputDir, "theme-switcher.js"), switcherJs);
1156
+ console.log(`Generated: theme-switcher.js`);
1157
+ const reactContext = generateReactThemeContext();
1158
+ fs2.writeFileSync(path2.join(this.options.outputDir, "ThemeContext.tsx"), reactContext);
1159
+ console.log(`Generated: ThemeContext.tsx`);
1160
+ }
1161
+ /**
1162
+ * Get generated token collection
1163
+ */
1164
+ getCollection() {
1165
+ return this.collection;
1166
+ }
1167
+ /**
1168
+ * Get generated themes
1169
+ */
1170
+ getThemes() {
1171
+ return this.themes;
1172
+ }
1173
+ };
1174
+ function createGenerator(options) {
1175
+ return new TokenGenerator(options);
1176
+ }
1177
+ async function generateTokens(tokenDir, outputDir, options) {
1178
+ const generator = createGenerator({
1179
+ ...options,
1180
+ outputDir
1181
+ });
1182
+ generator.loadDirectory(tokenDir);
1183
+ await generator.generate();
1184
+ }
1185
+
1186
+ export {
1187
+ isDesignToken,
1188
+ isTokenGroup,
1189
+ isReference,
1190
+ extractReferencePath,
1191
+ pathToCssVariable,
1192
+ pathToString,
1193
+ parseTokenFile,
1194
+ parseTokenFiles,
1195
+ mergeTokenFiles,
1196
+ flattenTokens,
1197
+ findReferences,
1198
+ createTokenCollection,
1199
+ parseTokenDirectory,
1200
+ getTokenByPath,
1201
+ filterTokensByType,
1202
+ filterTokensByPath,
1203
+ getCategories,
1204
+ resolveTokens,
1205
+ resolveTokenValue,
1206
+ resolveToVariable,
1207
+ getDependencyGraph,
1208
+ sortTokensByDependencies,
1209
+ checkCircularReferences,
1210
+ hexToRgb,
1211
+ hexToHsl,
1212
+ hexToOklch,
1213
+ lightenColor,
1214
+ darkenColor,
1215
+ getContrastRatio,
1216
+ getAccessibleTextColor,
1217
+ generateColorScale,
1218
+ pxToRem,
1219
+ remToPx,
1220
+ pxToEm,
1221
+ fontWeightToNumber,
1222
+ cubicBezierToCss,
1223
+ durationToMs,
1224
+ transformToCss,
1225
+ transformToScss,
1226
+ transformToJs,
1227
+ transformToTs,
1228
+ buildTokenObject,
1229
+ generateTokenTypes,
1230
+ transformToTailwind,
1231
+ defaultThemes,
1232
+ createTheme,
1233
+ generateThemesCss,
1234
+ generateThemeSwitcher,
1235
+ generateReactThemeContext,
1236
+ generateDarkModeTokens,
1237
+ generateHighContrastTokens,
1238
+ defaultOptions,
1239
+ TokenGenerator,
1240
+ createGenerator,
1241
+ generateTokens
1242
+ };