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