@nd-storybook/storybook 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nd-storybook/storybook",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "files": [
24
24
  "dist",
25
+ "scripts",
25
26
  "tailwind.config.js",
26
27
  "tailwind.tokens.js",
27
28
  "tokens.utilities.js"
@@ -0,0 +1,1110 @@
1
+ import fs from 'fs';
2
+
3
+ // ----------------------
4
+ // 🔧 Config
5
+ // ----------------------
6
+ const files = {
7
+ primitives: './tokens/primitives.value.tokens.json',
8
+ semantics: './tokens/semantic-colors.light.tokens.json',
9
+ semanticsDark: './tokens/semantic-colors.dark.tokens.json',
10
+ spacing: './tokens/semantic-layout.mobile.tokens.json',
11
+ effects: './tokens/effect.styles.tokens.json',
12
+ typography: './tokens/text.styles.tokens.json',
13
+ semanticBorders: './tokens/semantic-border.value.tokens.json',
14
+ responsiveTypography: {
15
+ mobile: './tokens/semantic-typography.mobile.tokens.json',
16
+ tablet: './tokens/semantic-typography.tablet.tokens.json',
17
+ desktop: './tokens/semantic-typography.desktop.tokens.json'
18
+ },
19
+ componentStates: {
20
+ default: './tokens/component-colors.default.tokens.json',
21
+ hover: './tokens/component-colors.hover.tokens.json',
22
+ active: './tokens/component-colors.active.tokens.json',
23
+ disabled: './tokens/component-colors.disabled.tokens.json'
24
+ }
25
+ };
26
+
27
+ // ----------------------
28
+ // 🧠 Utilities
29
+ // ----------------------
30
+ function load(file) {
31
+ try {
32
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
33
+ } catch (error) {
34
+ console.error(`Error loading file ${file}:`, error.message);
35
+ return {};
36
+ }
37
+ }
38
+
39
+ // Function to map font weight strings to numeric values
40
+ function mapFontWeight(value) {
41
+ const fontWeightMap = {
42
+ 'Bold': 700,
43
+ 'SemiBold': 600,
44
+ 'Regular': 400,
45
+ 'Light': 300
46
+ };
47
+
48
+ return fontWeightMap[value] || value;
49
+ }
50
+
51
+ // Legacy reference mapping to handle old token references
52
+ const legacyReferenceMap = {
53
+ // Font size mappings
54
+ 'font-size.heading.xl': '28px',
55
+ 'font-size.heading.l': '25px',
56
+ 'font-size.heading.m': '20px', // Should be 20px for heading.m
57
+ 'font-size.heading.s': '18px', // Should be 18px for heading.s
58
+ 'font-size.heading.xs': '14px', // Should be 14px for heading.xs
59
+ 'font-size.body.xxl': '28px', // Should be 28px
60
+ 'font-size.body.xl': '22px', // Should be 22px for body.xl
61
+ 'font-size.body.l': '20px', // Should be 20px for body.l
62
+ 'font-size.body.m': '18px', // Should be 18px for body.m
63
+ 'font-size.body.s': '16px', // Should be 16px for body.s
64
+ 'font-size.body.xs': '12px',
65
+ 'font-size.body.drop-cap': '96px', // Should be 96px based on your tokens
66
+ 'font-size.meta': '14px',
67
+
68
+ // Font family mappings - CORRECTED!
69
+ 'typography.font-family.gulliver semibold': "Gulliver Web, Georgia, serif", // ✅ Fixed - no quotes wrapping
70
+ 'typography.font-family.gulliver_semibold': "Gulliver Web, Georgia, serif", // ✅ Added alternate key
71
+ 'typography.font-family.gulliver': "Gulliver Web, Georgia, serif", // ✅ FIXED - This was the main issue!
72
+ 'typography.font-family.fira-sans': "Fira Sans", // ✅ Fixed - removed quotes
73
+ 'typography.font-family.montserrat': "Montserrat", // ✅ Fixed - removed quotes
74
+ 'typography.font-family.abril-fatface': "Abril Fatface", // ✅ Fixed - removed quotes
75
+
76
+ // Font weight mappings
77
+ 'typography.font-weight.light': '300',
78
+ 'typography.font-weight.regular': '400',
79
+ 'typography.font-weight.semibold': '600', // Added semibold
80
+ 'typography.font-weight.bold': '700',
81
+
82
+ // Letter spacing mappings - CORRECTED!
83
+ 'typography.letter-spacing.0': '0px', // Changed from 0% to 0px
84
+ 'typography.letter-spacing.s': '1.6px', // Should be 1.6px based on your primitives
85
+ 'typography.letter-spacing.m': '2.4px', // Should be 2.4px based on your primitives
86
+ 'typography.letter-spacing.l': '4px'
87
+ };
88
+
89
+ function flatten(obj, path = [], result = {}) {
90
+ for (const [key, val] of Object.entries(obj)) {
91
+ const current = [...path, key];
92
+ if (val && typeof val === 'object' && '$value' in val) {
93
+ result[current.join('.')] = val;
94
+ } else if (typeof val === 'object' && val !== null) {
95
+ flatten(val, current, result);
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+
101
+ function resolveReference(ref, flatTokens, visited = new Set()) {
102
+ if (!ref || typeof ref !== 'string') return ref;
103
+
104
+ // If it's not a reference, return as is
105
+ if (!ref.startsWith('{') || !ref.endsWith('}')) return ref;
106
+
107
+ const path = ref.replace(/[{}]/g, '');
108
+
109
+ // Prevent circular references
110
+ if (visited.has(path)) {
111
+ console.warn(`Circular reference detected: ${path}`);
112
+ return undefined;
113
+ }
114
+
115
+ visited.add(path);
116
+
117
+ // FIRST: Try to find the token in the actual flattened tokens
118
+ let token = flatTokens[path];
119
+
120
+ // If not found, try some common variations
121
+ if (!token) {
122
+ // Try with different prefixes/formats
123
+ const variations = [
124
+ path,
125
+ path.replace(/^font-size\./, 'fontSize.'),
126
+ path.replace(/^typography\.font-family\./, 'fontFamily.'),
127
+ path.replace(/\s+/g, '_'), // Replace spaces with underscores
128
+ path.replace(/\s+/g, '-'), // Replace spaces with hyphens
129
+ path.replace(/\./g, '-').toLowerCase(),
130
+ // Handle the specific case of gulliver_semibold
131
+ path.replace('gulliver semibold', 'gulliver_semibold'),
132
+ path.replace('gulliver-semibold', 'gulliver_semibold')
133
+ ];
134
+
135
+ for (const variant of variations) {
136
+ if (flatTokens[variant]) {
137
+ token = flatTokens[variant];
138
+ console.log(`Found token via variation: ${path} -> ${variant}`);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+
144
+ // If we found the token, use its actual value
145
+ if (token && token.$value) {
146
+ const value = token.$value;
147
+
148
+ // If the value is another reference, resolve it recursively
149
+ if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
150
+ return resolveReference(value, flatTokens, visited);
151
+ }
152
+
153
+ // For font weights, convert string to number if needed
154
+ if (path.includes('font-weight')) {
155
+ return mapFontWeight(value);
156
+ }
157
+
158
+ console.log(`✅ Resolved ${ref} -> ${value}`);
159
+ return value;
160
+ }
161
+
162
+ // ONLY use legacy map as a last resort fallback
163
+ if (legacyReferenceMap[path]) {
164
+ console.warn(`⚠️ Using legacy fallback for ${path} (token not found in source)`);
165
+ console.warn(` This should be fixed - token should exist in your JSON files`);
166
+ return legacyReferenceMap[path];
167
+ }
168
+
169
+ console.error(`❌ Reference not found and no fallback: ${path}`);
170
+ console.error(` Available font-family tokens:`,
171
+ Object.keys(flatTokens).filter(k => k.includes('font-family')));
172
+
173
+ return undefined;
174
+ }
175
+
176
+ function toVar(name) {
177
+ return `var(--${name})`;
178
+ }
179
+
180
+ function kebab(str) {
181
+ return str
182
+ .replace(/\./g, '-') // Replace dots with hyphens
183
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
184
+ .replace(/\+/g, '-plus-') // Replace + with -plus-
185
+ .replace(/[^a-zA-Z0-9-]/g, '-') // Replace other special chars with hyphens
186
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Add hyphen between camelCase
187
+ .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
188
+ .toLowerCase(); // Convert to lowercase
189
+ }
190
+
191
+ // Deep merge utility to properly combine nested objects without overwriting
192
+ function deepMerge(target, source) {
193
+ const result = { ...target };
194
+ for (const key of Object.keys(source)) {
195
+ if (
196
+ source[key] &&
197
+ typeof source[key] === 'object' &&
198
+ !('$value' in source[key]) &&
199
+ !Array.isArray(source[key])
200
+ ) {
201
+ // If both are objects (and not token values), merge recursively
202
+ result[key] = deepMerge(result[key] || {}, source[key]);
203
+ } else {
204
+ // Otherwise, use source value
205
+ result[key] = source[key];
206
+ }
207
+ }
208
+ return result;
209
+ }
210
+
211
+ // ----------------------
212
+ // 🚀 Build All Token Sets
213
+ // ----------------------
214
+ console.log('🔄 Loading and processing token files...');
215
+
216
+ // Load all base tokens using deep merge to preserve nested structures
217
+ // (e.g., border.border-width from primitives AND border.gray from semantics)
218
+ let baseTokens = load(files.primitives);
219
+ baseTokens = deepMerge(baseTokens, load(files.semantics));
220
+ baseTokens = deepMerge(baseTokens, load(files.effects));
221
+ baseTokens = deepMerge(baseTokens, load(files.typography));
222
+
223
+ const flattenedBase = flatten(baseTokens);
224
+
225
+ const primitiveColors = {};
226
+ const semanticColors = {};
227
+ const cssVarMap = {};
228
+ const primitiveFontFamilies = {};
229
+ const primitiveFontSizes = {};
230
+ const primitiveFontWeights = {};
231
+ const primitiveLetterSpacings = {};
232
+
233
+ // Font family fallbacks mapping
234
+ const fontFamilyFallbacks = {
235
+ 'gulliver': '"Gulliver Web", Georgia, serif',
236
+ 'fira-sans': '"Fira Sans", Arial, sans-serif',
237
+ 'montserrat': '"Montserrat", "Helvetica Neue", Arial, sans-serif',
238
+ 'abril-fatface': '"Abril Fatface", "Playfair Display", Georgia, serif'
239
+ };
240
+
241
+ for (const [tokenPath, token] of Object.entries(flattenedBase)) {
242
+ // Process font families
243
+ if (tokenPath.includes('font-family') || tokenPath.includes('fontFamily')) {
244
+ const fontName = tokenPath.split('.').pop();
245
+
246
+ // Skip gulliver-semibold since it should just use gulliver
247
+ if (fontName.toLowerCase().includes('gulliver') && fontName.toLowerCase().includes('semibold')) {
248
+ continue;
249
+ }
250
+
251
+ const kebabName = kebab(fontName);
252
+ const fontKey = `font-family-${kebabName}`;
253
+
254
+ // Use fallback if available, otherwise use the token value
255
+ const fontValue = fontFamilyFallbacks[kebabName] || token.$value;
256
+
257
+ primitiveFontFamilies[kebabName] = toVar(fontKey);
258
+ cssVarMap[fontKey] = fontValue;
259
+ }
260
+ // Process font weights
261
+ else if (tokenPath.includes('font-weight') || tokenPath.includes('fontWeight')) {
262
+ const weightName = tokenPath.split('.').pop();
263
+ const weightKey = `font-weight-${kebab(weightName)}`;
264
+ primitiveFontWeights[kebab(weightName)] = toVar(weightKey);
265
+ cssVarMap[weightKey] = mapFontWeight(token.$value);
266
+ }
267
+ // Process letter spacings
268
+ else if (tokenPath.includes('letter-spacing') || tokenPath.includes('letterSpacing')) {
269
+ const spacingName = tokenPath.split('.').pop();
270
+ const spacingKey = `letter-spacing-${kebab(spacingName)}`;
271
+ primitiveLetterSpacings[kebab(spacingName)] = toVar(spacingKey);
272
+ cssVarMap[spacingKey] = token.$value;
273
+ }
274
+ // Process font sizes
275
+ else if (tokenPath.includes('font-size') || tokenPath.includes('fontSize')) {
276
+ const sizeName = tokenPath.split('.').pop();
277
+ const sizeKey = `font-size-${kebab(sizeName)}`;
278
+ primitiveFontSizes[kebab(sizeName)] = toVar(sizeKey);
279
+ cssVarMap[sizeKey] = token.$value;
280
+
281
+ // Add additional mappings for common references
282
+ if (sizeName.includes('heading') || sizeName.includes('body')) {
283
+ // Create mappings for the old reference format
284
+ const category = sizeName.includes('heading') ? 'heading' : 'body';
285
+ const size = sizeName.split('-').pop();
286
+ cssVarMap[`font-size-${category}-${size}`] = token.$value;
287
+ }
288
+ }
289
+
290
+ // Create meta font size tokens for backward compatibility
291
+ if (tokenPath.includes('meta') && (tokenPath.includes('font-size') || tokenPath.includes('fontSize'))) {
292
+ cssVarMap['font-size-meta'] = token.$value;
293
+ cssVarMap['font-size-meta-font-size-meta'] = token.$value;
294
+ }
295
+ }
296
+
297
+ for (const [tokenPath, token] of Object.entries(flattenedBase)) {
298
+ if (token?.$type === 'color') {
299
+ const raw = token.$value;
300
+ const resolved = raw.startsWith('{')
301
+ ? resolveReference(raw, flattenedBase) ?? raw
302
+ : raw;
303
+ const kebabKey = kebab(tokenPath);
304
+
305
+ // Always prefix with "color-" regardless of whether it's primitive or semantic
306
+ const prefixedKey = tokenPath.startsWith('color.') ? kebabKey : `color-${kebabKey}`;
307
+
308
+ if (tokenPath.startsWith('color.')) {
309
+ const tailwindKey = kebabKey.replace(/^color-/, '');
310
+ primitiveColors[tailwindKey] = toVar(prefixedKey);
311
+ } else {
312
+ semanticColors[kebabKey] = toVar(prefixedKey);
313
+ }
314
+
315
+ cssVarMap[prefixedKey] = resolved;
316
+ }
317
+ }
318
+
319
+ // ----------------------
320
+ // 🌑 Process Dark Mode Colors
321
+ // ----------------------
322
+ console.log('🔄 Processing dark mode colors...');
323
+
324
+ const darkModeColors = {};
325
+
326
+ try {
327
+ const darkSemantics = load(files.semanticsDark);
328
+ const flattenedDark = flatten(darkSemantics);
329
+
330
+ for (const [tokenPath, token] of Object.entries(flattenedDark)) {
331
+ if (token?.$type === 'color') {
332
+ const raw = token.$value;
333
+ const resolved = raw.startsWith('{')
334
+ ? resolveReference(raw, {...flattenedBase, ...flattenedDark}) ?? raw
335
+ : raw;
336
+ const kebabKey = kebab(tokenPath);
337
+
338
+ // Use the same key structure as light mode
339
+ const prefixedKey = tokenPath.startsWith('color.') ? kebabKey : `color-${kebabKey}`;
340
+
341
+ darkModeColors[prefixedKey] = resolved;
342
+ }
343
+ }
344
+
345
+ console.log(`✅ Processed ${Object.keys(darkModeColors).length} dark mode color tokens`);
346
+ } catch (error) {
347
+ console.error(`Error processing dark mode colors: ${error.message}`);
348
+ }
349
+
350
+ // ----------------------
351
+ // 🎯 Process Component Tokens
352
+ // ----------------------
353
+ console.log('🔄 Processing component tokens...');
354
+
355
+ const componentColors = {};
356
+ const utilityClasses = {};
357
+
358
+ for (const [state, file] of Object.entries(files.componentStates)) {
359
+ const component = load(file);
360
+
361
+ (function process(obj, prefix = '') {
362
+ for (const [key, val] of Object.entries(obj)) {
363
+ const path = prefix ? `${prefix}-${key}` : key;
364
+
365
+ if (val?.$value) {
366
+ const raw = val.$value;
367
+ const resolved = raw.startsWith('{')
368
+ ? resolveReference(raw, flattenedBase) ?? raw
369
+ : raw;
370
+
371
+ const fullVarName = `${path}-${state}`;
372
+ const tailwindKey = state === 'default' ? path : `${path}-${state}`;
373
+
374
+ // Prefix component colors with "color-" instead of "component-"
375
+ const prefixedVarName = `color-${fullVarName}`;
376
+ componentColors[tailwindKey] = toVar(prefixedVarName);
377
+ cssVarMap[prefixedVarName] = resolved;
378
+
379
+ // Generate utility classes based on token name patterns
380
+ if (path.includes('bg')) {
381
+ utilityClasses[`.bg-${tailwindKey}`] = {backgroundColor: `var(--${prefixedVarName})`};
382
+ } else if (path.includes('text')) {
383
+ utilityClasses[`.text-${tailwindKey}`] = {color: `var(--${prefixedVarName})`};
384
+ } else if (path.includes('border')) {
385
+ utilityClasses[`.border-${tailwindKey}`] = {borderColor: `var(--${prefixedVarName})`};
386
+ }
387
+ } else if (typeof val === 'object' && val !== null) {
388
+ process(val, path);
389
+ }
390
+ }
391
+ })(component);
392
+ }
393
+ // -------------------------
394
+ // 🌑 Process Shadow Tokens
395
+ // -------------------------
396
+ console.log('🔄 Processing shadow tokens...');
397
+
398
+ const primitiveBoxShadows = {};
399
+ const shadowUtilities = {};
400
+
401
+ // Helper function to convert shadow object to CSS string
402
+ function shadowObjectToCSS(shadowObj) {
403
+ if (typeof shadowObj === 'string') return shadowObj;
404
+
405
+ const x = shadowObj.x || shadowObj.offsetX || '0px';
406
+ const y = shadowObj.y || shadowObj.offsetY || '0px';
407
+ const blur = shadowObj.blur || shadowObj.blurRadius || '0px';
408
+ const spread = shadowObj.spread || shadowObj.spreadRadius || '0px';
409
+ const color = shadowObj.color || 'rgba(0, 0, 0, 0.1)';
410
+ const inset = shadowObj.inset ? 'inset ' : '';
411
+
412
+ return `${inset}${x} ${y} ${blur} ${spread} ${color}`.trim();
413
+ }
414
+
415
+ // Process shadow tokens from all files
416
+ for (const [tokenPath, token] of Object.entries(flattenedBase)) {
417
+ // Check for shadows at root level (like shadow-s, shadow-m, shadow-l)
418
+ if ((tokenPath.includes('shadow') || tokenPath.includes('Shadow')) && token?.$type === 'shadow' && token?.$value !== undefined) {
419
+ console.log(` Processing shadow token: ${tokenPath}`);
420
+
421
+ let resolved;
422
+ const raw = token.$value;
423
+
424
+ // Handle different value formats
425
+ if (Array.isArray(raw)) {
426
+ // Handle array of shadows (multiple shadows)
427
+ resolved = raw.map(shadow => {
428
+ const shadowStr = shadowObjectToCSS(shadow);
429
+ return shadowStr.replace(/\{([^}]+)\}/g, (match, ref) => {
430
+ const colorValue = resolveReference(match, flattenedBase);
431
+ return colorValue || match;
432
+ });
433
+ }).join(', ');
434
+ } else if (typeof raw === 'object' && !raw.$value) {
435
+ // Handle single object shadow
436
+ resolved = shadowObjectToCSS(raw);
437
+ resolved = resolved.replace(/\{([^}]+)\}/g, (match, ref) => {
438
+ const colorValue = resolveReference(match, flattenedBase);
439
+ return colorValue || match;
440
+ });
441
+ } else if (typeof raw === 'string') {
442
+ // Handle string format shadow
443
+ resolved = raw;
444
+ if (raw.includes('{')) {
445
+ resolved = raw.replace(/\{([^}]+)\}/g, (match, ref) => {
446
+ const colorValue = resolveReference(match, flattenedBase);
447
+ return colorValue || match;
448
+ });
449
+ }
450
+ }
451
+
452
+ // Extract shadow name from the path
453
+ let shadowName = tokenPath;
454
+
455
+ // Handle different naming patterns
456
+ if (tokenPath.startsWith('shadow.')) {
457
+ shadowName = tokenPath.replace('shadow.', '');
458
+ } else if (tokenPath.startsWith('shadow-')) {
459
+ shadowName = tokenPath.replace('shadow-', '');
460
+ }
461
+
462
+ const originalKey = kebab(shadowName);
463
+ const kebabKey = `box-shadow-${originalKey}`;
464
+
465
+ // Store as CSS variable reference
466
+ primitiveBoxShadows[originalKey] = `var(--${kebabKey})`;
467
+ // Add to cssVarMap for CSS variable generation
468
+ cssVarMap[kebabKey] = resolved;
469
+
470
+ // Generate shadow utility classes
471
+ shadowUtilities[`.shadow-${originalKey}`] = {boxShadow: `var(--${kebabKey})`};
472
+
473
+ console.log(` ✅ Processed shadow: ${originalKey} = ${resolved}`);
474
+ }
475
+ }
476
+ //console.log(` Total shadows processed: ${Object.keys(primitiveBoxShadows).length}`);
477
+ //console.log(` Shadows found: ${Object.keys(primitiveBoxShadows).join(', ')}`);
478
+
479
+ // ----------------------
480
+ // 📏 Process Spacing Tokens
481
+ // ----------------------
482
+ console.log('🔄 Processing spacing tokens...');
483
+
484
+ const spacing = {};
485
+ const spacingUtilities = {};
486
+ const primitiveBorderWidth = {};
487
+ const primitiveBorderRadius = {};
488
+ const semanticBorderWidth = {};
489
+ const semanticBorderRadius = {};
490
+ const borderUtilities = {};
491
+
492
+ // Process spacing tokens from semantic-spacing file
493
+ try {
494
+ const rawSpacing = load(files.spacing);
495
+ const flattenedSpacing = flatten({spacing: rawSpacing}, []);
496
+
497
+ for (const [tokenPath, token] of Object.entries(flattenedSpacing)) {
498
+ if (token?.$type === 'dimension') {
499
+ const raw = token.$value;
500
+ const resolved = raw.startsWith('{')
501
+ ? resolveReference(raw, {...flattenedBase, ...flattenedSpacing})
502
+ : raw;
503
+
504
+ // Skip if reference couldn't be resolved
505
+ if (!resolved) {
506
+ console.warn(`Could not resolve reference: ${raw} for ${tokenPath}`);
507
+ continue;
508
+ }
509
+
510
+ const kebabKey = kebab(tokenPath);
511
+ const varName = `--${kebabKey}`;
512
+
513
+ spacing[kebabKey] = `var(${varName})`;
514
+ cssVarMap[kebabKey] = resolved;
515
+
516
+ // Generate spacing utility classes
517
+ spacingUtilities[`.p-${kebabKey}`] = {padding: `var(${varName})`};
518
+ spacingUtilities[`.px-${kebabKey}`] = {paddingLeft: `var(${varName})`, paddingRight: `var(${varName})`};
519
+ spacingUtilities[`.py-${kebabKey}`] = {paddingTop: `var(${varName})`, paddingBottom: `var(${varName})`};
520
+ spacingUtilities[`.pt-${kebabKey}`] = {paddingTop: `var(${varName})`};
521
+ spacingUtilities[`.pr-${kebabKey}`] = {paddingRight: `var(${varName})`};
522
+ spacingUtilities[`.pb-${kebabKey}`] = {paddingBottom: `var(${varName})`};
523
+ spacingUtilities[`.pl-${kebabKey}`] = {paddingLeft: `var(${varName})`};
524
+
525
+ spacingUtilities[`.m-${kebabKey}`] = {margin: `var(${varName})`};
526
+ spacingUtilities[`.mx-${kebabKey}`] = {marginLeft: `var(${varName})`, marginRight: `var(${varName})`};
527
+ spacingUtilities[`.my-${kebabKey}`] = {marginTop: `var(${varName})`, marginBottom: `var(${varName})`};
528
+ spacingUtilities[`.mt-${kebabKey}`] = {marginTop: `var(${varName})`};
529
+ spacingUtilities[`.mr-${kebabKey}`] = {marginRight: `var(${varName})`};
530
+ spacingUtilities[`.mb-${kebabKey}`] = {marginBottom: `var(${varName})`};
531
+ spacingUtilities[`.ml-${kebabKey}`] = {marginLeft: `var(${varName})`};
532
+
533
+ spacingUtilities[`.gap-${kebabKey}`] = {gap: `var(${varName})`};
534
+ }
535
+ }
536
+ } catch (error) {
537
+ console.warn(`Error processing semantic spacing: ${error.message}`);
538
+ }
539
+
540
+ // Also process spacing tokens from primitives file
541
+ for (const [tokenPath, token] of Object.entries(flattenedBase)) {
542
+ if (tokenPath.startsWith('spacing.') && token?.$type === 'dimension') {
543
+ const raw = token.$value;
544
+ const resolved = raw.startsWith('{')
545
+ ? resolveReference(raw, flattenedBase)
546
+ : raw;
547
+
548
+ // Skip if reference couldn't be resolved
549
+ if (!resolved) {
550
+ console.warn(`Could not resolve reference: ${raw} for ${tokenPath}`);
551
+ continue;
552
+ }
553
+
554
+ const spacingName = tokenPath.split('.').pop();
555
+ // Use the original name as the key (without spacing- prefix)
556
+ const originalKey = kebab(spacingName);
557
+ // But keep the CSS variable name prefixed
558
+ const kebabKey = `spacing-${originalKey}`;
559
+ const varName = `--${kebabKey}`;
560
+
561
+ // Use the original name as the key in the spacing object
562
+ spacing[originalKey] = `var(${varName})`;
563
+ cssVarMap[kebabKey] = resolved;
564
+
565
+ // Generate spacing utility classes with the original name
566
+ spacingUtilities[`.p-${originalKey}`] = {padding: `var(${varName})`};
567
+ spacingUtilities[`.px-${originalKey}`] = {paddingLeft: `var(${varName})`, paddingRight: `var(${varName})`};
568
+ spacingUtilities[`.py-${originalKey}`] = {paddingTop: `var(${varName})`, paddingBottom: `var(${varName})`};
569
+ spacingUtilities[`.pt-${originalKey}`] = {paddingTop: `var(${varName})`};
570
+ spacingUtilities[`.pr-${originalKey}`] = {paddingRight: `var(${varName})`};
571
+ spacingUtilities[`.pb-${originalKey}`] = {paddingBottom: `var(${varName})`};
572
+ spacingUtilities[`.pl-${originalKey}`] = {paddingLeft: `var(${varName})`};
573
+
574
+ spacingUtilities[`.m-${originalKey}`] = {margin: `var(${varName})`};
575
+ spacingUtilities[`.mx-${originalKey}`] = {marginLeft: `var(${varName})`, marginRight: `var(${varName})`};
576
+ spacingUtilities[`.my-${originalKey}`] = {marginTop: `var(${varName})`, marginBottom: `var(${varName})`};
577
+ spacingUtilities[`.mt-${originalKey}`] = {marginTop: `var(${varName})`};
578
+ spacingUtilities[`.mr-${originalKey}`] = {marginRight: `var(${varName})`};
579
+ spacingUtilities[`.mb-${originalKey}`] = {marginBottom: `var(${varName})`};
580
+ spacingUtilities[`.ml-${originalKey}`] = {marginLeft: `var(${varName})`};
581
+
582
+ spacingUtilities[`.gap-${originalKey}`] = {gap: `var(${varName})`};
583
+ }
584
+ }
585
+
586
+ // ----------------------
587
+ // 🔲 Process Border Tokens
588
+ // ----------------------
589
+ console.log('🔄 Processing border tokens...');
590
+
591
+ // Process border tokens from primitives file
592
+ for (const [tokenPath, token] of Object.entries(flattenedBase)) {
593
+ // Process border width tokens
594
+ if (tokenPath.startsWith('border.border-width.') && token?.$type === 'dimension') {
595
+ const raw = token.$value;
596
+ const resolved = raw.startsWith('{')
597
+ ? resolveReference(raw, flattenedBase)
598
+ : raw;
599
+
600
+ // Skip if reference couldn't be resolved
601
+ if (!resolved) {
602
+ console.warn(`Could not resolve reference: ${raw} for ${tokenPath}`);
603
+ continue;
604
+ }
605
+
606
+ const widthName = tokenPath.split('.').pop();
607
+ const originalKey = kebab(widthName);
608
+ const kebabKey = `border-width-${originalKey}`;
609
+
610
+ // Store as CSS variable reference instead of raw value
611
+ primitiveBorderWidth[originalKey] = `var(--${kebabKey})`;
612
+ // Make sure to add to cssVarMap for CSS variable generation
613
+ cssVarMap[kebabKey] = resolved;
614
+
615
+ // Generate border width utility classes
616
+ borderUtilities[`.border-${originalKey}`] = {borderWidth: `var(--${kebabKey})`};
617
+ borderUtilities[`.border-t-${originalKey}`] = {borderTopWidth: `var(--${kebabKey})`};
618
+ borderUtilities[`.border-r-${originalKey}`] = {borderRightWidth: `var(--${kebabKey})`};
619
+ borderUtilities[`.border-b-${originalKey}`] = {borderBottomWidth: `var(--${kebabKey})`};
620
+ borderUtilities[`.border-l-${originalKey}`] = {borderLeftWidth: `var(--${kebabKey})`};
621
+ }
622
+
623
+ // Process border radius tokens
624
+ if (tokenPath.startsWith('border.border-radius.') && token?.$type === 'dimension') {
625
+ const raw = token.$value;
626
+ const resolved = raw.startsWith('{')
627
+ ? resolveReference(raw, flattenedBase)
628
+ : raw;
629
+
630
+ // Skip if reference couldn't be resolved
631
+ if (!resolved) {
632
+ console.warn(`Could not resolve reference: ${raw} for ${tokenPath}`);
633
+ continue;
634
+ }
635
+
636
+ const radiusName = tokenPath.split('.').pop();
637
+ const originalKey = kebab(radiusName);
638
+ const kebabKey = `border-radius-${originalKey}`;
639
+
640
+ // Store as CSS variable reference instead of raw value
641
+ primitiveBorderRadius[originalKey] = `var(--${kebabKey})`;
642
+ // Make sure to add to cssVarMap for CSS variable generation
643
+ cssVarMap[kebabKey] = resolved;
644
+
645
+ // Generate border radius utility classes
646
+ borderUtilities[`.rounded-${originalKey}`] = {borderRadius: `var(--${kebabKey})`};
647
+ borderUtilities[`.rounded-t-${originalKey}`] = {
648
+ borderTopLeftRadius: `var(--${kebabKey})`,
649
+ borderTopRightRadius: `var(--${kebabKey})`
650
+ };
651
+ borderUtilities[`.rounded-r-${originalKey}`] = {
652
+ borderTopRightRadius: `var(--${kebabKey})`,
653
+ borderBottomRightRadius: `var(--${kebabKey})`
654
+ };
655
+ borderUtilities[`.rounded-b-${originalKey}`] = {
656
+ borderBottomLeftRadius: `var(--${kebabKey})`,
657
+ borderBottomRightRadius: `var(--${kebabKey})`
658
+ };
659
+ borderUtilities[`.rounded-l-${originalKey}`] = {
660
+ borderTopLeftRadius: `var(--${kebabKey})`,
661
+ borderBottomLeftRadius: `var(--${kebabKey})`
662
+ };
663
+ borderUtilities[`.rounded-tl-${originalKey}`] = {borderTopLeftRadius: `var(--${kebabKey})`};
664
+ borderUtilities[`.rounded-tr-${originalKey}`] = {borderTopRightRadius: `var(--${kebabKey})`};
665
+ borderUtilities[`.rounded-bl-${originalKey}`] = {borderBottomLeftRadius: `var(--${kebabKey})`};
666
+ borderUtilities[`.rounded-br-${originalKey}`] = {borderBottomRightRadius: `var(--${kebabKey})`};
667
+ }
668
+ }
669
+
670
+ // ----------------------
671
+ // 🔲 Process Semantic Border Tokens
672
+ // ----------------------
673
+ console.log('🔄 Processing semantic border tokens...');
674
+
675
+ try {
676
+ const semanticBorders = load(files.semanticBorders);
677
+
678
+ // Process semantic border-width tokens
679
+ if (semanticBorders['border-width']) {
680
+ for (const [key, token] of Object.entries(semanticBorders['border-width'])) {
681
+ if (token?.$type === 'dimension') {
682
+ const raw = token.$value;
683
+ const resolved = raw.startsWith('{')
684
+ ? resolveReference(raw, flattenedBase)
685
+ : raw;
686
+
687
+ if (!resolved) {
688
+ console.warn(`Could not resolve reference: ${raw} for border-width-${key}`);
689
+ continue;
690
+ }
691
+
692
+ const kebabKey = `border-width-${kebab(key)}`;
693
+
694
+ // Store as CSS variable reference
695
+ semanticBorderWidth[kebab(key)] = `var(--${kebabKey})`;
696
+ cssVarMap[kebabKey] = resolved;
697
+
698
+ // Generate border width utility class
699
+ borderUtilities[`.border-${kebab(key)}`] = {borderWidth: `var(--${kebabKey})`};
700
+ }
701
+ }
702
+ }
703
+
704
+ // Process semantic border-radius tokens
705
+ if (semanticBorders['border-radius']) {
706
+ for (const [key, token] of Object.entries(semanticBorders['border-radius'])) {
707
+ if (token?.$type === 'dimension') {
708
+ const raw = token.$value;
709
+ const resolved = raw.startsWith('{')
710
+ ? resolveReference(raw, flattenedBase)
711
+ : raw;
712
+
713
+ if (!resolved) {
714
+ console.warn(`Could not resolve reference: ${raw} for border-radius-${key}`);
715
+ continue;
716
+ }
717
+
718
+ const kebabKey = `border-radius-${kebab(key)}`;
719
+
720
+ // Store as CSS variable reference
721
+ semanticBorderRadius[kebab(key)] = `var(--${kebabKey})`;
722
+ cssVarMap[kebabKey] = resolved;
723
+
724
+ // Generate border radius utility class
725
+ borderUtilities[`.rounded-${kebab(key)}`] = {borderRadius: `var(--${kebabKey})`};
726
+ }
727
+ }
728
+ }
729
+ } catch (error) {
730
+ console.error(`Error processing semantic borders: ${error.message}`);
731
+ }
732
+
733
+ // ----------------------
734
+ // 📝 Process Typography Tokens
735
+ // ----------------------
736
+ console.log('🔄 Processing typography tokens...');
737
+
738
+ const rawTypography = load(files.typography);
739
+ const typographyStyles = {};
740
+ const typographyUtilities = {};
741
+ const fontFamilies = {};
742
+ const fontWeights = {};
743
+ const fontSizes = {};
744
+ const lineHeights = {};
745
+ const letterSpacings = {};
746
+
747
+ function processTypographyTokens(obj, prefix = '') {
748
+ for (const [key, value] of Object.entries(obj)) {
749
+ const path = prefix ? `${prefix}.${key}` : key;
750
+
751
+ if (value?.$type === 'typography' && value?.$value) {
752
+ const typographyValue = value.$value;
753
+ const kebabKey = kebab(path);
754
+
755
+ // Create CSS variables for each typography property
756
+ // These are only added to cssVarMap for filtering later
757
+ for (const [propKey, propValue] of Object.entries(typographyValue)) {
758
+ const propName = `${kebabKey}-${propKey}`;
759
+ let resolvedValue = propValue;
760
+
761
+ // Resolve references if needed
762
+ if (typeof propValue === 'string' && propValue.startsWith('{')) {
763
+ resolvedValue = resolveReference(propValue, flattenedBase);
764
+ if (!resolvedValue) {
765
+ console.warn(`Could not resolve reference: ${propValue} for ${propName}`);
766
+ continue;
767
+ }
768
+ }
769
+
770
+ // Apply font weight mapping if this is a font weight property
771
+ if (propKey === 'fontWeight') {
772
+ resolvedValue = mapFontWeight(resolvedValue);
773
+ }
774
+
775
+ cssVarMap[propName] = resolvedValue;
776
+
777
+ // NOTE: We do NOT collect these into fontFamilies, fontWeights, etc.
778
+ // since we don't want them exported in tailwind.tokens.js
779
+ }
780
+
781
+ // Helper function to convert a value or reference to a CSS variable reference
782
+ function toCssVarReference(propValue, propKey) {
783
+ // If it's a reference like {typography.font-family.gulliver}
784
+ if (typeof propValue === 'string' && propValue.startsWith('{') && propValue.endsWith('}')) {
785
+ const refPath = propValue.replace(/[{}]/g, '');
786
+
787
+ // Find the corresponding primitive variable
788
+ // For font-family references
789
+ if (refPath.includes('font-family')) {
790
+ let fontName = refPath.split('.').pop();
791
+
792
+ // Special handling: "gulliver semibold" should map to "gulliver"
793
+ // since "Gulliver Semibold" is just the Gulliver font family
794
+ if (fontName.includes('gulliver') && fontName.includes('semibold')) {
795
+ fontName = 'gulliver';
796
+ }
797
+
798
+ const kebabName = kebab(fontName);
799
+ return `var(--font-family-${kebabName})`;
800
+ }
801
+ // For font-weight references
802
+ else if (refPath.includes('font-weight')) {
803
+ const weightName = refPath.split('.').pop();
804
+ const kebabName = kebab(weightName);
805
+ return `var(--font-weight-${kebabName})`;
806
+ }
807
+ // For letter-spacing references
808
+ else if (refPath.includes('letter-spacing')) {
809
+ const spacingName = refPath.split('.').pop();
810
+ const kebabName = kebab(spacingName);
811
+ return `var(--letter-spacing-${kebabName})`;
812
+ }
813
+ // For font-size references
814
+ else if (refPath.includes('font-size')) {
815
+ // Handle different font-size reference patterns
816
+ if (refPath.includes('font-size.heading.') || refPath.includes('font-size.body.') || refPath.includes('font-size.meta')) {
817
+ return `var(--${kebab(refPath)})`;
818
+ }
819
+ // Handle numeric font sizes like {typography.font-size.22}
820
+ const sizeName = refPath.split('.').pop();
821
+ return `var(--font-size-${sizeName})`;
822
+ }
823
+ }
824
+
825
+ // If it's not a reference, return the value as-is
826
+ return propValue;
827
+ }
828
+
829
+ // Create a utility class with CSS variable references to primitives
830
+ const utilityStyle = {
831
+ fontFamily: toCssVarReference(typographyValue.fontFamily, 'fontFamily'),
832
+ fontSize: toCssVarReference(typographyValue.fontSize, 'fontSize'),
833
+ fontWeight: toCssVarReference(typographyValue.fontWeight, 'fontWeight'),
834
+ lineHeight: typographyValue.lineHeight,
835
+ letterSpacing: toCssVarReference(typographyValue.letterSpacing, 'letterSpacing'),
836
+ textDecoration: typographyValue.textDecoration || 'none'
837
+ };
838
+
839
+ // Use font-variant for small-caps instead of text-transform
840
+ if (typographyValue.textTransform === 'small-caps') {
841
+ utilityStyle.fontVariant = 'all-small-caps';
842
+ } else if (typographyValue.textTransform && typographyValue.textTransform !== 'none') {
843
+ utilityStyle.textTransform = typographyValue.textTransform;
844
+ }
845
+
846
+ typographyUtilities[`.text-${kebabKey}`] = utilityStyle;
847
+ typographyStyles[kebabKey] = `var(--${kebabKey})`;
848
+ } else if (typeof value === 'object' && !value.$type && value !== null) {
849
+ processTypographyTokens(value, path);
850
+ }
851
+ }
852
+ }
853
+
854
+ processTypographyTokens(rawTypography);
855
+
856
+ // ----------------------
857
+ // 📱 Process Responsive Typography Tokens
858
+ // ----------------------
859
+ console.log('🔄 Processing responsive typography tokens...');
860
+
861
+ const responsiveTokens = {
862
+ mobile: {},
863
+ tablet: {},
864
+ desktop: {}
865
+ };
866
+
867
+ function processResponsiveTypography() {
868
+ // Process each breakpoint
869
+ for (const [breakpoint, file] of Object.entries(files.responsiveTypography)) {
870
+ try {
871
+ const rawTokens = load(file);
872
+
873
+ // Add these tokens to the base tokens for reference resolution
874
+ const combinedTokens = {
875
+ ...flattenedBase,
876
+ ...flatten(rawTokens)
877
+ };
878
+
879
+ // Extract typography tokens
880
+ for (const [key, value] of Object.entries(rawTokens)) {
881
+ if (typeof value === 'object') {
882
+ processResponsiveTypographySection(value, key, breakpoint, combinedTokens);
883
+ }
884
+ }
885
+
886
+ // Process font sizes specifically
887
+ if (rawTokens['font-size']) {
888
+ processFontSizes(rawTokens['font-size'], breakpoint);
889
+ }
890
+ } catch (error) {
891
+ console.error(`Error processing ${breakpoint} typography: ${error.message}`);
892
+ }
893
+ }
894
+ }
895
+
896
+ // Process font sizes from semantic typography tokens
897
+ function processFontSizes(fontSizes, breakpoint) {
898
+ // Process heading sizes
899
+ if (fontSizes.heading) {
900
+ Object.entries(fontSizes.heading).forEach(([size, value]) => {
901
+ if (value.$value) {
902
+ const varName = `font-size-heading-${size}`;
903
+ responsiveTokens[breakpoint][varName] = resolveReference(value.$value, flattenedBase) || value.$value;
904
+
905
+ // For desktop, also add to the main CSS var map (without breakpoint suffix)
906
+ if (breakpoint === 'desktop') {
907
+ cssVarMap[varName] = responsiveTokens[breakpoint][varName];
908
+ }
909
+ }
910
+ });
911
+ }
912
+
913
+ // Process body sizes
914
+ if (fontSizes.body) {
915
+ Object.entries(fontSizes.body).forEach(([size, value]) => {
916
+ if (value.$value) {
917
+ const varName = `font-size-body-${size}`;
918
+ responsiveTokens[breakpoint][varName] = resolveReference(value.$value, flattenedBase) || value.$value;
919
+
920
+ // For desktop, also add to the main CSS var map (without breakpoint suffix)
921
+ if (breakpoint === 'desktop') {
922
+ cssVarMap[varName] = responsiveTokens[breakpoint][varName];
923
+ }
924
+ }
925
+ });
926
+ }
927
+
928
+ // Process meta size
929
+ if (fontSizes.meta && fontSizes.meta.$value) {
930
+ const varName = `font-size-meta`;
931
+ responsiveTokens[breakpoint][varName] = resolveReference(fontSizes.meta.$value, flattenedBase) || fontSizes.meta.$value;
932
+
933
+ // For desktop, also add to the main CSS var map (without breakpoint suffix)
934
+ if (breakpoint === 'desktop') {
935
+ cssVarMap[varName] = responsiveTokens[breakpoint][varName];
936
+ }
937
+ }
938
+ }
939
+
940
+ function processResponsiveTypographySection(obj, prefix, breakpoint, combinedTokens) {
941
+ for (const [key, value] of Object.entries(obj)) {
942
+ const path = prefix ? `${prefix}.${key}` : key;
943
+
944
+ if (value?.$type === 'typography' && value?.$value) {
945
+ const typographyValue = value.$value;
946
+ const kebabKey = kebab(path);
947
+
948
+ // Create CSS variables for each typography property
949
+ for (const [propKey, propValue] of Object.entries(typographyValue)) {
950
+ const propName = `${kebabKey}-${propKey}`;
951
+ let resolvedValue = propValue;
952
+
953
+ // Resolve references if needed
954
+ if (typeof propValue === 'string' && propValue.startsWith('{')) {
955
+ resolvedValue = resolveReference(propValue, combinedTokens);
956
+ if (!resolvedValue) {
957
+ console.warn(`Could not resolve reference: ${propValue} for ${propName} in ${breakpoint}`);
958
+ continue;
959
+ }
960
+ }
961
+
962
+ // Apply font weight mapping if this is a font weight property
963
+ if (propKey === 'fontWeight') {
964
+ resolvedValue = mapFontWeight(resolvedValue);
965
+ }
966
+
967
+ // Store the responsive value
968
+ responsiveTokens[breakpoint][propName] = resolvedValue;
969
+ }
970
+ } else if (typeof value === 'object' && !value.$type && value !== null) {
971
+ processResponsiveTypographySection(value, path, breakpoint, combinedTokens);
972
+ }
973
+ }
974
+ }
975
+
976
+ processResponsiveTypography();
977
+
978
+ // ----------------------
979
+ // 📐 Text Alignment Utilities
980
+ // ----------------------
981
+ console.log('🔄 Adding text alignment utilities...');
982
+
983
+ const textAlignmentUtilities = {
984
+ '.text-left': {textAlign: 'left'},
985
+ '.text-center': {textAlign: 'center'},
986
+ '.text-right': {textAlign: 'right'},
987
+ '.text-justify': {textAlign: 'justify'}
988
+ };
989
+
990
+ // ----------------------
991
+ // ✏️ Write tailwind.tokens.js
992
+ // ----------------------
993
+ console.log('📝 Writing tailwind.tokens.js...');
994
+
995
+ const jsOutput = `
996
+ // Generated from design tokens - DO NOT EDIT DIRECTLY
997
+ // Last generated: ${new Date().toISOString()}
998
+
999
+ export const primitiveColors = ${JSON.stringify(primitiveColors, null, 2)};
1000
+ export const semanticColors = ${JSON.stringify(semanticColors, null, 2)};
1001
+ export const componentColors = ${JSON.stringify(componentColors, null, 2)};
1002
+ export const spacing = ${JSON.stringify(spacing, null, 2)};
1003
+ export const typographyStyles = ${JSON.stringify(typographyStyles, null, 2)};
1004
+ export const primitiveFontFamilies = ${JSON.stringify(primitiveFontFamilies, null, 2)};
1005
+ export const primitiveFontSizes = ${JSON.stringify(primitiveFontSizes, null, 2)};
1006
+ export const primitiveFontWeights = ${JSON.stringify(primitiveFontWeights, null, 2)};
1007
+ export const primitiveLetterSpacings = ${JSON.stringify(primitiveLetterSpacings, null, 2)};
1008
+ export const primitiveBorderWidth = ${JSON.stringify(primitiveBorderWidth, null, 2)};
1009
+ export const primitiveBorderRadius = ${JSON.stringify(primitiveBorderRadius, null, 2)};
1010
+ export const semanticBorderWidth = ${JSON.stringify(semanticBorderWidth, null, 2)};
1011
+ export const semanticBorderRadius = ${JSON.stringify(semanticBorderRadius, null, 2)};
1012
+ export const primitiveBoxShadows = ${JSON.stringify(primitiveBoxShadows, null, 2)};
1013
+
1014
+ `.trim();
1015
+
1016
+ fs.writeFileSync('./tailwind.tokens.js', jsOutput);
1017
+ console.log('✅ tailwind.tokens.js written.');
1018
+
1019
+ // ----------------------
1020
+ // 🎨 Write tokens.css
1021
+ // ----------------------
1022
+ console.log('📝 Writing tokens.css...');
1023
+
1024
+ // Filter out individual typography tokens (we only want font-size-* tokens in the CSS)
1025
+ const filteredCssVarMap = Object.fromEntries(
1026
+ Object.entries(cssVarMap).filter(([key]) => {
1027
+ // Exclude individual typography property tokens (article-*, body-*, heading-*, meta-*, menu-*)
1028
+ // but keep font-size-* tokens
1029
+ if (key.startsWith('font-size-')) return true;
1030
+
1031
+ const isTypographyToken = key.match(/^(article|body|heading|meta|menu)-.*-(fontFamily|fontSize|fontWeight|letterSpacing|lineHeight|textTransform|textDecoration)$/);
1032
+ if (isTypographyToken) return false;
1033
+
1034
+ return true;
1035
+ })
1036
+ );
1037
+
1038
+ const cssOutput = `
1039
+ /**
1040
+ * Design System Tokens
1041
+ * Generated from design tokens - DO NOT EDIT DIRECTLY
1042
+ * Last generated: ${new Date().toISOString()}
1043
+ */
1044
+
1045
+ :root {
1046
+ ${Object.entries(filteredCssVarMap)
1047
+ .map(([key, val]) => ` --${key}: ${val};`)
1048
+ .join('\n')}
1049
+ }
1050
+
1051
+ /* Dark Mode Colors */
1052
+ @media (prefers-color-scheme: dark) {
1053
+ :root {
1054
+ ${Object.entries(darkModeColors)
1055
+ .map(([key, val]) => ` --${key}: ${val};`)
1056
+ .join('\n')}
1057
+ }
1058
+ }
1059
+
1060
+ /* Mobile Typography (Default) */
1061
+ @media (max-width: 767px) {
1062
+ :root {
1063
+ ${Object.entries(responsiveTokens.mobile)
1064
+ .map(([key, val]) => ` --${key}: ${val};`)
1065
+ .join('\n')}
1066
+ }
1067
+ }
1068
+
1069
+ /* Tablet Typography (768px - 1023px) */
1070
+ @media (min-width: 768px) and (max-width: 1023px) {
1071
+ :root {
1072
+ ${Object.entries(responsiveTokens.tablet)
1073
+ .map(([key, val]) => ` --${key}: ${val};`)
1074
+ .join('\n')}
1075
+ }
1076
+ }
1077
+
1078
+ /* Desktop Typography (1024px+) is defined in the main :root block */
1079
+ `.trim();
1080
+
1081
+ fs.writeFileSync('./src/assets/css/tokens.css', cssOutput);
1082
+ console.log('✅ tokens.css written.');
1083
+
1084
+ // ----------------------
1085
+ // ⚡ Write tokens.utilities.js plugin
1086
+ // ----------------------
1087
+ console.log('📝 Writing tokens.utilities.js...');
1088
+
1089
+ const utilOutput = `
1090
+ /**
1091
+ * Tailwind CSS Utilities Plugin
1092
+ * Generated from design tokens - DO NOT EDIT DIRECTLY
1093
+ * Last generated: ${new Date().toISOString()}
1094
+ */
1095
+
1096
+ export default function ({ addUtilities }) {
1097
+ addUtilities({
1098
+ ...${JSON.stringify(utilityClasses, null, 2)},
1099
+ ...${JSON.stringify(spacingUtilities, null, 2)},
1100
+ ...${JSON.stringify(borderUtilities, null, 2)},
1101
+ ...${JSON.stringify(typographyUtilities, null, 2)},
1102
+ ...${JSON.stringify(shadowUtilities, null, 2)},
1103
+ ...${JSON.stringify(textAlignmentUtilities, null, 2)}
1104
+ });
1105
+ }
1106
+ `.trim();
1107
+
1108
+ fs.writeFileSync('./tokens.utilities.js', utilOutput);
1109
+ console.log('✅ Tailwind plugin written: tokens.utilities.js');
1110
+ console.log('🎉 Token processing complete!');
@@ -0,0 +1,37 @@
1
+ import { rollup } from 'rollup';
2
+ import dts from 'rollup-plugin-dts';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ async function buildTypes() {
10
+ const bundle = await rollup({
11
+ input: path.resolve(__dirname, '../src/index.ts'),
12
+ plugins: [
13
+ dts({
14
+ compilerOptions: {
15
+ baseUrl: path.resolve(__dirname, '..'),
16
+ paths: {
17
+ "@atoms/*": ["src/atoms/*"],
18
+ "@molecules/*": ["src/molecules/*"],
19
+ "@components/*": ["src/components/*"],
20
+ "@experimental/*": ["src/experimental/*"],
21
+ "@hooks/*": ["src/hooks/*"],
22
+ "@helpers/*": ["src/helpers/*"]
23
+ }
24
+ }
25
+ })
26
+ ],
27
+ });
28
+
29
+ await bundle.write({
30
+ file: path.resolve(__dirname, '../dist/index.d.ts'),
31
+ format: 'es',
32
+ });
33
+
34
+ console.log('✔ Type declarations built');
35
+ }
36
+
37
+ buildTypes();
@@ -0,0 +1,55 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const alleenWaarschuwen = process.argv.includes('--warn');
6
+
7
+ const agent = process.env.npm_config_user_agent || '';
8
+ const manager = agent.split('/')[0];
9
+ const pad = (p) => {
10
+ try {
11
+ return realpathSync(p);
12
+ } catch {
13
+ return p;
14
+ }
15
+ };
16
+ const pakketRoot = pad(resolve(dirname(fileURLToPath(import.meta.url)), '..'));
17
+ const startMap = process.env.INIT_CWD ? pad(process.env.INIT_CWD) : pakketRoot;
18
+
19
+ const alsDependency = startMap !== pakketRoot;
20
+
21
+ if (!agent || manager === 'yarn' || alsDependency || process.env.ALLOW_NPM === '1') {
22
+ process.exit(0);
23
+ }
24
+
25
+ const rood = '\x1b[31m';
26
+ const geel = '\x1b[33m';
27
+ const vet = '\x1b[1m';
28
+ const uit = '\x1b[0m';
29
+
30
+ if (alleenWaarschuwen) {
31
+ console.error(
32
+ `${geel} Let op: dit project gebruikt yarn. Draai dit liever als ` +
33
+ `${vet}yarn ${process.env.npm_lifecycle_event?.replace(/^pre/, '') || '<script>'}${uit}${geel}.${uit}\n`
34
+ );
35
+ process.exit(0);
36
+ }
37
+
38
+ console.error(`
39
+ ${rood}${vet} Dit project gebruikt yarn, geen ${manager}.${uit}
40
+
41
+ Gebruik in plaats daarvan:
42
+
43
+ ${vet}yarn install${uit} in plaats van ${manager} install
44
+ ${vet}yarn add <pakket>${uit} in plaats van ${manager} install <pakket>
45
+ ${vet}yarn add --dev <pakket>${uit} in plaats van ${manager} install --save-dev <pakket>
46
+
47
+ ${geel}Waarom:${uit} ${manager} install herschrijft yarn.lock in zijn geheel, ook de
48
+ pakketten waar je niets aan verandert. CI draait op yarn install
49
+ --frozen-lockfile, dus zo'n herschrijving kan de resolutie van de hele
50
+ dependencytree verschuiven.
51
+
52
+ Moet je toch echt ${manager} gebruiken: ALLOW_NPM=1 ${manager} install
53
+ `);
54
+
55
+ process.exit(1);
@@ -0,0 +1,34 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const rootDir = path.resolve(__dirname, '..');
8
+
9
+ // Files to copy
10
+ const filesToCopy = [
11
+ 'tailwind.config.js',
12
+ 'tailwind.tokens.js',
13
+ 'tokens.utilities.js'
14
+ ];
15
+
16
+ // Ensure dist directory exists
17
+ if (!fs.existsSync(path.join(rootDir, 'dist'))) {
18
+ fs.mkdirSync(path.join(rootDir, 'dist'));
19
+ }
20
+
21
+ // Copy each file
22
+ filesToCopy.forEach(file => {
23
+ const sourcePath = path.join(rootDir, file);
24
+ const destPath = path.join(rootDir, 'dist', file);
25
+
26
+ try {
27
+ fs.copyFileSync(sourcePath, destPath);
28
+ console.log(`✓ Copied ${file} to dist/`);
29
+ } catch (error) {
30
+ console.error(`Error copying ${file}:`, error.message);
31
+ }
32
+ });
33
+
34
+ console.log('✓ Tailwind files copied to dist/');
@@ -0,0 +1,58 @@
1
+ import { optimize } from 'svgo';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ // Standaard alle iconen; geef desgewenst losse paden of mappen mee als argument.
10
+ const targets = process.argv.slice(2).length
11
+ ? process.argv.slice(2)
12
+ : [path.resolve(__dirname, '../src/atoms/basicAtoms/Icon/icons')];
13
+
14
+ const config = {
15
+ multipass: true,
16
+ js2svg: { indent: 2, pretty: false },
17
+ // Let op: viewBox moet blijven staan (svgo 4 laat 'm met rust), want Icon.tsx
18
+ // zet width/height op de svg en zonder viewBox schaalt het icoon niet mee.
19
+ plugins: [
20
+ {
21
+ name: 'preset-default',
22
+ params: {
23
+ overrides: {
24
+ // Houd de coördinaten op 2 decimalen; op 16x16 is dat ruim genoeg.
25
+ convertPathData: { floatPrecision: 2 },
26
+ cleanupNumericValues: { floatPrecision: 2 },
27
+ },
28
+ },
29
+ },
30
+ ],
31
+ };
32
+
33
+ function collect(target) {
34
+ const stat = fs.statSync(target);
35
+ if (stat.isFile()) return target.endsWith('.svg') ? [target] : [];
36
+ return fs.readdirSync(target).flatMap((entry) => collect(path.join(target, entry)));
37
+ }
38
+
39
+ let before = 0;
40
+ let after = 0;
41
+ let changed = 0;
42
+
43
+ for (const file of targets.flatMap(collect)) {
44
+ const source = fs.readFileSync(file, 'utf8');
45
+ const { data } = optimize(source, { ...config, path: file });
46
+
47
+ before += Buffer.byteLength(source);
48
+ after += Buffer.byteLength(data);
49
+
50
+ if (data !== source) {
51
+ fs.writeFileSync(file, data);
52
+ changed++;
53
+ }
54
+ }
55
+
56
+ const saved = before - after;
57
+ const percentage = before ? Math.round((saved / before) * 100) : 0;
58
+ console.log(`svgo: ${changed} bestanden aangepast, ${before} → ${after} bytes (-${percentage}%)`);