@shohojdhara/atomix 0.3.2 → 0.3.4

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 (84) hide show
  1. package/README.md +58 -21
  2. package/dist/atomix.css +96 -121
  3. package/dist/atomix.min.css +3 -3
  4. package/dist/index.d.ts +7937 -7765
  5. package/dist/index.esm.js +3677 -4031
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +3648 -3952
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/package.json +44 -16
  12. package/scripts/atomix-cli.js +1764 -0
  13. package/scripts/build-themes.js +208 -0
  14. package/scripts/cli/interactive-init.js +520 -0
  15. package/scripts/cli/migration-tools.js +603 -0
  16. package/scripts/cli/theme-bridge.js +129 -0
  17. package/scripts/cli/token-manager.js +519 -0
  18. package/scripts/sync-theme-config.js +309 -0
  19. package/src/components/Button/Button.tsx +36 -1
  20. package/src/components/List/ListGroup.tsx +1 -2
  21. package/src/components/Popover/Popover.tsx +2 -2
  22. package/src/components/Tooltip/Tooltip.stories.tsx +49 -12
  23. package/src/components/Tooltip/Tooltip.tsx +32 -58
  24. package/src/lib/composables/useTooltip.ts +285 -0
  25. package/src/lib/config/index.ts +275 -0
  26. package/src/lib/config/loader.ts +105 -0
  27. package/src/lib/constants/cssVariables.ts +390 -0
  28. package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +151 -0
  29. package/src/lib/hooks/index.ts +19 -0
  30. package/src/lib/hooks/useComponentCustomization.ts +175 -0
  31. package/src/lib/index.ts +14 -1
  32. package/src/lib/patterns/__tests__/slots.test.ts +108 -0
  33. package/src/lib/patterns/index.ts +35 -0
  34. package/src/lib/patterns/slots.tsx +421 -0
  35. package/src/lib/theme/composeTheme.ts +0 -5
  36. package/src/lib/theme/config/index.ts +1 -1
  37. package/src/lib/theme/config/loader.ts +75 -41
  38. package/src/lib/theme/config/types.ts +21 -7
  39. package/src/lib/theme/config/validator.ts +1 -1
  40. package/src/lib/theme/constants.ts +12 -2
  41. package/src/lib/theme/createTheme.ts +2 -135
  42. package/src/lib/theme/createThemeFromConfig.ts +132 -0
  43. package/src/lib/theme/cssVariableMapper.ts +261 -0
  44. package/src/lib/theme/devtools/CLI.ts +161 -76
  45. package/src/lib/theme/devtools/Comparator.tsx +343 -0
  46. package/src/lib/theme/devtools/IMPROVEMENTS.md +429 -0
  47. package/src/lib/theme/devtools/Inspector.tsx +21 -6
  48. package/src/lib/theme/devtools/LiveEditor.tsx +393 -0
  49. package/src/lib/theme/devtools/README.md +433 -0
  50. package/src/lib/theme/devtools/index.ts +12 -11
  51. package/src/lib/theme/generateCSSVariables.ts +79 -38
  52. package/src/lib/theme/index.ts +45 -246
  53. package/src/lib/theme/runtime/ThemeApplicator.ts +252 -0
  54. package/src/lib/theme/runtime/ThemeManager.test.ts +17 -1
  55. package/src/lib/theme/runtime/ThemeManager.ts +7 -7
  56. package/src/lib/theme/themeUtils.ts +27 -5
  57. package/src/lib/theme/types.ts +59 -1
  58. package/src/lib/theme-tools.ts +125 -0
  59. package/src/lib/types/components.ts +260 -72
  60. package/src/lib/types/partProps.ts +426 -0
  61. package/src/lib/utils/__tests__/componentUtils.test.ts +144 -0
  62. package/src/lib/utils/componentUtils.ts +163 -0
  63. package/src/lib/utils/index.ts +17 -57
  64. package/src/styles/01-settings/_settings.colors.scss +10 -10
  65. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  66. package/src/styles/01-settings/_settings.tooltip.scss +1 -1
  67. package/src/styles/03-generic/_generated-root.css +5 -0
  68. package/src/styles/06-components/_components.navbar.scss +12 -5
  69. package/src/styles/06-components/_components.tooltip.scss +31 -81
  70. package/src/themes/README.md +442 -0
  71. package/src/themes/themes.config.js +35 -0
  72. package/src/lib/theme/errors.test.ts +0 -207
  73. package/src/lib/theme/generators/CSSGenerator.ts +0 -311
  74. package/src/lib/theme/generators/ConfigGenerator.ts +0 -287
  75. package/src/lib/theme/generators/TypeGenerator.ts +0 -228
  76. package/src/lib/theme/generators/index.ts +0 -21
  77. package/src/lib/theme/monitoring/ThemeAnalytics.ts +0 -409
  78. package/src/lib/theme/monitoring/index.ts +0 -17
  79. package/src/lib/theme/overrides/ComponentOverrides.ts +0 -243
  80. package/src/lib/theme/overrides/index.ts +0 -15
  81. package/src/lib/theme/studio/ThemeStudio.tsx +0 -312
  82. package/src/lib/theme/studio/index.ts +0 -8
  83. package/src/lib/theme/whitelabel/WhiteLabelManager.ts +0 -364
  84. package/src/lib/theme/whitelabel/index.ts +0 -13
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Theme CLI Bridge
3
+ *
4
+ * Bridges the TypeScript theme devtools CLI with the main JavaScript CLI
5
+ */
6
+
7
+ import { spawn } from 'child_process';
8
+ import { join, dirname } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import chalk from 'chalk';
11
+ import ora from 'ora';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ /**
17
+ * Execute theme CLI command
18
+ */
19
+ export async function executeThemeCommand(command, args = [], options = {}) {
20
+ const spinner = options.spinner || ora(`Running theme ${command}...`).start();
21
+
22
+ try {
23
+ // Path to the theme CLI
24
+ const themeCliPath = join(__dirname, '../../src/lib/theme/devtools/CLI.ts');
25
+
26
+ // Use ts-node to execute TypeScript CLI
27
+ const tsNodePath = join(__dirname, '../../node_modules/.bin/ts-node');
28
+
29
+ return new Promise((resolve, reject) => {
30
+ const child = spawn(tsNodePath, [themeCliPath, command, ...args], {
31
+ stdio: 'inherit',
32
+ cwd: process.cwd(),
33
+ });
34
+
35
+ child.on('close', (code) => {
36
+ if (code === 0) {
37
+ spinner.succeed(chalk.green(`✓ Theme ${command} completed`));
38
+ resolve();
39
+ } else {
40
+ spinner.fail(chalk.red(`✗ Theme ${command} failed`));
41
+ reject(new Error(`Theme command failed with code ${code}`));
42
+ }
43
+ });
44
+
45
+ child.on('error', (error) => {
46
+ spinner.fail(chalk.red(`✗ Theme ${command} failed`));
47
+ reject(error);
48
+ });
49
+ });
50
+ } catch (error) {
51
+ spinner.fail(chalk.red(`✗ Theme ${command} failed`));
52
+ throw error;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Create theme CLI bridge
58
+ */
59
+ export function createThemeCLIBridge() {
60
+ return {
61
+ /**
62
+ * Validate theme configuration
63
+ */
64
+ async validate(options = {}) {
65
+ const args = [];
66
+ if (options.config) args.push('--config', options.config);
67
+ if (options.strict) args.push('--strict');
68
+
69
+ return executeThemeCommand('validate', args, options);
70
+ },
71
+
72
+ /**
73
+ * List all themes
74
+ */
75
+ async list(options = {}) {
76
+ return executeThemeCommand('list', [], options);
77
+ },
78
+
79
+ /**
80
+ * Inspect a theme
81
+ */
82
+ async inspect(themeName, options = {}) {
83
+ const args = ['--theme', themeName];
84
+ if (options.json) args.push('--json');
85
+
86
+ return executeThemeCommand('inspect', args, options);
87
+ },
88
+
89
+ /**
90
+ * Compare two themes
91
+ */
92
+ async compare(theme1, theme2, options = {}) {
93
+ const args = ['--theme1', theme1, '--theme2', theme2];
94
+
95
+ return executeThemeCommand('compare', args, options);
96
+ },
97
+
98
+ /**
99
+ * Export a theme
100
+ */
101
+ async export(themeName, options = {}) {
102
+ const args = ['--theme', themeName];
103
+ if (options.output) args.push('--output', options.output);
104
+
105
+ return executeThemeCommand('export', args, options);
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Check if theme CLI is available
112
+ */
113
+ export async function isThemeCLIAvailable() {
114
+ try {
115
+ const themeCliPath = join(__dirname, '../../src/lib/theme/devtools/CLI.ts');
116
+ const { access } = await import('fs/promises');
117
+ await access(themeCliPath);
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ export default {
125
+ createThemeCLIBridge,
126
+ executeThemeCommand,
127
+ isThemeCLIAvailable,
128
+ };
129
+
@@ -0,0 +1,519 @@
1
+ /**
2
+ * Design Token Manager for Atomix Design System
3
+ */
4
+
5
+ import { readFile, writeFile, readdir } from 'fs/promises';
6
+ import { join, basename } from 'path';
7
+ import { existsSync } from 'fs';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import boxen from 'boxen';
11
+
12
+ /**
13
+ * Token categories in the design system
14
+ */
15
+ const tokenCategories = {
16
+ colors: {
17
+ path: 'src/styles/01-settings/_settings.colors.scss',
18
+ prefix: '--atomix-color',
19
+ description: 'Color palette tokens'
20
+ },
21
+ typography: {
22
+ path: 'src/styles/01-settings/_settings.typography.scss',
23
+ prefix: '--atomix-font',
24
+ description: 'Typography scale tokens'
25
+ },
26
+ spacing: {
27
+ path: 'src/styles/01-settings/_settings.spacing.scss',
28
+ prefix: '--atomix-space',
29
+ description: 'Spacing scale tokens'
30
+ },
31
+ radius: {
32
+ path: 'src/styles/01-settings/_settings.radius.scss',
33
+ prefix: '--atomix-radius',
34
+ description: 'Border radius tokens'
35
+ },
36
+ shadows: {
37
+ path: 'src/styles/01-settings/_settings.shadows.scss',
38
+ prefix: '--atomix-shadow',
39
+ description: 'Box shadow tokens'
40
+ },
41
+ breakpoints: {
42
+ path: 'src/styles/01-settings/_settings.breakpoints.scss',
43
+ prefix: '--atomix-breakpoint',
44
+ description: 'Responsive breakpoint tokens'
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Extract tokens from SCSS file
50
+ */
51
+ async function extractTokensFromFile(filePath) {
52
+ if (!existsSync(filePath)) {
53
+ return null;
54
+ }
55
+
56
+ const content = await readFile(filePath, 'utf8');
57
+ const tokens = {};
58
+
59
+ // Extract SCSS variables
60
+ const scssVarPattern = /\$([a-z-]+):\s*([^;!]+)(?:\s*!default)?;/gi;
61
+ let match;
62
+ while ((match = scssVarPattern.exec(content)) !== null) {
63
+ tokens[`$${match[1]}`] = {
64
+ type: 'scss',
65
+ name: match[1],
66
+ value: match[2].trim(),
67
+ line: content.substring(0, match.index).split('\n').length
68
+ };
69
+ }
70
+
71
+ // Extract CSS custom properties
72
+ const cssVarPattern = /--(atomix-[a-z-]+):\s*([^;]+);/gi;
73
+ while ((match = cssVarPattern.exec(content)) !== null) {
74
+ tokens[`--${match[1]}`] = {
75
+ type: 'css',
76
+ name: match[1],
77
+ value: match[2].trim(),
78
+ line: content.substring(0, match.index).split('\n').length
79
+ };
80
+ }
81
+
82
+ return tokens;
83
+ }
84
+
85
+ /**
86
+ * List all design tokens
87
+ */
88
+ export async function listTokens(category = null) {
89
+ const spinner = ora('Loading design tokens...').start();
90
+
91
+ try {
92
+ const results = {};
93
+
94
+ // Get tokens from specified category or all categories
95
+ const categories = category
96
+ ? [category]
97
+ : Object.keys(tokenCategories);
98
+
99
+ for (const cat of categories) {
100
+ if (!tokenCategories[cat]) {
101
+ spinner.warn(chalk.yellow(`Unknown category: ${cat}`));
102
+ continue;
103
+ }
104
+
105
+ const { path, description } = tokenCategories[cat];
106
+ const fullPath = join(process.cwd(), path);
107
+ const tokens = await extractTokensFromFile(fullPath);
108
+
109
+ if (tokens) {
110
+ results[cat] = {
111
+ description,
112
+ path: path,
113
+ tokens: tokens,
114
+ count: Object.keys(tokens).length
115
+ };
116
+ }
117
+ }
118
+
119
+ spinner.stop();
120
+
121
+ // Display results
122
+ if (Object.keys(results).length === 0) {
123
+ console.log(chalk.yellow('\n⚠️ No design tokens found'));
124
+ console.log(chalk.gray('Make sure you are in an Atomix project directory'));
125
+ return;
126
+ }
127
+
128
+ console.log(chalk.bold.cyan('\n📐 Design Tokens\n'));
129
+
130
+ for (const [category, data] of Object.entries(results)) {
131
+ console.log(boxen(
132
+ chalk.bold(category.toUpperCase()) + '\n' +
133
+ chalk.gray(data.description) + '\n\n' +
134
+ chalk.cyan(`Tokens: ${data.count}\n`) +
135
+ chalk.gray(`File: ${data.path}`),
136
+ {
137
+ padding: 1,
138
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
139
+ borderStyle: 'round',
140
+ borderColor: 'gray'
141
+ }
142
+ ));
143
+
144
+ // Show first 5 tokens as examples
145
+ const tokenEntries = Object.entries(data.tokens).slice(0, 5);
146
+ tokenEntries.forEach(([name, info]) => {
147
+ const value = info.value.length > 30
148
+ ? info.value.substring(0, 30) + '...'
149
+ : info.value;
150
+ console.log(` ${chalk.green(name)}: ${chalk.white(value)}`);
151
+ });
152
+
153
+ if (Object.keys(data.tokens).length > 5) {
154
+ console.log(chalk.gray(` ... and ${Object.keys(data.tokens).length - 5} more\n`));
155
+ } else {
156
+ console.log();
157
+ }
158
+ }
159
+
160
+ return results;
161
+
162
+ } catch (error) {
163
+ spinner.fail(chalk.red('Failed to load tokens'));
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Validate design tokens
170
+ */
171
+ export async function validateTokens(options = {}) {
172
+ const spinner = ora('Validating design tokens...').start();
173
+
174
+ try {
175
+ const issues = [];
176
+ const warnings = [];
177
+ let totalTokens = 0;
178
+
179
+ for (const [category, config] of Object.entries(tokenCategories)) {
180
+ const fullPath = join(process.cwd(), config.path);
181
+
182
+ if (!existsSync(fullPath)) {
183
+ issues.push({
184
+ category,
185
+ issue: 'Token file missing',
186
+ file: config.path,
187
+ fix: `Create file: ${config.path}`
188
+ });
189
+ continue;
190
+ }
191
+
192
+ const tokens = await extractTokensFromFile(fullPath);
193
+ if (tokens) {
194
+ totalTokens += Object.keys(tokens).length;
195
+
196
+ // Check for hardcoded values
197
+ const content = await readFile(fullPath, 'utf8');
198
+
199
+ // Check for hardcoded colors
200
+ if (category === 'colors') {
201
+ const hardcodedColors = content.match(/#[0-9a-fA-F]{3,8}(?![0-9a-fA-F])/g);
202
+ if (hardcodedColors) {
203
+ const uniqueColors = [...new Set(hardcodedColors)];
204
+ warnings.push({
205
+ category,
206
+ issue: `Found ${uniqueColors.length} hardcoded color values`,
207
+ values: uniqueColors.slice(0, 3),
208
+ fix: 'Use color tokens or CSS custom properties'
209
+ });
210
+ }
211
+ }
212
+
213
+ // Check for hardcoded pixel values
214
+ if (category === 'spacing' || category === 'typography') {
215
+ const hardcodedPixels = content.match(/\d+px/g);
216
+ if (hardcodedPixels) {
217
+ const uniquePixels = [...new Set(hardcodedPixels)];
218
+ warnings.push({
219
+ category,
220
+ issue: `Found ${uniquePixels.length} hardcoded pixel values`,
221
+ values: uniquePixels.slice(0, 3),
222
+ fix: 'Use spacing tokens or rem units'
223
+ });
224
+ }
225
+ }
226
+
227
+ // Check for missing !default flags
228
+ const scssVars = content.match(/\$[a-z-]+:\s*[^;]+;/gi);
229
+ const defaultFlags = content.match(/!default/g);
230
+ if (scssVars && (!defaultFlags || defaultFlags.length < scssVars.length)) {
231
+ warnings.push({
232
+ category,
233
+ issue: 'Some SCSS variables missing !default flag',
234
+ fix: 'Add !default to allow theme overrides'
235
+ });
236
+ }
237
+
238
+ // Check naming conventions
239
+ for (const [name, info] of Object.entries(tokens)) {
240
+ if (info.type === 'css' && !name.startsWith(config.prefix)) {
241
+ warnings.push({
242
+ category,
243
+ issue: `Token "${name}" doesn't follow naming convention`,
244
+ fix: `Should start with "${config.prefix}"`
245
+ });
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ spinner.stop();
252
+
253
+ // Display validation results
254
+ console.log(chalk.bold.cyan('\n🔍 Token Validation Report\n'));
255
+
256
+ if (issues.length === 0 && warnings.length === 0) {
257
+ console.log(boxen(
258
+ chalk.bold.green('✅ All tokens valid!\n\n') +
259
+ chalk.gray(`Total tokens: ${totalTokens}\n`) +
260
+ chalk.gray('All naming conventions followed\n') +
261
+ chalk.gray('No hardcoded values found'),
262
+ {
263
+ padding: 1,
264
+ borderStyle: 'round',
265
+ borderColor: 'green'
266
+ }
267
+ ));
268
+ } else {
269
+ if (issues.length > 0) {
270
+ console.log(chalk.bold.red(`❌ Issues (${issues.length}):\n`));
271
+ issues.forEach((issue, i) => {
272
+ console.log(chalk.red(` ${i + 1}. [${issue.category}] ${issue.issue}`));
273
+ console.log(chalk.gray(` File: ${issue.file}`));
274
+ console.log(chalk.yellow(` Fix: ${issue.fix}\n`));
275
+ });
276
+ }
277
+
278
+ if (warnings.length > 0) {
279
+ console.log(chalk.bold.yellow(`⚠️ Warnings (${warnings.length}):\n`));
280
+ warnings.forEach((warning, i) => {
281
+ console.log(chalk.yellow(` ${i + 1}. [${warning.category}] ${warning.issue}`));
282
+ if (warning.values) {
283
+ console.log(chalk.gray(` Examples: ${warning.values.join(', ')}`));
284
+ }
285
+ console.log(chalk.cyan(` Fix: ${warning.fix}\n`));
286
+ });
287
+ }
288
+
289
+ console.log(chalk.gray('─'.repeat(50)));
290
+ console.log(chalk.cyan('\n💡 Run with --fix to attempt automatic fixes'));
291
+ }
292
+
293
+ return { issues, warnings, totalTokens };
294
+
295
+ } catch (error) {
296
+ spinner.fail(chalk.red('Validation failed'));
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Export tokens to different formats
303
+ */
304
+ export async function exportTokens(format = 'json', outputPath = null) {
305
+ const spinner = ora(`Exporting tokens as ${format.toUpperCase()}...`).start();
306
+
307
+ try {
308
+ const allTokens = {};
309
+
310
+ // Collect all tokens
311
+ for (const [category, config] of Object.entries(tokenCategories)) {
312
+ const fullPath = join(process.cwd(), config.path);
313
+ const tokens = await extractTokensFromFile(fullPath);
314
+
315
+ if (tokens) {
316
+ allTokens[category] = {};
317
+ for (const [name, info] of Object.entries(tokens)) {
318
+ allTokens[category][name] = info.value;
319
+ }
320
+ }
321
+ }
322
+
323
+ let output;
324
+ let filename;
325
+
326
+ switch (format.toLowerCase()) {
327
+ case 'json':
328
+ output = JSON.stringify(allTokens, null, 2);
329
+ filename = 'atomix-tokens.json';
330
+ break;
331
+
332
+ case 'css':
333
+ output = ':root {\n';
334
+ for (const [category, tokens] of Object.entries(allTokens)) {
335
+ output += ` /* ${category} */\n`;
336
+ for (const [name, value] of Object.entries(tokens)) {
337
+ if (name.startsWith('--')) {
338
+ output += ` ${name}: ${value};\n`;
339
+ }
340
+ }
341
+ }
342
+ output += '}';
343
+ filename = 'atomix-tokens.css';
344
+ break;
345
+
346
+ case 'scss':
347
+ output = '// Atomix Design Tokens\n\n';
348
+ for (const [category, tokens] of Object.entries(allTokens)) {
349
+ output += `// ${category}\n`;
350
+ for (const [name, value] of Object.entries(tokens)) {
351
+ if (name.startsWith('$')) {
352
+ output += `${name}: ${value} !default;\n`;
353
+ }
354
+ }
355
+ output += '\n';
356
+ }
357
+ filename = 'atomix-tokens.scss';
358
+ break;
359
+
360
+ case 'js':
361
+ case 'javascript':
362
+ output = '// Atomix Design Tokens\n';
363
+ output += 'export const tokens = ';
364
+ output += JSON.stringify(allTokens, null, 2);
365
+ output += ';\n\nexport default tokens;';
366
+ filename = 'atomix-tokens.js';
367
+ break;
368
+
369
+ case 'ts':
370
+ case 'typescript':
371
+ output = '// Atomix Design Tokens\n\n';
372
+ output += 'export interface AtomixTokens {\n';
373
+ for (const category of Object.keys(allTokens)) {
374
+ output += ` ${category}: Record<string, string>;\n`;
375
+ }
376
+ output += '}\n\n';
377
+ output += 'export const tokens: AtomixTokens = ';
378
+ output += JSON.stringify(allTokens, null, 2);
379
+ output += ';\n\nexport default tokens;';
380
+ filename = 'atomix-tokens.ts';
381
+ break;
382
+
383
+ default:
384
+ throw new Error(`Unsupported format: ${format}`);
385
+ }
386
+
387
+ // Write to file
388
+ const finalPath = outputPath || filename;
389
+ await writeFile(finalPath, output, 'utf8');
390
+
391
+ spinner.succeed(chalk.green(`✓ Exported tokens to ${finalPath}`));
392
+
393
+ // Show summary
394
+ const categoryCount = Object.keys(allTokens).length;
395
+ const tokenCount = Object.values(allTokens).reduce(
396
+ (sum, cat) => sum + Object.keys(cat).length,
397
+ 0
398
+ );
399
+
400
+ console.log(chalk.gray(`\n Categories: ${categoryCount}`));
401
+ console.log(chalk.gray(` Total tokens: ${tokenCount}`));
402
+ console.log(chalk.gray(` Format: ${format.toUpperCase()}`));
403
+
404
+ return { path: finalPath, tokens: allTokens };
405
+
406
+ } catch (error) {
407
+ spinner.fail(chalk.red('Export failed'));
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Import tokens from file
414
+ */
415
+ export async function importTokens(filePath, options = {}) {
416
+ const spinner = ora('Importing design tokens...').start();
417
+
418
+ try {
419
+ if (!existsSync(filePath)) {
420
+ throw new Error(`File not found: ${filePath}`);
421
+ }
422
+
423
+ const content = await readFile(filePath, 'utf8');
424
+ const extension = filePath.split('.').pop().toLowerCase();
425
+
426
+ let tokens;
427
+
428
+ // Parse tokens based on file type
429
+ switch (extension) {
430
+ case 'json':
431
+ tokens = JSON.parse(content);
432
+ break;
433
+
434
+ case 'js':
435
+ case 'ts':
436
+ // Extract tokens from JS/TS export
437
+ const match = content.match(/export\s+(?:const|default)\s+\w*\s*=\s*({[\s\S]*})/);
438
+ if (match) {
439
+ // Simple eval (be careful in production)
440
+ tokens = eval(`(${match[1]})`);
441
+ } else {
442
+ throw new Error('Could not parse JavaScript/TypeScript tokens');
443
+ }
444
+ break;
445
+
446
+ default:
447
+ throw new Error(`Unsupported file type: ${extension}`);
448
+ }
449
+
450
+ spinner.text = 'Updating token files...';
451
+
452
+ // Update token files
453
+ for (const [category, categoryTokens] of Object.entries(tokens)) {
454
+ if (!tokenCategories[category]) {
455
+ console.warn(chalk.yellow(`\n⚠️ Unknown category: ${category} (skipped)`));
456
+ continue;
457
+ }
458
+
459
+ const config = tokenCategories[category];
460
+ const fullPath = join(process.cwd(), config.path);
461
+
462
+ // Generate SCSS content
463
+ let scssContent = `// ${config.description}\n`;
464
+ scssContent += `// Imported from: ${basename(filePath)}\n`;
465
+ scssContent += `// Date: ${new Date().toISOString()}\n\n`;
466
+
467
+ for (const [name, value] of Object.entries(categoryTokens)) {
468
+ if (name.startsWith('$')) {
469
+ scssContent += `${name}: ${value} !default;\n`;
470
+ } else if (name.startsWith('--')) {
471
+ scssContent += `:root {\n ${name}: ${value};\n}\n`;
472
+ } else {
473
+ // Convert to SCSS variable
474
+ scssContent += `$${name}: ${value} !default;\n`;
475
+ }
476
+ }
477
+
478
+ if (options.dryRun) {
479
+ console.log(chalk.yellow(`\n Would update: ${config.path}`));
480
+ console.log(chalk.gray(scssContent.substring(0, 200) + '...'));
481
+ } else {
482
+ await writeFile(fullPath, scssContent, 'utf8');
483
+ console.log(chalk.green(` ✓ Updated ${config.path}`));
484
+ }
485
+ }
486
+
487
+ spinner.succeed(chalk.green('✓ Tokens imported successfully'));
488
+
489
+ // Show summary
490
+ const categoryCount = Object.keys(tokens).length;
491
+ const tokenCount = Object.values(tokens).reduce(
492
+ (sum, cat) => sum + Object.keys(cat).length,
493
+ 0
494
+ );
495
+
496
+ console.log(chalk.gray(`\n Categories imported: ${categoryCount}`));
497
+ console.log(chalk.gray(` Total tokens: ${tokenCount}`));
498
+
499
+ if (!options.dryRun) {
500
+ console.log(chalk.cyan('\n💡 Next steps:'));
501
+ console.log(chalk.gray(' 1. Review the updated token files'));
502
+ console.log(chalk.gray(' 2. Rebuild your themes: atomix build-theme <theme>'));
503
+ console.log(chalk.gray(' 3. Test your components with new tokens'));
504
+ }
505
+
506
+ return { tokens, categoryCount, tokenCount };
507
+
508
+ } catch (error) {
509
+ spinner.fail(chalk.red('Import failed'));
510
+ throw error;
511
+ }
512
+ }
513
+
514
+ export default {
515
+ listTokens,
516
+ validateTokens,
517
+ exportTokens,
518
+ importTokens
519
+ };