@shohojdhara/atomix 0.3.12 → 0.3.14

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.
Files changed (155) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -0
  3. package/dist/atomix.css +101 -88
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +5 -15258
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/charts.d.ts +1 -1
  8. package/dist/charts.js +17 -19
  9. package/dist/charts.js.map +1 -1
  10. package/dist/core.d.ts +41 -11
  11. package/dist/core.js +55 -41
  12. package/dist/core.js.map +1 -1
  13. package/dist/forms.d.ts +28 -11
  14. package/dist/forms.js +25 -24
  15. package/dist/forms.js.map +1 -1
  16. package/dist/heavy.d.ts +1 -1
  17. package/dist/heavy.js +32 -25
  18. package/dist/heavy.js.map +1 -1
  19. package/dist/index.d.ts +122 -46
  20. package/dist/index.esm.js +865 -200
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/index.js +870 -204
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1 -1
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/theme.d.ts +27 -2
  27. package/dist/theme.js +721 -108
  28. package/dist/theme.js.map +1 -1
  29. package/package.json +1 -1
  30. package/scripts/atomix-cli.js +610 -1111
  31. package/scripts/cli/component-generator.js +610 -0
  32. package/scripts/cli/documentation-sync.js +542 -0
  33. package/scripts/cli/interactive-init.js +84 -288
  34. package/scripts/cli/mappings.js +211 -0
  35. package/scripts/cli/migration-tools.js +95 -288
  36. package/scripts/cli/template-manager.js +107 -0
  37. package/scripts/cli/templates/README.md +123 -0
  38. package/scripts/cli/templates/composable-templates.js +149 -0
  39. package/scripts/cli/templates/config-templates.js +126 -0
  40. package/scripts/cli/templates/index.js +95 -0
  41. package/scripts/cli/templates/project-templates.js +214 -0
  42. package/scripts/cli/templates/react-templates.js +261 -0
  43. package/scripts/cli/templates/scss-templates.js +156 -0
  44. package/scripts/cli/templates/storybook-templates.js +236 -0
  45. package/scripts/cli/templates/testing-templates.js +45 -0
  46. package/scripts/cli/templates/token-templates.js +447 -0
  47. package/scripts/cli/templates/types-templates.js +133 -0
  48. package/scripts/cli/templates-original-backup.js +1655 -0
  49. package/scripts/cli/templates.js +35 -0
  50. package/scripts/cli/templates_backup.js +684 -0
  51. package/scripts/cli/theme-bridge.js +20 -14
  52. package/scripts/cli/token-manager.js +150 -77
  53. package/scripts/cli/utils.js +37 -25
  54. package/src/components/Accordion/Accordion.stories.tsx +5 -5
  55. package/src/components/Accordion/Accordion.test.tsx +57 -0
  56. package/src/components/Accordion/Accordion.tsx +4 -0
  57. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +41 -44
  58. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -1
  59. package/src/components/AtomixGlass/stories/Examples.stories.tsx +37 -37
  60. package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -2
  61. package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -51
  62. package/src/components/Avatar/Avatar.stories.tsx +26 -26
  63. package/src/components/Badge/Badge.stories.tsx +31 -31
  64. package/src/components/Badge/Badge.test.tsx +51 -0
  65. package/src/components/Badge/Badge.tsx +20 -1
  66. package/src/components/Block/Block.stories.tsx +5 -5
  67. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  68. package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
  69. package/src/components/Button/Button.stories.tsx +13 -13
  70. package/src/components/Button/Button.tsx +4 -4
  71. package/src/components/Button/ButtonGroup.stories.tsx +2 -2
  72. package/src/components/Button/README.md +5 -0
  73. package/src/components/Callout/Callout.stories.tsx +11 -11
  74. package/src/components/Callout/Callout.test.tsx +10 -10
  75. package/src/components/Callout/Callout.tsx +7 -7
  76. package/src/components/Callout/README.md +9 -8
  77. package/src/components/Card/Card.tsx +2 -2
  78. package/src/components/Chart/Chart.stories.tsx +6 -6
  79. package/src/components/Chart/Chart.tsx +1 -1
  80. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +1 -1
  81. package/src/components/DataTable/DataTable.tsx +14 -12
  82. package/src/components/DatePicker/DatePicker.stories.tsx +6 -6
  83. package/src/components/Dropdown/Dropdown.stories.tsx +4 -4
  84. package/src/components/Form/Checkbox.stories.tsx +3 -3
  85. package/src/components/Form/Checkbox.tsx +4 -2
  86. package/src/components/Form/Form.stories.tsx +3 -3
  87. package/src/components/Form/FormGroup.stories.tsx +1 -1
  88. package/src/components/Form/Input.stories.tsx +28 -16
  89. package/src/components/Form/Input.test.tsx +59 -0
  90. package/src/components/Form/Input.tsx +97 -95
  91. package/src/components/Form/Radio.stories.tsx +94 -94
  92. package/src/components/Form/Radio.tsx +2 -2
  93. package/src/components/Form/Select.stories.tsx +4 -4
  94. package/src/components/Form/Select.tsx +2 -2
  95. package/src/components/Form/Textarea.stories.tsx +22 -7
  96. package/src/components/Form/Textarea.test.tsx +45 -0
  97. package/src/components/Form/Textarea.tsx +88 -86
  98. package/src/components/List/List.stories.tsx +2 -2
  99. package/src/components/Modal/Modal.stories.tsx +4 -4
  100. package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
  101. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  102. package/src/components/Navigation/README.md +1 -1
  103. package/src/components/Pagination/Pagination.stories.tsx +5 -2
  104. package/src/components/Pagination/Pagination.tsx +1 -1
  105. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
  106. package/src/components/Popover/Popover.stories.tsx +1 -1
  107. package/src/components/ProductReview/ProductReview.tsx +1 -1
  108. package/src/components/Progress/Progress.tsx +46 -46
  109. package/src/components/Rating/Rating.stories.tsx +4 -4
  110. package/src/components/Rating/Rating.tsx +8 -8
  111. package/src/components/Slider/Slider.stories.tsx +63 -63
  112. package/src/components/Spinner/Spinner.stories.tsx +2 -2
  113. package/src/components/Spinner/Spinner.test.tsx +35 -0
  114. package/src/components/Spinner/Spinner.tsx +9 -2
  115. package/src/components/Testimonial/Testimonial.stories.tsx +1 -1
  116. package/src/components/Toggle/Toggle.stories.tsx +32 -9
  117. package/src/components/Toggle/Toggle.test.tsx +91 -0
  118. package/src/components/Toggle/Toggle.tsx +44 -27
  119. package/src/components/Tooltip/Tooltip.tsx +1 -1
  120. package/src/layouts/Grid/Grid.stories.tsx +49 -49
  121. package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
  122. package/src/lib/composables/useAccordion.ts +12 -3
  123. package/src/lib/composables/useBreadcrumb.ts +2 -2
  124. package/src/lib/composables/useCallout.ts +7 -7
  125. package/src/lib/composables/useNavbar.ts +1 -1
  126. package/src/lib/constants/components.ts +1 -1
  127. package/src/lib/storybook/InteractiveDemo.tsx +113 -0
  128. package/src/lib/storybook/PreviewContainer.tsx +36 -0
  129. package/src/lib/storybook/VariantsGrid.tsx +21 -0
  130. package/src/lib/storybook/index.ts +3 -0
  131. package/src/lib/theme/core/createThemeObject.ts +9 -5
  132. package/src/lib/theme/devtools/CLI.ts +155 -0
  133. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
  134. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
  135. package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
  136. package/src/lib/theme/devtools/index.ts +3 -0
  137. package/src/lib/theme/errors/errors.ts +8 -0
  138. package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
  139. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
  140. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
  141. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
  142. package/src/lib/theme/utils/index.ts +1 -0
  143. package/src/lib/theme/utils/themeValidation.ts +501 -0
  144. package/src/lib/theme-tools.ts +32 -3
  145. package/src/lib/types/components.ts +81 -26
  146. package/src/lib/utils/themeNaming.ts +1 -1
  147. package/src/styles/06-components/_components.atomix-glass.scss +14 -15
  148. package/src/styles/06-components/_components.callout.scss +29 -33
  149. package/src/styles/06-components/_index.scss +1 -1
  150. package/src/styles/99-utilities/_utilities.display.scss +14 -3
  151. package/src/styles/99-utilities/_utilities.flex.scss +10 -10
  152. package/src/styles/99-utilities/_utilities.text.scss +28 -8
  153. package/scripts/cli/__tests__/cli-commands.test.js +0 -204
  154. package/scripts/cli/__tests__/utils.test.js +0 -201
  155. package/scripts/cli/__tests__/vitest.config.js +0 -26
@@ -18,20 +18,26 @@ const __dirname = dirname(__filename);
18
18
  */
19
19
  export async function executeThemeCommand(command, args = [], options = {}) {
20
20
  const spinner = options.spinner || ora(`Running theme ${command}...`).start();
21
-
21
+
22
22
  try {
23
23
  // Path to the theme CLI
24
24
  const themeCliPath = join(__dirname, '../../src/lib/theme/devtools/CLI.ts');
25
-
25
+
26
26
  // Use ts-node to execute TypeScript CLI
27
27
  const tsNodePath = join(__dirname, '../../node_modules/.bin/ts-node');
28
-
28
+
29
29
  return new Promise((resolve, reject) => {
30
- const child = spawn(tsNodePath, [themeCliPath, command, ...args], {
30
+ const child = spawn(tsNodePath, [
31
+ '--esm',
32
+ '--experimental-specifier-resolution=node',
33
+ themeCliPath,
34
+ command,
35
+ ...args
36
+ ], {
31
37
  stdio: 'inherit',
32
38
  cwd: process.cwd(),
33
39
  });
34
-
40
+
35
41
  child.on('close', (code) => {
36
42
  if (code === 0) {
37
43
  spinner.succeed(chalk.green(`✓ Theme ${command} completed`));
@@ -41,7 +47,7 @@ export async function executeThemeCommand(command, args = [], options = {}) {
41
47
  reject(new Error(`Theme command failed with code ${code}`));
42
48
  }
43
49
  });
44
-
50
+
45
51
  child.on('error', (error) => {
46
52
  spinner.fail(chalk.red(`✗ Theme ${command} failed`));
47
53
  reject(error);
@@ -65,43 +71,43 @@ export function createThemeCLIBridge() {
65
71
  const args = [];
66
72
  if (options.config) args.push('--config', options.config);
67
73
  if (options.strict) args.push('--strict');
68
-
74
+
69
75
  return executeThemeCommand('validate', args, options);
70
76
  },
71
-
77
+
72
78
  /**
73
79
  * List all themes
74
80
  */
75
81
  async list(options = {}) {
76
82
  return executeThemeCommand('list', [], options);
77
83
  },
78
-
84
+
79
85
  /**
80
86
  * Inspect a theme
81
87
  */
82
88
  async inspect(themeName, options = {}) {
83
89
  const args = ['--theme', themeName];
84
90
  if (options.json) args.push('--json');
85
-
91
+
86
92
  return executeThemeCommand('inspect', args, options);
87
93
  },
88
-
94
+
89
95
  /**
90
96
  * Compare two themes
91
97
  */
92
98
  async compare(theme1, theme2, options = {}) {
93
99
  const args = ['--theme1', theme1, '--theme2', theme2];
94
-
100
+
95
101
  return executeThemeCommand('compare', args, options);
96
102
  },
97
-
103
+
98
104
  /**
99
105
  * Export a theme
100
106
  */
101
107
  async export(themeName, options = {}) {
102
108
  const args = ['--theme', themeName];
103
109
  if (options.output) args.push('--output', options.output);
104
-
110
+
105
111
  return executeThemeCommand('export', args, options);
106
112
  },
107
113
  };
@@ -52,10 +52,10 @@ async function extractTokensFromFile(filePath) {
52
52
  if (!existsSync(filePath)) {
53
53
  return null;
54
54
  }
55
-
55
+
56
56
  const content = await readFile(filePath, 'utf8');
57
57
  const tokens = {};
58
-
58
+
59
59
  // Extract SCSS variables
60
60
  const scssVarPattern = /\$([a-z-]+):\s*([^;!]+)(?:\s*!default)?;/gi;
61
61
  let match;
@@ -67,7 +67,7 @@ async function extractTokensFromFile(filePath) {
67
67
  line: content.substring(0, match.index).split('\n').length
68
68
  };
69
69
  }
70
-
70
+
71
71
  // Extract CSS custom properties
72
72
  const cssVarPattern = /--(atomix-[a-z-]+):\s*([^;]+);/gi;
73
73
  while ((match = cssVarPattern.exec(content)) !== null) {
@@ -78,7 +78,7 @@ async function extractTokensFromFile(filePath) {
78
78
  line: content.substring(0, match.index).split('\n').length
79
79
  };
80
80
  }
81
-
81
+
82
82
  return tokens;
83
83
  }
84
84
 
@@ -87,25 +87,25 @@ async function extractTokensFromFile(filePath) {
87
87
  */
88
88
  export async function listTokens(category = null) {
89
89
  const spinner = ora('Loading design tokens...').start();
90
-
90
+
91
91
  try {
92
92
  const results = {};
93
-
93
+
94
94
  // Get tokens from specified category or all categories
95
- const categories = category
96
- ? [category]
95
+ const categories = category
96
+ ? [category]
97
97
  : Object.keys(tokenCategories);
98
-
98
+
99
99
  for (const cat of categories) {
100
100
  if (!tokenCategories[cat]) {
101
101
  spinner.warn(chalk.yellow(`Unknown category: ${cat}`));
102
102
  continue;
103
103
  }
104
-
104
+
105
105
  const { path, description } = tokenCategories[cat];
106
106
  const fullPath = join(process.cwd(), path);
107
107
  const tokens = await extractTokensFromFile(fullPath);
108
-
108
+
109
109
  if (tokens) {
110
110
  results[cat] = {
111
111
  description,
@@ -115,18 +115,18 @@ export async function listTokens(category = null) {
115
115
  };
116
116
  }
117
117
  }
118
-
118
+
119
119
  spinner.stop();
120
-
120
+
121
121
  // Display results
122
122
  if (Object.keys(results).length === 0) {
123
123
  console.log(chalk.yellow('\n⚠️ No design tokens found'));
124
124
  console.log(chalk.gray('Make sure you are in an Atomix project directory'));
125
125
  return;
126
126
  }
127
-
127
+
128
128
  console.log(chalk.bold.cyan('\n📐 Design Tokens\n'));
129
-
129
+
130
130
  for (const [category, data] of Object.entries(results)) {
131
131
  console.log(boxen(
132
132
  chalk.bold(category.toUpperCase()) + '\n' +
@@ -140,25 +140,25 @@ export async function listTokens(category = null) {
140
140
  borderColor: 'gray'
141
141
  }
142
142
  ));
143
-
143
+
144
144
  // Show first 5 tokens as examples
145
145
  const tokenEntries = Object.entries(data.tokens).slice(0, 5);
146
146
  tokenEntries.forEach(([name, info]) => {
147
- const value = info.value.length > 30
148
- ? info.value.substring(0, 30) + '...'
147
+ const value = info.value.length > 30
148
+ ? info.value.substring(0, 30) + '...'
149
149
  : info.value;
150
150
  console.log(` ${chalk.green(name)}: ${chalk.white(value)}`);
151
151
  });
152
-
152
+
153
153
  if (Object.keys(data.tokens).length > 5) {
154
154
  console.log(chalk.gray(` ... and ${Object.keys(data.tokens).length - 5} more\n`));
155
155
  } else {
156
156
  console.log();
157
157
  }
158
158
  }
159
-
159
+
160
160
  return results;
161
-
161
+
162
162
  } catch (error) {
163
163
  spinner.fail(chalk.red('Failed to load tokens'));
164
164
  throw error;
@@ -170,15 +170,15 @@ export async function listTokens(category = null) {
170
170
  */
171
171
  export async function validateTokens(options = {}) {
172
172
  const spinner = ora('Validating design tokens...').start();
173
-
173
+
174
174
  try {
175
175
  const issues = [];
176
176
  const warnings = [];
177
177
  let totalTokens = 0;
178
-
178
+
179
179
  for (const [category, config] of Object.entries(tokenCategories)) {
180
180
  const fullPath = join(process.cwd(), config.path);
181
-
181
+
182
182
  if (!existsSync(fullPath)) {
183
183
  issues.push({
184
184
  category,
@@ -188,14 +188,14 @@ export async function validateTokens(options = {}) {
188
188
  });
189
189
  continue;
190
190
  }
191
-
191
+
192
192
  const tokens = await extractTokensFromFile(fullPath);
193
193
  if (tokens) {
194
194
  totalTokens += Object.keys(tokens).length;
195
-
195
+
196
196
  // Check for hardcoded values
197
197
  const content = await readFile(fullPath, 'utf8');
198
-
198
+
199
199
  // Check for hardcoded colors
200
200
  if (category === 'colors') {
201
201
  const hardcodedColors = content.match(/#[0-9a-fA-F]{3,8}(?![0-9a-fA-F])/g);
@@ -209,7 +209,7 @@ export async function validateTokens(options = {}) {
209
209
  });
210
210
  }
211
211
  }
212
-
212
+
213
213
  // Check for hardcoded pixel values
214
214
  if (category === 'spacing' || category === 'typography') {
215
215
  const hardcodedPixels = content.match(/\d+px/g);
@@ -223,7 +223,7 @@ export async function validateTokens(options = {}) {
223
223
  });
224
224
  }
225
225
  }
226
-
226
+
227
227
  // Check for missing !default flags
228
228
  const scssVars = content.match(/\$[a-z-]+:\s*[^;]+;/gi);
229
229
  const defaultFlags = content.match(/!default/g);
@@ -234,7 +234,7 @@ export async function validateTokens(options = {}) {
234
234
  fix: 'Add !default to allow theme overrides'
235
235
  });
236
236
  }
237
-
237
+
238
238
  // Check naming conventions
239
239
  for (const [name, info] of Object.entries(tokens)) {
240
240
  if (info.type === 'css' && !name.startsWith(config.prefix)) {
@@ -247,12 +247,12 @@ export async function validateTokens(options = {}) {
247
247
  }
248
248
  }
249
249
  }
250
-
250
+
251
251
  spinner.stop();
252
-
252
+
253
253
  // Display validation results
254
254
  console.log(chalk.bold.cyan('\n🔍 Token Validation Report\n'));
255
-
255
+
256
256
  if (issues.length === 0 && warnings.length === 0) {
257
257
  console.log(boxen(
258
258
  chalk.bold.green('✅ All tokens valid!\n\n') +
@@ -274,7 +274,7 @@ export async function validateTokens(options = {}) {
274
274
  console.log(chalk.yellow(` Fix: ${issue.fix}\n`));
275
275
  });
276
276
  }
277
-
277
+
278
278
  if (warnings.length > 0) {
279
279
  console.log(chalk.bold.yellow(`⚠️ Warnings (${warnings.length}):\n`));
280
280
  warnings.forEach((warning, i) => {
@@ -285,13 +285,13 @@ export async function validateTokens(options = {}) {
285
285
  console.log(chalk.cyan(` Fix: ${warning.fix}\n`));
286
286
  });
287
287
  }
288
-
288
+
289
289
  console.log(chalk.gray('─'.repeat(50)));
290
290
  console.log(chalk.cyan('\n💡 Run with --fix to attempt automatic fixes'));
291
291
  }
292
-
292
+
293
293
  return { issues, warnings, totalTokens };
294
-
294
+
295
295
  } catch (error) {
296
296
  spinner.fail(chalk.red('Validation failed'));
297
297
  throw error;
@@ -303,15 +303,15 @@ export async function validateTokens(options = {}) {
303
303
  */
304
304
  export async function exportTokens(format = 'json', outputPath = null) {
305
305
  const spinner = ora(`Exporting tokens as ${format.toUpperCase()}...`).start();
306
-
306
+
307
307
  try {
308
308
  const allTokens = {};
309
-
309
+
310
310
  // Collect all tokens
311
311
  for (const [category, config] of Object.entries(tokenCategories)) {
312
312
  const fullPath = join(process.cwd(), config.path);
313
313
  const tokens = await extractTokensFromFile(fullPath);
314
-
314
+
315
315
  if (tokens) {
316
316
  allTokens[category] = {};
317
317
  for (const [name, info] of Object.entries(tokens)) {
@@ -319,16 +319,16 @@ export async function exportTokens(format = 'json', outputPath = null) {
319
319
  }
320
320
  }
321
321
  }
322
-
322
+
323
323
  let output;
324
324
  let filename;
325
-
325
+
326
326
  switch (format.toLowerCase()) {
327
327
  case 'json':
328
328
  output = JSON.stringify(allTokens, null, 2);
329
329
  filename = 'atomix-tokens.json';
330
330
  break;
331
-
331
+
332
332
  case 'css':
333
333
  output = ':root {\n';
334
334
  for (const [category, tokens] of Object.entries(allTokens)) {
@@ -342,7 +342,7 @@ export async function exportTokens(format = 'json', outputPath = null) {
342
342
  output += '}';
343
343
  filename = 'atomix-tokens.css';
344
344
  break;
345
-
345
+
346
346
  case 'scss':
347
347
  output = '// Atomix Design Tokens\n\n';
348
348
  for (const [category, tokens] of Object.entries(allTokens)) {
@@ -356,7 +356,7 @@ export async function exportTokens(format = 'json', outputPath = null) {
356
356
  }
357
357
  filename = 'atomix-tokens.scss';
358
358
  break;
359
-
359
+
360
360
  case 'js':
361
361
  case 'javascript':
362
362
  output = '// Atomix Design Tokens\n';
@@ -365,7 +365,7 @@ export async function exportTokens(format = 'json', outputPath = null) {
365
365
  output += ';\n\nexport default tokens;';
366
366
  filename = 'atomix-tokens.js';
367
367
  break;
368
-
368
+
369
369
  case 'ts':
370
370
  case 'typescript':
371
371
  output = '// Atomix Design Tokens\n\n';
@@ -379,30 +379,30 @@ export async function exportTokens(format = 'json', outputPath = null) {
379
379
  output += ';\n\nexport default tokens;';
380
380
  filename = 'atomix-tokens.ts';
381
381
  break;
382
-
382
+
383
383
  default:
384
384
  throw new Error(`Unsupported format: ${format}`);
385
385
  }
386
-
386
+
387
387
  // Write to file
388
388
  const finalPath = outputPath || filename;
389
389
  await writeFile(finalPath, output, 'utf8');
390
-
390
+
391
391
  spinner.succeed(chalk.green(`✓ Exported tokens to ${finalPath}`));
392
-
392
+
393
393
  // Show summary
394
394
  const categoryCount = Object.keys(allTokens).length;
395
395
  const tokenCount = Object.values(allTokens).reduce(
396
- (sum, cat) => sum + Object.keys(cat).length,
396
+ (sum, cat) => sum + Object.keys(cat).length,
397
397
  0
398
398
  );
399
-
399
+
400
400
  console.log(chalk.gray(`\n Categories: ${categoryCount}`));
401
401
  console.log(chalk.gray(` Total tokens: ${tokenCount}`));
402
402
  console.log(chalk.gray(` Format: ${format.toUpperCase()}`));
403
-
403
+
404
404
  return { path: finalPath, tokens: allTokens };
405
-
405
+
406
406
  } catch (error) {
407
407
  spinner.fail(chalk.red('Export failed'));
408
408
  throw error;
@@ -414,23 +414,23 @@ export async function exportTokens(format = 'json', outputPath = null) {
414
414
  */
415
415
  export async function importTokens(filePath, options = {}) {
416
416
  const spinner = ora('Importing design tokens...').start();
417
-
417
+
418
418
  try {
419
419
  if (!existsSync(filePath)) {
420
420
  throw new Error(`File not found: ${filePath}`);
421
421
  }
422
-
422
+
423
423
  const content = await readFile(filePath, 'utf8');
424
424
  const extension = filePath.split('.').pop().toLowerCase();
425
-
425
+
426
426
  let tokens;
427
-
427
+
428
428
  // Parse tokens based on file type
429
429
  switch (extension) {
430
430
  case 'json':
431
431
  tokens = JSON.parse(content);
432
432
  break;
433
-
433
+
434
434
  case 'js':
435
435
  case 'ts':
436
436
  // Parse tokens safely without eval
@@ -440,10 +440,10 @@ export async function importTokens(filePath, options = {}) {
440
440
  if (jsonMatch) {
441
441
  // Extract the object literal and clean it up
442
442
  let objectStr = jsonMatch[1];
443
-
443
+
444
444
  // Remove trailing semicolon if present
445
445
  objectStr = objectStr.replace(/;\s*$/, '');
446
-
446
+
447
447
  // Try direct JSON parsing first
448
448
  try {
449
449
  tokens = JSON.parse(objectStr);
@@ -457,7 +457,7 @@ export async function importTokens(filePath, options = {}) {
457
457
  .replace(/(\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
458
458
  // Handle trailing commas
459
459
  .replace(/,(\s*[}\]])/g, '$1');
460
-
460
+
461
461
  tokens = JSON.parse(objectStr);
462
462
  }
463
463
  } else {
@@ -467,28 +467,28 @@ export async function importTokens(filePath, options = {}) {
467
467
  throw new Error(`Could not parse JavaScript/TypeScript tokens: ${error.message}`);
468
468
  }
469
469
  break;
470
-
470
+
471
471
  default:
472
472
  throw new Error(`Unsupported file type: ${extension}`);
473
473
  }
474
-
474
+
475
475
  spinner.text = 'Updating token files...';
476
-
476
+
477
477
  // Update token files
478
478
  for (const [category, categoryTokens] of Object.entries(tokens)) {
479
479
  if (!tokenCategories[category]) {
480
480
  console.warn(chalk.yellow(`\n⚠️ Unknown category: ${category} (skipped)`));
481
481
  continue;
482
482
  }
483
-
483
+
484
484
  const config = tokenCategories[category];
485
485
  const fullPath = join(process.cwd(), config.path);
486
-
486
+
487
487
  // Generate SCSS content
488
488
  let scssContent = `// ${config.description}\n`;
489
489
  scssContent += `// Imported from: ${basename(filePath)}\n`;
490
490
  scssContent += `// Date: ${new Date().toISOString()}\n\n`;
491
-
491
+
492
492
  for (const [name, value] of Object.entries(categoryTokens)) {
493
493
  if (name.startsWith('$')) {
494
494
  scssContent += `${name}: ${value} !default;\n`;
@@ -499,7 +499,7 @@ export async function importTokens(filePath, options = {}) {
499
499
  scssContent += `$${name}: ${value} !default;\n`;
500
500
  }
501
501
  }
502
-
502
+
503
503
  if (options.dryRun) {
504
504
  console.log(chalk.yellow(`\n Would update: ${config.path}`));
505
505
  console.log(chalk.gray(scssContent.substring(0, 200) + '...'));
@@ -508,37 +508,110 @@ export async function importTokens(filePath, options = {}) {
508
508
  console.log(chalk.green(` ✓ Updated ${config.path}`));
509
509
  }
510
510
  }
511
-
511
+
512
512
  spinner.succeed(chalk.green('✓ Tokens imported successfully'));
513
-
513
+
514
514
  // Show summary
515
515
  const categoryCount = Object.keys(tokens).length;
516
516
  const tokenCount = Object.values(tokens).reduce(
517
- (sum, cat) => sum + Object.keys(cat).length,
517
+ (sum, cat) => sum + Object.keys(cat).length,
518
518
  0
519
519
  );
520
-
520
+
521
521
  console.log(chalk.gray(`\n Categories imported: ${categoryCount}`));
522
522
  console.log(chalk.gray(` Total tokens: ${tokenCount}`));
523
-
523
+
524
524
  if (!options.dryRun) {
525
525
  console.log(chalk.cyan('\n💡 Next steps:'));
526
526
  console.log(chalk.gray(' 1. Review the updated token files'));
527
527
  console.log(chalk.gray(' 2. Rebuild your themes: atomix build-theme <theme>'));
528
528
  console.log(chalk.gray(' 3. Test your components with new tokens'));
529
529
  }
530
-
530
+
531
531
  return { tokens, categoryCount, tokenCount };
532
-
532
+
533
533
  } catch (error) {
534
534
  spinner.fail(chalk.red('Import failed'));
535
535
  throw error;
536
536
  }
537
537
  }
538
538
 
539
+ /**
540
+ * Automatically fix issues in token files
541
+ */
542
+ export async function fixTokens(options = {}) {
543
+ const spinner = ora('Attempting to fix design tokens...').start();
544
+ let totalFixed = 0;
545
+
546
+ try {
547
+ for (const [category, config] of Object.entries(tokenCategories)) {
548
+ const fullPath = join(process.cwd(), config.path);
549
+
550
+ if (!existsSync(fullPath)) continue;
551
+
552
+ let content = await readFile(fullPath, 'utf8');
553
+ let originalContent = content;
554
+ let fileFixedCount = 0;
555
+
556
+ // 1. Fix missing !default flags
557
+ const defaultFixed = content.replace(/(\$[a-z-]+:\s*[^;!]+)(;)/gi, (match, p1, p2) => {
558
+ fileFixedCount++;
559
+ return `${p1} !default${p2}`;
560
+ });
561
+ content = defaultFixed;
562
+
563
+ // 2. Fix hardcoded colors (exact matches for basic tokens)
564
+ if (category === 'colors') {
565
+ const colorMappings = {
566
+ '#7AFFD7': 'var(--atomix-color-primary)',
567
+ '#FF5733': 'var(--atomix-color-secondary)',
568
+ '#000000': 'var(--atomix-color-background)',
569
+ '#FFFFFF': 'var(--atomix-color-text)'
570
+ };
571
+
572
+ for (const [hex, token] of Object.entries(colorMappings)) {
573
+ const regex = new RegExp(hex, 'gi');
574
+ if (regex.test(content)) {
575
+ content = content.replace(regex, token);
576
+ fileFixedCount++;
577
+ }
578
+ }
579
+ }
580
+
581
+ // 3. Fix hardcoded spacing (rem preferred)
582
+ if (category === 'spacing') {
583
+ content = content.replace(/(\d+)px/g, (match, pixels) => {
584
+ const px = parseInt(pixels);
585
+ if (px % 4 === 0) {
586
+ fileFixedCount++;
587
+ return `var(--atomix-space-${px / 4})`;
588
+ }
589
+ return match;
590
+ });
591
+ }
592
+
593
+ if (content !== originalContent) {
594
+ if (!options.dryRun) {
595
+ await writeFile(fullPath, content, 'utf8');
596
+ }
597
+ totalFixed += fileFixedCount;
598
+ console.log(chalk.green(` ✓ Fixed ${fileFixedCount} issues in ${config.path}`));
599
+ }
600
+ }
601
+
602
+ spinner.succeed(chalk.green(`✓ Successfully fixed ${totalFixed} issues!`));
603
+ return { totalFixed };
604
+
605
+ } catch (error) {
606
+ spinner.fail(chalk.red('Fix operation failed'));
607
+ throw error;
608
+ }
609
+ }
610
+
539
611
  export default {
540
612
  listTokens,
541
613
  validateTokens,
542
614
  exportTokens,
543
- importTokens
615
+ importTokens,
616
+ fixTokens
544
617
  };