@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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Atomix CLI - Enhanced Version
4
+ * Atomix CLI
5
5
  * Design System Development Tools
6
6
  */
7
7
 
@@ -20,17 +20,18 @@ import chokidar from 'chokidar';
20
20
  import inquirer from 'inquirer';
21
21
  import boxen from 'boxen';
22
22
  import { runInitWizard } from './cli/interactive-init.js';
23
- import {
24
- migrateTailwind,
25
- migrateBootstrap,
26
- migrateSCSSVariables,
27
- displayMigrationReport
23
+ import {
24
+ migrateTailwind,
25
+ migrateBootstrap,
26
+ migrateSCSSVariables,
27
+ displayMigrationReport
28
28
  } from './cli/migration-tools.js';
29
29
  import {
30
30
  listTokens,
31
31
  validateTokens,
32
32
  exportTokens,
33
- importTokens
33
+ importTokens,
34
+ fixTokens
34
35
  } from './cli/token-manager.js';
35
36
  import { createThemeCLIBridge } from './cli/theme-bridge.js';
36
37
  import {
@@ -39,8 +40,32 @@ import {
39
40
  validateThemeName,
40
41
  sanitizeInput,
41
42
  fileExists,
42
- isDebug as checkDebugMode
43
+ isDebug as checkDebugMode,
44
+ checkNodeVersion,
45
+ AtomixCLIError
43
46
  } from './cli/utils.js';
47
+ import {
48
+ componentTemplates,
49
+ generateColorTokens,
50
+ generateSpacingTokens,
51
+ generateTypographyTokens,
52
+ generateShadowTokens,
53
+ generateRadiusTokens,
54
+ generateAnimationTokens
55
+ } from './cli/templates.js';
56
+ import {
57
+ COMPLEXITY_LEVELS,
58
+ COMPONENT_FEATURES,
59
+ generateComponentByComplexity,
60
+ interactiveComponentGeneration,
61
+ validateGeneratedComponent,
62
+ displayValidationReport
63
+ } from './cli/component-generator.js';
64
+ import {
65
+ syncDocumentation,
66
+ validateDocumentation,
67
+ generateCLIDocumentation
68
+ } from './cli/documentation-sync.js';
44
69
 
45
70
  const __filename = fileURLToPath(import.meta.url);
46
71
  const __dirname = dirname(__filename);
@@ -53,18 +78,6 @@ const packageJson = JSON.parse(
53
78
  // CLI Configuration
54
79
  const DEBUG = process.env.ATOMIX_DEBUG === 'true' || process.argv.includes('--debug');
55
80
 
56
- /**
57
- * Enhanced Error Class
58
- */
59
- class AtomixCLIError extends Error {
60
- constructor(message, code, suggestions = []) {
61
- super(message);
62
- this.name = 'AtomixCLIError';
63
- this.code = code;
64
- this.suggestions = suggestions;
65
- }
66
- }
67
-
68
81
  /**
69
82
  * Debug logger
70
83
  */
@@ -72,7 +85,7 @@ function debug(message, data = null) {
72
85
  if (DEBUG) {
73
86
  console.log(chalk.gray(`[DEBUG] ${message}`));
74
87
  if (data) {
75
- console.log(chalk.gray(JSON.stringify(data, null, 2)));
88
+ console.log(chalk.gray(JSON.stringify(data, null, 2),));
76
89
  }
77
90
  }
78
91
  }
@@ -82,497 +95,25 @@ function debug(message, data = null) {
82
95
  */
83
96
  function handleError(error, spinner = null) {
84
97
  if (spinner) spinner.fail(chalk.red('Operation failed'));
85
-
98
+
86
99
  console.error(chalk.bold.red(`\n❌ ${error.message}`));
87
-
100
+
88
101
  if (error instanceof AtomixCLIError && error.suggestions.length > 0) {
89
102
  console.log(chalk.yellow('\n💡 Suggestions:'));
90
103
  error.suggestions.forEach((suggestion, index) => {
91
104
  console.log(chalk.gray(` ${index + 1}. ${suggestion}`));
92
105
  });
93
106
  }
94
-
107
+
95
108
  if (DEBUG && error.stack) {
96
109
  console.error(chalk.gray('\nStack trace:'));
97
110
  console.error(chalk.gray(error.stack));
98
111
  }
99
-
100
- process.exit(1);
101
- }
102
-
103
- /**
104
- * Component templates for design system
105
- */
106
- const componentTemplates = {
107
- react: {
108
- component: (name, options = {}) => `import React, { forwardRef, memo } from 'react';
109
- ${options.scssModule ? `import styles from './${name}.module.scss';` : ''}
110
- ${options.types ? `import type { ${name}Props } from '../../lib/types/components';` : ''}
111
-
112
- ${options.types ? '' : `export interface ${name}Props {
113
- /**
114
- * Content to be rendered
115
- */
116
- children?: React.ReactNode;
117
-
118
- /**
119
- * Additional CSS classes
120
- */
121
- className?: string;
122
-
123
- /**
124
- * Size variant
125
- */
126
- size?: 'sm' | 'md' | 'lg';
127
-
128
- /**
129
- * Color variant
130
- */
131
- variant?: 'primary' | 'secondary' | 'success' | 'error';
132
-
133
- /**
134
- * Disabled state
135
- */
136
- disabled?: boolean;
137
- }`}
138
-
139
- /**
140
- * ${name} component
141
- *
142
- * @component
143
- * @example
144
- * <${name} variant="primary" size="md">
145
- * Content
146
- * </${name}>
147
- */
148
- export const ${name} = memo(
149
- forwardRef<HTMLDivElement, ${name}Props>(
150
- ({ children, className = '', size = 'md', variant = 'primary', disabled = false, ...props }, ref) => {
151
- ${options.scssModule ? `const componentClasses = [
152
- styles.${name.toLowerCase()},
153
- styles[\`${name.toLowerCase()}--\${size}\`],
154
- styles[\`${name.toLowerCase()}--\${variant}\`],
155
- disabled && styles['${name.toLowerCase()}--disabled'],
156
- className
157
- ].filter(Boolean).join(' ');` : `const componentClasses = \`c-${name.toLowerCase()} c-${name.toLowerCase()}--\${size} c-${name.toLowerCase()}--\${variant} \${disabled ? 'c-${name.toLowerCase()}--disabled' : ''} \${className}\`.trim();`}
158
-
159
- return (
160
- <div
161
- ref={ref}
162
- className={componentClasses}
163
- aria-disabled={disabled}
164
- {...props}
165
- >
166
- {children}
167
- </div>
168
- );
169
- }
170
- )
171
- );
172
-
173
- ${name}.displayName = '${name}';
174
-
175
- export default ${name};`,
176
-
177
- index: (name) => `export { default as ${name} } from './${name}';
178
- export type { ${name}Props } from './${name}';`,
179
-
180
- story: (name) => `import type { Meta, StoryObj } from '@storybook/react';
181
- import { ${name} } from './${name}';
182
-
183
- const meta: Meta<typeof ${name}> = {
184
- title: 'Components/${name}',
185
- component: ${name},
186
- parameters: {
187
- layout: 'centered',
188
- },
189
- tags: ['autodocs'],
190
- argTypes: {
191
- size: {
192
- control: 'select',
193
- options: ['sm', 'md', 'lg'],
194
- },
195
- variant: {
196
- control: 'select',
197
- options: ['primary', 'secondary', 'success', 'error'],
198
- },
199
- disabled: {
200
- control: 'boolean',
201
- },
202
- },
203
- };
204
-
205
- export default meta;
206
- type Story = StoryObj<typeof meta>;
207
-
208
- export const Default: Story = {
209
- args: {
210
- children: '${name} Component',
211
- size: 'md',
212
- variant: 'primary',
213
- },
214
- };
215
-
216
- export const Small: Story = {
217
- args: {
218
- ...Default.args,
219
- size: 'sm',
220
- },
221
- };
222
-
223
- export const Large: Story = {
224
- args: {
225
- ...Default.args,
226
- size: 'lg',
227
- },
228
- };
229
-
230
- export const Secondary: Story = {
231
- args: {
232
- ...Default.args,
233
- variant: 'secondary',
234
- },
235
- };
236
-
237
- export const Success: Story = {
238
- args: {
239
- ...Default.args,
240
- variant: 'success',
241
- },
242
- };
243
-
244
- export const Error: Story = {
245
- args: {
246
- ...Default.args,
247
- variant: 'error',
248
- },
249
- };
250
-
251
- export const Disabled: Story = {
252
- args: {
253
- ...Default.args,
254
- disabled: true,
255
- },
256
- };`,
257
-
258
- test: (name) => `import { describe, it, expect } from 'vitest';
259
- import { render, screen } from '@testing-library/react';
260
- import { ${name} } from './${name}';
261
-
262
- describe('${name}', () => {
263
- it('renders children correctly', () => {
264
- render(<${name}>Test Content</${name}>);
265
- expect(screen.getByText('Test Content')).toBeInTheDocument();
266
- });
267
-
268
- it('applies size variant classes', () => {
269
- const { container } = render(<${name} size="lg">Content</${name}>);
270
- const element = container.firstChild;
271
- expect(element).toHaveClass('c-${name.toLowerCase()}--lg');
272
- });
273
-
274
- it('applies variant classes', () => {
275
- const { container } = render(<${name} variant="success">Content</${name}>);
276
- const element = container.firstChild;
277
- expect(element).toHaveClass('c-${name.toLowerCase()}--success');
278
- });
279
-
280
- it('handles disabled state', () => {
281
- const { container } = render(<${name} disabled>Content</${name}>);
282
- const element = container.firstChild;
283
- expect(element).toHaveAttribute('aria-disabled', 'true');
284
- expect(element).toHaveClass('c-${name.toLowerCase()}--disabled');
285
- });
286
-
287
- it('forwards ref correctly', () => {
288
- const ref = React.createRef<HTMLDivElement>();
289
- render(<${name} ref={ref}>Content</${name}>);
290
- expect(ref.current).toBeInstanceOf(HTMLDivElement);
291
- });
292
- });`,
293
-
294
- scss: (name) => `// Component: ${name}
295
- // =============================================================================
296
- // Design system component following ITCSS architecture
297
-
298
- @import '../../styles/01-settings';
299
- @import '../../styles/02-tools';
300
-
301
- // Block: Base component
302
- // =============================================================================
303
- .c-${name.toLowerCase()} {
304
- // Layout
305
- display: flex;
306
- align-items: center;
307
- justify-content: center;
308
-
309
- // Spacing
310
- padding: var(--atomix-space-3) var(--atomix-space-4);
311
- gap: var(--atomix-space-2);
312
-
313
- // Typography
314
- font-family: var(--atomix-font-family-base);
315
- font-size: var(--atomix-font-size-base);
316
- font-weight: var(--atomix-font-weight-normal);
317
- line-height: var(--atomix-line-height-base);
318
-
319
- // Appearance
320
- background-color: var(--atomix-color-background);
321
- color: var(--atomix-color-text);
322
- border: 1px solid var(--atomix-color-border);
323
- border-radius: var(--atomix-radius-md);
324
-
325
- // Interaction
326
- cursor: default;
327
- user-select: none;
328
- transition: all 0.2s ease-in-out;
329
-
330
- // Focus
331
- &:focus-visible {
332
- @include focus-ring;
333
- }
334
- }
335
-
336
- // Size Modifiers
337
- // =============================================================================
338
- .c-${name.toLowerCase()}--sm {
339
- padding: var(--atomix-space-2) var(--atomix-space-3);
340
- font-size: var(--atomix-font-size-sm);
341
- gap: var(--atomix-space-1);
342
- }
343
-
344
- .c-${name.toLowerCase()}--md {
345
- // Default size - explicitly defined for clarity
346
- padding: var(--atomix-space-3) var(--atomix-space-4);
347
- font-size: var(--atomix-font-size-base);
348
- gap: var(--atomix-space-2);
349
- }
350
-
351
- .c-${name.toLowerCase()}--lg {
352
- padding: var(--atomix-space-4) var(--atomix-space-5);
353
- font-size: var(--atomix-font-size-lg);
354
- gap: var(--atomix-space-3);
355
- }
356
-
357
- // Color/Variant Modifiers
358
- // =============================================================================
359
- .c-${name.toLowerCase()}--primary {
360
- background-color: var(--atomix-color-primary);
361
- color: var(--atomix-color-primary-text);
362
- border-color: var(--atomix-color-primary-dark);
363
-
364
- &:hover:not(:disabled) {
365
- background-color: var(--atomix-color-primary-dark);
366
- }
367
- }
368
-
369
- .c-${name.toLowerCase()}--secondary {
370
- background-color: var(--atomix-color-secondary);
371
- color: var(--atomix-color-secondary-text);
372
- border-color: var(--atomix-color-secondary-dark);
373
-
374
- &:hover:not(:disabled) {
375
- background-color: var(--atomix-color-secondary-dark);
376
- }
377
- }
378
-
379
- .c-${name.toLowerCase()}--success {
380
- background-color: var(--atomix-color-success);
381
- color: var(--atomix-color-success-text);
382
- border-color: var(--atomix-color-success-dark);
383
-
384
- &:hover:not(:disabled) {
385
- background-color: var(--atomix-color-success-dark);
386
- }
387
- }
388
-
389
- .c-${name.toLowerCase()}--error {
390
- background-color: var(--atomix-color-error);
391
- color: var(--atomix-color-error-text);
392
- border-color: var(--atomix-color-error-dark);
393
-
394
- &:hover:not(:disabled) {
395
- background-color: var(--atomix-color-error-dark);
396
- }
397
- }
398
-
399
- // State Modifiers
400
- // =============================================================================
401
- .c-${name.toLowerCase()}--disabled {
402
- @include disabled;
403
- cursor: not-allowed;
404
-
405
- &:hover {
406
- transform: none;
407
- }
408
- }
409
-
410
- // Elements (if component has child elements)
411
- // =============================================================================
412
- .c-${name.toLowerCase()}__icon {
413
- display: flex;
414
- align-items: center;
415
- justify-content: center;
416
- flex-shrink: 0;
417
- }
418
-
419
- .c-${name.toLowerCase()}__content {
420
- flex: 1;
421
- }
422
-
423
- // Responsive Design
424
- // =============================================================================
425
- @include respond-to('tablet') {
426
- .c-${name.toLowerCase()} {
427
- // Tablet adjustments
428
- }
429
- }
430
-
431
- @include respond-to('mobile') {
432
- .c-${name.toLowerCase()} {
433
- // Mobile adjustments
434
- padding: var(--atomix-space-2) var(--atomix-space-3);
435
- }
436
- }
437
-
438
- // Accessibility
439
- // =============================================================================
440
- @media (prefers-reduced-motion: reduce) {
441
- .c-${name.toLowerCase()} {
442
- transition: none;
443
- }
444
- }
445
-
446
- @media (prefers-contrast: high) {
447
- .c-${name.toLowerCase()} {
448
- border-width: 2px;
449
- }
450
- }
451
-
452
- // Dark Mode Support
453
- // =============================================================================
454
- [data-theme="dark"] {
455
- .c-${name.toLowerCase()} {
456
- background-color: var(--atomix-color-background-dark);
457
- color: var(--atomix-color-text-dark);
458
- border-color: var(--atomix-color-border-dark);
459
- }
460
- }`,
461
-
462
- scssModule: (name) => `// Component: ${name}
463
- // =============================================================================
464
- // Design system component using CSS Modules
465
-
466
- @import '../../styles/01-settings';
467
- @import '../../styles/02-tools';
468
-
469
- // Block: Base component
470
- // =============================================================================
471
- .${name.toLowerCase()} {
472
- // Layout
473
- display: flex;
474
- align-items: center;
475
- justify-content: center;
476
-
477
- // Spacing
478
- padding: var(--atomix-space-3) var(--atomix-space-4);
479
- gap: var(--atomix-space-2);
480
-
481
- // Typography
482
- font-family: var(--atomix-font-family-base);
483
- font-size: var(--atomix-font-size-base);
484
- font-weight: var(--atomix-font-weight-normal);
485
- line-height: var(--atomix-line-height-base);
486
-
487
- // Appearance
488
- background-color: var(--atomix-color-background);
489
- color: var(--atomix-color-text);
490
- border: 1px solid var(--atomix-color-border);
491
- border-radius: var(--atomix-radius-md);
492
-
493
- // Interaction
494
- cursor: default;
495
- user-select: none;
496
- transition: all 0.2s ease-in-out;
497
-
498
- // Focus
499
- &:focus-visible {
500
- outline: 2px solid var(--atomix-color-focus);
501
- outline-offset: 2px;
502
- }
503
- }
504
-
505
- // Size Modifiers
506
- // =============================================================================
507
- .${name.toLowerCase()}--sm {
508
- composes: ${name.toLowerCase()};
509
- padding: var(--atomix-space-2) var(--atomix-space-3);
510
- font-size: var(--atomix-font-size-sm);
511
- gap: var(--atomix-space-1);
512
- }
513
-
514
- .${name.toLowerCase()}--md {
515
- composes: ${name.toLowerCase()};
516
- }
517
-
518
- .${name.toLowerCase()}--lg {
519
- composes: ${name.toLowerCase()};
520
- padding: var(--atomix-space-4) var(--atomix-space-5);
521
- font-size: var(--atomix-font-size-lg);
522
- gap: var(--atomix-space-3);
523
- }
524
-
525
- // Color/Variant Modifiers
526
- // =============================================================================
527
- .${name.toLowerCase()}--primary {
528
- background-color: var(--atomix-color-primary);
529
- color: var(--atomix-color-primary-text);
530
- border-color: var(--atomix-color-primary-dark);
531
-
532
- &:hover:not([aria-disabled="true"]) {
533
- background-color: var(--atomix-color-primary-dark);
534
- }
535
- }
536
112
 
537
- .${name.toLowerCase()}--secondary {
538
- background-color: var(--atomix-color-secondary);
539
- color: var(--atomix-color-secondary-text);
540
- border-color: var(--atomix-color-secondary-dark);
541
-
542
- &:hover:not([aria-disabled="true"]) {
543
- background-color: var(--atomix-color-secondary-dark);
544
- }
113
+ process.exit(1);
545
114
  }
546
115
 
547
- .${name.toLowerCase()}--success {
548
- background-color: var(--atomix-color-success);
549
- color: var(--atomix-color-success-text);
550
- border-color: var(--atomix-color-success-dark);
551
-
552
- &:hover:not([aria-disabled="true"]) {
553
- background-color: var(--atomix-color-success-dark);
554
- }
555
- }
556
116
 
557
- .${name.toLowerCase()}--error {
558
- background-color: var(--atomix-color-error);
559
- color: var(--atomix-color-error-text);
560
- border-color: var(--atomix-color-error-dark);
561
-
562
- &:hover:not([aria-disabled="true"]) {
563
- background-color: var(--atomix-color-error-dark);
564
- }
565
- }
566
-
567
- // State Modifiers
568
- // =============================================================================
569
- .${name.toLowerCase()}--disabled {
570
- opacity: 0.5;
571
- cursor: not-allowed;
572
- pointer-events: none;
573
- }`
574
- }
575
- };
576
117
 
577
118
  // Initialize program
578
119
  program
@@ -599,15 +140,40 @@ program
599
140
  .option('--analyze', 'Analyze bundle size', false)
600
141
  .action(async (themePath, options) => {
601
142
  let spinner = ora('Initializing theme build...').start();
602
-
143
+
603
144
  try {
145
+ const sanitizedThemePath = sanitizeInput(themePath);
146
+ const themePathValidation = validatePath(sanitizedThemePath);
147
+ if (!themePathValidation.isValid) {
148
+ throw new AtomixCLIError(
149
+ themePathValidation.error,
150
+ 'INVALID_PATH',
151
+ [
152
+ 'Ensure theme path is within the project directory',
153
+ 'Avoid sensitive or absolute system paths',
154
+ 'Example: atomix build-theme themes/my-theme'
155
+ ]
156
+ );
157
+ }
158
+ const sanitizedOutput = sanitizeInput(options.output);
159
+ const outputValidation = validatePath(sanitizedOutput);
160
+ if (!outputValidation.isValid) {
161
+ throw new AtomixCLIError(
162
+ outputValidation.error,
163
+ 'INVALID_PATH',
164
+ [
165
+ 'Use a project-relative directory for output',
166
+ 'Example: --output ./dist'
167
+ ]
168
+ );
169
+ }
604
170
  // Resolve paths
605
- const indexPath = themePath.endsWith('.scss')
606
- ? resolve(themePath)
607
- : resolve(themePath, 'index.scss');
608
-
171
+ const indexPath = sanitizedThemePath.endsWith('.scss')
172
+ ? resolve(themePathValidation.safePath)
173
+ : resolve(themePathValidation.safePath, 'index.scss');
174
+
609
175
  debug(`Building theme from: ${indexPath}`);
610
-
176
+
611
177
  // Check if path exists
612
178
  try {
613
179
  await access(indexPath);
@@ -622,16 +188,16 @@ program
622
188
  ]
623
189
  );
624
190
  }
625
-
191
+
626
192
  // Build function
627
193
  const buildTheme = async () => {
628
194
  const startTime = Date.now();
629
-
195
+
630
196
  try {
631
197
  // Compile SCSS
632
198
  spinner.text = 'Compiling SCSS...';
633
199
  debug('Starting SCSS compilation');
634
-
200
+
635
201
  const result = sass.compile(indexPath, {
636
202
  loadPaths: [
637
203
  process.cwd(),
@@ -643,7 +209,7 @@ program
643
209
  sourceMap: options.sourcemap,
644
210
  style: 'expanded',
645
211
  });
646
-
212
+
647
213
  // Process with PostCSS
648
214
  spinner.text = 'Processing with PostCSS...';
649
215
  const processed = await postcss([
@@ -654,23 +220,23 @@ program
654
220
  from: indexPath,
655
221
  map: options.sourcemap,
656
222
  });
657
-
223
+
658
224
  // Ensure output directory exists
659
- await mkdir(options.output, { recursive: true });
660
-
225
+ await mkdir(outputValidation.safePath, { recursive: true });
226
+
661
227
  // Get theme name
662
228
  const themeName = basename(dirname(indexPath));
663
-
229
+
664
230
  // Write expanded CSS
665
- const outputPath = join(options.output, `${themeName}.css`);
231
+ const outputPath = join(outputValidation.safePath, `${themeName}.css`);
666
232
  await writeFile(outputPath, processed.css, 'utf8');
667
-
233
+
668
234
  // Get file size
669
235
  const stats = await stat(outputPath);
670
236
  const sizeKB = (stats.size / 1024).toFixed(2);
671
-
237
+
672
238
  spinner.succeed(chalk.green(`✓ Built ${outputPath} (${sizeKB} KB)`));
673
-
239
+
674
240
  // Write minified if requested
675
241
  if (options.minify) {
676
242
  const minifySpinner = ora('Minifying CSS...').start();
@@ -680,22 +246,22 @@ program
680
246
  ]).process(result.css, {
681
247
  from: indexPath,
682
248
  });
683
-
684
- const minPath = join(options.output, `${themeName}.min.css`);
249
+
250
+ const minPath = join(outputValidation.safePath, `${themeName}.min.css`);
685
251
  await writeFile(minPath, minified.css, 'utf8');
686
-
252
+
687
253
  const minStats = await stat(minPath);
688
254
  const minSizeKB = (minStats.size / 1024).toFixed(2);
689
-
255
+
690
256
  minifySpinner.succeed(chalk.green(`✓ Built ${minPath} (${minSizeKB} KB)`));
691
257
  }
692
-
258
+
693
259
  // Analyze if requested
694
260
  if (options.analyze) {
695
261
  console.log(chalk.cyan('\n📊 Theme Analysis:'));
696
262
  console.log(chalk.gray(` Original size: ${sizeKB} KB`));
697
263
  if (options.minify) {
698
- const minPath = join(options.output, `${themeName}.min.css`);
264
+ const minPath = join(outputValidation.safePath, `${themeName}.min.css`);
699
265
  const minStats = await stat(minPath);
700
266
  const minSizeKB = (minStats.size / 1024).toFixed(2);
701
267
  const reduction = ((1 - minStats.size / stats.size) * 100).toFixed(1);
@@ -703,11 +269,11 @@ program
703
269
  }
704
270
  console.log(chalk.gray(` Build time: ${Date.now() - startTime}ms`));
705
271
  }
706
-
272
+
707
273
  if (!options.watch) {
708
274
  console.log(chalk.bold.green('\n✨ Theme build complete!'));
709
275
  }
710
-
276
+
711
277
  } catch (error) {
712
278
  if (options.watch) {
713
279
  console.error(chalk.red(`Build error: ${error.message}`));
@@ -717,35 +283,35 @@ program
717
283
  }
718
284
  }
719
285
  };
720
-
286
+
721
287
  // Initial build
722
288
  await buildTheme();
723
289
  spinner.stop();
724
-
290
+
725
291
  // Watch mode
726
292
  if (options.watch) {
727
293
  console.log(chalk.cyan('\n👁️ Watch mode enabled. Press Ctrl+C to exit.\n'));
728
-
729
- const watcher = chokidar.watch([themePath], {
294
+
295
+ const watcher = chokidar.watch([themePathValidation.safePath], {
730
296
  ignored: /node_modules/,
731
297
  persistent: true,
732
298
  ignoreInitial: true
733
299
  });
734
-
300
+
735
301
  watcher.on('change', async (path) => {
736
302
  console.log(chalk.gray(`\n[${new Date().toLocaleTimeString()}] File changed: ${relative(process.cwd(), path)}`));
737
303
  spinner = ora('Rebuilding theme...').start();
738
304
  await buildTheme();
739
305
  spinner.stop();
740
306
  });
741
-
307
+
742
308
  watcher.on('add', async (path) => {
743
309
  console.log(chalk.gray(`\n[${new Date().toLocaleTimeString()}] File added: ${relative(process.cwd(), path)}`));
744
310
  spinner = ora('Rebuilding theme...').start();
745
311
  await buildTheme();
746
312
  spinner.stop();
747
313
  });
748
-
314
+
749
315
  // Handle graceful shutdown
750
316
  process.on('SIGINT', () => {
751
317
  console.log(chalk.yellow('\n\nShutting down watch mode...'));
@@ -753,7 +319,7 @@ program
753
319
  process.exit(0);
754
320
  });
755
321
  }
756
-
322
+
757
323
  } catch (error) {
758
324
  handleError(error, spinner);
759
325
  }
@@ -772,30 +338,43 @@ program
772
338
  .option('--scss-module', 'Use SCSS modules', false)
773
339
  .option('--path <path>', 'Custom output path', './src/components')
774
340
  .option('-f, --force', 'Overwrite existing files', false)
341
+ .option('-i, --interactive', 'Interactive component generation', false)
342
+ .option('--complexity <level>', 'Component complexity level (simple|medium|complex)', 'medium')
343
+ .option('--validate', 'Validate component after generation', true)
775
344
  .action(async (type, name, options) => {
776
345
  const spinner = ora(`Generating ${type}: ${name}...`).start();
777
-
346
+
778
347
  try {
779
348
  debug(`Generating ${type} with name: ${name}`, options);
780
-
781
- // Validate name
782
- const nameValidation = validateComponentName(name);
783
- if (!nameValidation.isValid) {
784
- throw new AtomixCLIError(
785
- nameValidation.error,
786
- 'INVALID_NAME',
787
- [
788
- 'Use PascalCase naming (e.g., MyComponent)',
789
- 'Start with an uppercase letter',
790
- 'Use only letters and numbers',
791
- 'Avoid reserved words'
792
- ]
793
- );
794
- }
795
-
349
+ const safeName = sanitizeInput(name);
350
+
796
351
  if (type === 'component' || type === 'c') {
352
+ // Handle interactive generation
353
+ if (options.interactive) {
354
+ const interactiveResult = await interactiveComponentGeneration();
355
+ if (!interactiveResult) {
356
+ return; // User cancelled
357
+ }
358
+ // Update options with interactive results
359
+ Object.assign(options, interactiveResult.options);
360
+ }
361
+
362
+ const nameValidation = validateComponentName(safeName);
363
+ if (!nameValidation.isValid) {
364
+ throw new AtomixCLIError(
365
+ nameValidation.error,
366
+ 'INVALID_NAME',
367
+ [
368
+ 'Use PascalCase naming (e.g., MyComponent)',
369
+ 'Start with an uppercase letter',
370
+ 'Use only letters and numbers',
371
+ 'Avoid reserved words'
372
+ ]
373
+ );
374
+ }
797
375
  // Validate output path
798
- const pathValidation = validatePath(options.path);
376
+ const sanitizedPath = sanitizeInput(options.path);
377
+ const pathValidation = validatePath(sanitizedPath);
799
378
  if (!pathValidation.isValid) {
800
379
  throw new AtomixCLIError(
801
380
  pathValidation.error,
@@ -807,13 +386,13 @@ program
807
386
  ]
808
387
  );
809
388
  }
810
-
811
- const componentPath = join(pathValidation.safePath, name);
812
-
389
+
390
+ const componentPath = join(pathValidation.safePath, safeName);
391
+
813
392
  // Check if component already exists
814
393
  if (existsSync(componentPath) && !options.force) {
815
394
  throw new AtomixCLIError(
816
- `Component ${name} already exists`,
395
+ `Component ${safeName} already exists`,
817
396
  'COMPONENT_EXISTS',
818
397
  [
819
398
  `Delete the existing component at ${componentPath}`,
@@ -822,77 +401,215 @@ program
822
401
  ]
823
402
  );
824
403
  }
825
-
404
+
826
405
  // Create component directory
827
406
  await mkdir(componentPath, { recursive: true });
828
-
407
+
408
+ // Generate composable hook
409
+ const hookPath = join(process.cwd(), 'src/lib/composables');
410
+ if (existsSync(hookPath)) {
411
+ const hookContent = componentTemplates.composable.hook(safeName);
412
+ const hookFilePath = join(hookPath, `use${safeName}.ts`);
413
+
414
+ if (!existsSync(hookFilePath) || options.force) {
415
+ await writeFile(hookFilePath, hookContent, 'utf8');
416
+ console.log(chalk.green(` ✓ Created use${safeName}.ts in src/lib/composables`));
417
+ } else {
418
+ console.log(chalk.yellow(` ⚠️ Hook file already exists: use${safeName}.ts`));
419
+ }
420
+ } else {
421
+ console.log(chalk.yellow(` ⚠️ Composables directory not found: ${hookPath}`));
422
+ }
423
+
424
+ // Generate types in lib/types/components.ts
425
+ const typesPath = join(process.cwd(), 'src/lib/types');
426
+ if (existsSync(typesPath)) {
427
+ const componentsTypesPath = join(typesPath, 'components.ts');
428
+ if (existsSync(componentsTypesPath)) {
429
+ let typesContent = await readFile(componentsTypesPath, 'utf8');
430
+ const newTypeContent = componentTemplates.react.types(safeName);
431
+
432
+ // Check if type already exists
433
+ if (!typesContent.includes(`interface ${safeName}Props`)) {
434
+ // Insert before the last export
435
+ const lastExportIndex = typesContent.lastIndexOf('export');
436
+ const insertionPoint = typesContent.lastIndexOf('\n\n', lastExportIndex) + 2;
437
+ typesContent =
438
+ typesContent.slice(0, insertionPoint) +
439
+ '\n' + newTypeContent + '\n' +
440
+ typesContent.slice(insertionPoint);
441
+
442
+ await writeFile(componentsTypesPath, typesContent, 'utf8');
443
+ console.log(chalk.green(` ✓ Added ${safeName}Props to src/lib/types/components.ts`));
444
+ } else {
445
+ console.log(chalk.yellow(` ⚠️ ${safeName}Props already exists in types`));
446
+ }
447
+ }
448
+ }
449
+
450
+ // Generate constants in lib/constants/components.ts
451
+ const constantsPath = join(process.cwd(), 'src/lib/constants');
452
+ if (existsSync(constantsPath)) {
453
+ const constantsFilePath = join(constantsPath, 'components.ts');
454
+ if (existsSync(constantsFilePath)) {
455
+ let constantsContent = await readFile(constantsFilePath, 'utf8');
456
+ const newConstantsContent = componentTemplates.react.constants(safeName);
457
+
458
+ // Check if constants already exist
459
+ if (!constantsContent.includes(`export const ${safeName.toUpperCase()}`)) {
460
+ // Insert before the last export
461
+ const lastExportIndex = constantsContent.lastIndexOf('export');
462
+ const insertionPoint = constantsContent.lastIndexOf('\n\n', lastExportIndex) + 2;
463
+ constantsContent =
464
+ constantsContent.slice(0, insertionPoint) +
465
+ '\n' + newConstantsContent + '\n' +
466
+ constantsContent.slice(insertionPoint);
467
+
468
+ await writeFile(constantsFilePath, constantsContent, 'utf8');
469
+ console.log(chalk.green(` ✓ Added ${safeName.toUpperCase()} constants to src/lib/constants/components.ts`));
470
+ } else {
471
+ console.log(chalk.yellow(` ⚠️ ${safeName.toUpperCase()} constants already exist`));
472
+ }
473
+ }
474
+ }
475
+
829
476
  // Generate component file
830
- const componentContent = componentTemplates.react.component(name, {
831
- scssModule: options.scssModule,
832
- types: false // We'll generate inline types for now
833
- });
834
-
477
+ const componentContent = componentTemplates.react.component(safeName);
478
+
835
479
  await writeFile(
836
- join(componentPath, `${name}.tsx`),
480
+ join(componentPath, `${safeName}.tsx`),
837
481
  componentContent,
838
482
  'utf8'
839
483
  );
840
- spinner.succeed(chalk.green(`✓ Created ${name}.tsx`));
841
-
484
+ spinner.succeed(chalk.green(`✓ Created ${safeName}.tsx`));
485
+
842
486
  // Generate index file
843
- const indexContent = componentTemplates.react.index(name);
487
+ const indexContent = componentTemplates.react.index(safeName);
844
488
  await writeFile(
845
489
  join(componentPath, 'index.ts'),
846
490
  indexContent,
847
491
  'utf8'
848
492
  );
849
493
  console.log(chalk.green(` ✓ Created index.ts`));
850
-
851
- // Generate SCSS file
852
- const scssContent = options.scssModule
853
- ? componentTemplates.react.scssModule(name)
854
- : componentTemplates.react.scss(name);
855
-
856
- const scssFilename = options.scssModule ? `${name}.module.scss` : `_${name.toLowerCase()}.scss`;
857
- await writeFile(
858
- join(componentPath, scssFilename),
859
- scssContent,
860
- 'utf8'
861
- );
862
- console.log(chalk.green(` ✓ Created ${scssFilename}`));
863
-
494
+
495
+ // Generate SCSS files
496
+ if (!options.scssModule) {
497
+ // Generate settings file first
498
+ const settingsPath = join(process.cwd(), 'src/styles/01-settings');
499
+ if (existsSync(settingsPath)) {
500
+ const settingsContent = componentTemplates.react.scssSettings(safeName);
501
+ const settingsFilename = `_settings.${safeName.toLowerCase()}.scss`;
502
+ const settingsFilePath = join(settingsPath, settingsFilename);
503
+
504
+ if (!existsSync(settingsFilePath) || options.force) {
505
+ await writeFile(settingsFilePath, settingsContent, 'utf8');
506
+ console.log(chalk.green(` ✓ Created ${settingsFilename} in src/styles/01-settings`));
507
+
508
+ // Update _index.scss in settings
509
+ const settingsIndexPath = join(settingsPath, '_index.scss');
510
+ if (existsSync(settingsIndexPath)) {
511
+ let indexContent = await readFile(settingsIndexPath, 'utf8');
512
+ const forwardStatement = `@forward 'settings.${safeName.toLowerCase()}';`;
513
+
514
+ if (!indexContent.includes(forwardStatement)) {
515
+ if (!indexContent.endsWith('\n')) indexContent += '\n';
516
+ indexContent += `${forwardStatement}\n`;
517
+ await writeFile(settingsIndexPath, indexContent, 'utf8');
518
+ console.log(chalk.green(` ✓ Updated settings _index.scss`));
519
+ }
520
+ }
521
+ } else {
522
+ console.log(chalk.yellow(` ⚠️ Settings file already exists: ${settingsFilename}`));
523
+ }
524
+ }
525
+
526
+ // Generate component SCSS file
527
+ const scssContent = componentTemplates.react.scss(safeName);
528
+ const scssPath = join(process.cwd(), 'src/styles/06-components');
529
+ const scssFilename = `_components.${safeName.toLowerCase()}.scss`;
530
+ const scssFilePath = join(scssPath, scssFilename);
531
+
532
+ // Ensure styles directory exists
533
+ if (existsSync(scssPath)) {
534
+ // Check if SCSS file already exists
535
+ if (!existsSync(scssFilePath) || options.force) {
536
+ await writeFile(scssFilePath, scssContent, 'utf8');
537
+ console.log(chalk.green(` ✓ Created ${scssFilename} in src/styles/06-components`));
538
+
539
+ // Update _index.scss
540
+ const indexPath = join(scssPath, '_index.scss');
541
+ if (existsSync(indexPath)) {
542
+ let indexContent = await readFile(indexPath, 'utf8');
543
+ const forwardStatement = `@forward 'components.${safeName.toLowerCase()}';`;
544
+
545
+ if (!indexContent.includes(forwardStatement)) {
546
+ // Append to end of file, ensuring newline
547
+ if (!indexContent.endsWith('\n')) indexContent += '\n';
548
+ indexContent += `${forwardStatement}\n`;
549
+ await writeFile(indexPath, indexContent, 'utf8');
550
+ console.log(chalk.green(` ✓ Updated _index.scss`));
551
+ }
552
+ }
553
+ } else {
554
+ console.log(chalk.yellow(` ⚠️ SCSS file already exists: ${scssFilename}`));
555
+ }
556
+ } else {
557
+ console.log(chalk.yellow(` ⚠️ Styles directory not found: ${scssPath}`));
558
+ }
559
+ } else {
560
+ // Fallback for modules if strictly requested (though we discourage it)
561
+ const scssContent = componentTemplates.react.scssModule(safeName);
562
+ await writeFile(
563
+ join(componentPath, `${safeName}.module.scss`),
564
+ scssContent,
565
+ 'utf8'
566
+ );
567
+ }
568
+
864
569
  // Generate Storybook story
865
570
  if (options.story) {
866
- const storyContent = componentTemplates.react.story(name);
571
+ const storyContent = componentTemplates.react.storyEnhanced(safeName);
867
572
  await writeFile(
868
- join(componentPath, `${name}.stories.tsx`),
573
+ join(componentPath, `${safeName}.stories.tsx`),
869
574
  storyContent,
870
575
  'utf8'
871
576
  );
872
- console.log(chalk.green(` ✓ Created ${name}.stories.tsx`));
577
+ console.log(chalk.green(` ✓ Created ${safeName}.stories.tsx`));
873
578
  }
874
-
579
+
875
580
  // Generate test file
876
581
  if (options.test) {
877
- const testContent = componentTemplates.react.test(name);
582
+ const testContent = componentTemplates.react.test(safeName);
878
583
  await writeFile(
879
- join(componentPath, `${name}.test.tsx`),
584
+ join(componentPath, `${safeName}.test.tsx`),
880
585
  testContent,
881
586
  'utf8'
882
587
  );
883
- console.log(chalk.green(` ✓ Created ${name}.test.tsx`));
588
+ console.log(chalk.green(` ✓ Created ${safeName}.test.tsx`));
884
589
  }
885
-
590
+
591
+ // Post-generation validation if requested
592
+ if (options.validate) {
593
+ const validationResult = await validateGeneratedComponent(safeName, componentPath);
594
+ const isValid = displayValidationReport(validationResult.issues, validationResult.warnings, safeName);
595
+
596
+ if (!isValid) {
597
+ console.log(chalk.yellow('\n💡 Some issues were found. Consider addressing them for better component quality.'));
598
+ }
599
+ }
600
+
886
601
  // Success message with next steps
887
602
  console.log(boxen(
888
- chalk.bold.green(`🎉 Component ${name} created successfully!\n\n`) +
603
+ chalk.bold.green(`🎉 Component ${safeName} created successfully!\n\n`) +
889
604
  chalk.cyan('Next steps:\n') +
890
605
  chalk.gray(`1. Import in your app:\n`) +
891
- chalk.white(` import { ${name} } from '${options.path}/${name}';\n\n`) +
606
+ chalk.white(` import { ${safeName} } from '${options.path}/${safeName}';\n\n`) +
892
607
  chalk.gray(`2. Add to design system exports:\n`) +
893
- chalk.white(` export { ${name} } from './${name}';\n\n`) +
608
+ chalk.white(` export { ${safeName} } from './${safeName}';\n\n`) +
894
609
  chalk.gray(`3. Run Storybook to see your component:\n`) +
895
- chalk.white(` npm run storybook`),
610
+ chalk.white(` npm run storybook\n\n`) +
611
+ chalk.gray(`4. Validate component quality:\n`) +
612
+ chalk.white(` atomix validate component ${safeName}`),
896
613
  {
897
614
  padding: 1,
898
615
  margin: 1,
@@ -900,11 +617,62 @@ program
900
617
  borderColor: 'green'
901
618
  }
902
619
  ));
903
-
620
+
621
+ } else if (type === 'hook' || type === 'h') {
622
+ const componentPath = join(options.path, `use${safeName}`);
623
+ await mkdir(componentPath, { recursive: true });
624
+
625
+ const hookContent = componentTemplates.hook.hook(safeName);
626
+ await writeFile(join(componentPath, `use${safeName}.ts`), hookContent, 'utf8');
627
+
628
+ if (options.test) {
629
+ const testContent = componentTemplates.hook.test(safeName);
630
+ await writeFile(join(componentPath, `use${safeName}.test.ts`), testContent, 'utf8');
631
+ }
632
+
633
+ spinner.succeed(chalk.green(`✓ Created hook use${safeName}`));
634
+
635
+ } else if (type === 'layout' || type === 'l') {
636
+ const layoutPath = join(options.path, safeName);
637
+ await mkdir(layoutPath, { recursive: true });
638
+
639
+ const componentContent = componentTemplates.layout.component(safeName);
640
+ await writeFile(join(layoutPath, `${safeName}.tsx`), componentContent, 'utf8');
641
+
642
+ const scssContent = componentTemplates.layout.scss(safeName);
643
+ const scssFilename = `_layouts.${safeName.toLowerCase()}.scss`;
644
+ const scssPath = join(process.cwd(), 'src/styles/05-layouts');
645
+
646
+ if (existsSync(scssPath)) {
647
+ await writeFile(join(scssPath, scssFilename), scssContent, 'utf8');
648
+
649
+ // Update layouts index
650
+ const indexPath = join(scssPath, '_index.scss');
651
+ if (existsSync(indexPath)) {
652
+ let indexContent = await readFile(indexPath, 'utf8');
653
+ const forwardStatement = `@forward 'layouts.${safeName.toLowerCase()}';`;
654
+ if (!indexContent.includes(forwardStatement)) {
655
+ indexContent += `\n${forwardStatement}`;
656
+ await writeFile(indexPath, indexContent, 'utf8');
657
+ }
658
+ }
659
+ }
660
+
661
+ spinner.succeed(chalk.green(`✓ Created layout ${safeName}`));
662
+
663
+ } else if (type === 'context' || type === 'ctx') {
664
+ const contextPath = join(options.path, `${safeName}Context`);
665
+ await mkdir(contextPath, { recursive: true });
666
+
667
+ const contextContent = componentTemplates.context.context(safeName);
668
+ await writeFile(join(contextPath, `${safeName}Context.tsx`), contextContent, 'utf8');
669
+
670
+ spinner.succeed(chalk.green(`✓ Created context ${safeName}Context`));
671
+
904
672
  } else if (type === 'token' || type === 't') {
905
673
  // Token generation
906
674
  const validCategories = ['colors', 'spacing', 'typography', 'shadows', 'radius', 'animations'];
907
-
675
+
908
676
  if (!validCategories.includes(name.toLowerCase())) {
909
677
  throw new AtomixCLIError(
910
678
  `Invalid token category: ${name}`,
@@ -916,9 +684,9 @@ program
916
684
  ]
917
685
  );
918
686
  }
919
-
687
+
920
688
  const tokenPath = join(process.cwd(), 'src/styles/01-settings');
921
-
689
+
922
690
  // Check if settings directory exists
923
691
  if (!existsSync(tokenPath)) {
924
692
  throw new AtomixCLIError(
@@ -931,11 +699,11 @@ program
931
699
  ]
932
700
  );
933
701
  }
934
-
702
+
935
703
  // Generate token file based on category
936
704
  let tokenContent = '';
937
705
  let filename = '';
938
-
706
+
939
707
  switch (name.toLowerCase()) {
940
708
  case 'colors':
941
709
  filename = '_settings.colors.custom.scss';
@@ -962,9 +730,9 @@ program
962
730
  tokenContent = generateAnimationTokens();
963
731
  break;
964
732
  }
965
-
733
+
966
734
  const filePath = join(tokenPath, filename);
967
-
735
+
968
736
  // Check if file already exists
969
737
  if (existsSync(filePath) && !options.force) {
970
738
  throw new AtomixCLIError(
@@ -977,11 +745,11 @@ program
977
745
  ]
978
746
  );
979
747
  }
980
-
748
+
981
749
  // Write token file
982
750
  await writeFile(filePath, tokenContent, 'utf8');
983
751
  spinner.succeed(chalk.green(`✓ Created token file: ${filename}`));
984
-
752
+
985
753
  // Success message
986
754
  console.log(boxen(
987
755
  chalk.bold.green(`🎨 ${name} tokens generated successfully!\n\n`) +
@@ -1010,7 +778,7 @@ program
1010
778
  ]
1011
779
  );
1012
780
  }
1013
-
781
+
1014
782
  } catch (error) {
1015
783
  handleError(error, spinner);
1016
784
  }
@@ -1021,36 +789,37 @@ program
1021
789
  */
1022
790
  program
1023
791
  .command('validate [target]')
1024
- .description('Validate themes, design tokens, or accessibility')
792
+ .description('Validate themes, design tokens, components, or accessibility')
1025
793
  .option('--tokens', 'Validate design tokens', false)
1026
794
  .option('--theme <path>', 'Validate specific theme', '')
795
+ .option('--component <name>', 'Validate specific component', '')
1027
796
  .option('--a11y, --accessibility', 'Check accessibility compliance', false)
1028
797
  .option('--fix', 'Attempt to fix issues automatically', false)
1029
798
  .action(async (target, options) => {
1030
799
  const spinner = ora('Running validation...').start();
1031
-
800
+
1032
801
  try {
1033
802
  debug('Validation options:', options);
1034
-
803
+
1035
804
  const issues = [];
1036
805
  const warnings = [];
1037
-
806
+
1038
807
  // Token validation
1039
808
  if (options.tokens || target === 'tokens') {
1040
809
  spinner.text = 'Validating design tokens...';
1041
-
810
+
1042
811
  const tokenFiles = [
1043
812
  'src/styles/01-settings/_settings.colors.scss',
1044
813
  'src/styles/01-settings/_settings.typography.scss',
1045
814
  'src/styles/01-settings/_settings.spacing.scss',
1046
815
  'src/styles/01-settings/_settings.radius.scss'
1047
816
  ];
1048
-
817
+
1049
818
  for (const file of tokenFiles) {
1050
819
  const filePath = join(process.cwd(), file);
1051
820
  if (existsSync(filePath)) {
1052
821
  const content = await readFile(filePath, 'utf8');
1053
-
822
+
1054
823
  // Check for hardcoded values
1055
824
  const hardcodedColors = content.match(/#[0-9a-fA-F]{3,8}(?![0-9a-fA-F])/g);
1056
825
  if (hardcodedColors && hardcodedColors.length > 0) {
@@ -1060,7 +829,7 @@ program
1060
829
  suggestion: 'Use CSS custom properties or SCSS variables'
1061
830
  });
1062
831
  }
1063
-
832
+
1064
833
  // Check for missing default flags
1065
834
  const variables = content.match(/\$[a-z-]+:/gi);
1066
835
  const defaultFlags = content.match(/!default/g);
@@ -1080,11 +849,36 @@ program
1080
849
  }
1081
850
  }
1082
851
  }
1083
-
852
+
853
+ // Component validation
854
+ if (options.component) {
855
+ spinner.text = `Validating component: ${options.component}...`;
856
+
857
+ const componentPath = join('./src/components', options.component);
858
+ const validationResult = await validateGeneratedComponent(options.component, componentPath);
859
+
860
+ // Convert validation results to issue/warning format
861
+ validationResult.issues.forEach(issue => {
862
+ issues.push({
863
+ file: options.component,
864
+ issue: issue,
865
+ suggestion: 'Check component structure and implementation'
866
+ });
867
+ });
868
+
869
+ validationResult.warnings.forEach(warning => {
870
+ warnings.push({
871
+ file: options.component,
872
+ issue: warning,
873
+ suggestion: 'Improve component quality by addressing warnings'
874
+ });
875
+ });
876
+ }
877
+
1084
878
  // Theme validation
1085
879
  if (options.theme) {
1086
880
  spinner.text = `Validating theme: ${options.theme}...`;
1087
-
881
+
1088
882
  const themePath = resolve(options.theme);
1089
883
  if (!existsSync(themePath)) {
1090
884
  issues.push({
@@ -1094,7 +888,7 @@ program
1094
888
  });
1095
889
  } else {
1096
890
  const content = await readFile(themePath, 'utf8');
1097
-
891
+
1098
892
  // Check for required imports
1099
893
  const requiredImports = [
1100
894
  '@import.*settings',
@@ -1102,7 +896,7 @@ program
1102
896
  '@import.*tools',
1103
897
  '@use.*tools'
1104
898
  ];
1105
-
899
+
1106
900
  let hasSettings = false;
1107
901
  for (const pattern of requiredImports) {
1108
902
  if (new RegExp(pattern).test(content)) {
@@ -1110,7 +904,7 @@ program
1110
904
  break;
1111
905
  }
1112
906
  }
1113
-
907
+
1114
908
  if (!hasSettings) {
1115
909
  issues.push({
1116
910
  file: options.theme,
@@ -1120,16 +914,16 @@ program
1120
914
  }
1121
915
  }
1122
916
  }
1123
-
917
+
1124
918
  // Accessibility validation
1125
919
  if (options.a11y || options.accessibility) {
1126
920
  spinner.text = 'Checking accessibility compliance...';
1127
-
921
+
1128
922
  // Check for focus styles
1129
923
  const componentFiles = [
1130
924
  'src/styles/06-components'
1131
925
  ];
1132
-
926
+
1133
927
  for (const dir of componentFiles) {
1134
928
  const dirPath = join(process.cwd(), dir);
1135
929
  if (existsSync(dirPath)) {
@@ -1142,9 +936,9 @@ program
1142
936
  }
1143
937
  }
1144
938
  }
1145
-
939
+
1146
940
  spinner.stop();
1147
-
941
+
1148
942
  // Display results
1149
943
  if (issues.length === 0 && warnings.length === 0) {
1150
944
  console.log(boxen(
@@ -1166,7 +960,7 @@ program
1166
960
  console.log(chalk.yellow(` Fix: ${issue.suggestion}\n`));
1167
961
  });
1168
962
  }
1169
-
963
+
1170
964
  if (warnings.length > 0) {
1171
965
  console.log(chalk.bold.yellow(`\n⚠️ Found ${warnings.length} warning(s):\n`));
1172
966
  warnings.forEach((warning, index) => {
@@ -1175,12 +969,27 @@ program
1175
969
  console.log(chalk.cyan(` Suggestion: ${warning.suggestion}\n`));
1176
970
  });
1177
971
  }
1178
-
1179
- if (options.fix && issues.length > 0) {
1180
- console.log(chalk.cyan('\n🔧 Auto-fix is not yet implemented. Please fix issues manually.'));
972
+
973
+ if (options.fix && (issues.length > 0 || warnings.length > 0)) {
974
+ console.log(chalk.cyan('\n🔧 Attempting to fix issues...'));
975
+
976
+ let fixedCount = 0;
977
+
978
+ // Run token fixes
979
+ if (options.tokens || target === 'tokens' || !target) {
980
+ const tokenFixResult = await fixTokens(options);
981
+ fixedCount += tokenFixResult.totalFixed;
982
+ }
983
+
984
+ if (fixedCount > 0) {
985
+ console.log(chalk.green(`\n✨ Fixed ${fixedCount} issue(s). Please run validate again to verify.`));
986
+ } else {
987
+ console.log(chalk.yellow('\nCould not automatically fix all reported issues. Manual intervention required.'));
988
+ }
989
+ } else {
990
+ console.log(chalk.yellow('\nCould not automatically fix reported issues. Manual intervention required.'));
1181
991
  }
1182
992
  }
1183
-
1184
993
  } catch (error) {
1185
994
  handleError(error, spinner);
1186
995
  }
@@ -1206,7 +1015,7 @@ program
1206
1015
  borderColor: 'cyan'
1207
1016
  }
1208
1017
  ));
1209
-
1018
+
1210
1019
  // Reuse build command with watch flag
1211
1020
  await program.parseAsync([
1212
1021
  ...process.argv.slice(0, 2),
@@ -1231,10 +1040,10 @@ program
1231
1040
  .option('--create-backup', 'Create backup before migration', true)
1232
1041
  .action(async (from, options) => {
1233
1042
  const spinner = ora('Preparing migration...').start();
1234
-
1043
+
1235
1044
  try {
1236
1045
  debug(`Migrating from ${from}`, options);
1237
-
1046
+
1238
1047
  // Validate migration type
1239
1048
  const validMigrations = ['tailwind', 'bootstrap', 'scss-variables'];
1240
1049
  if (!validMigrations.includes(from.toLowerCase())) {
@@ -1248,9 +1057,21 @@ program
1248
1057
  ]
1249
1058
  );
1250
1059
  }
1251
-
1252
- // Check source directory
1253
- const sourcePath = resolve(options.source);
1060
+
1061
+ const sanitizedSource = sanitizeInput(options.source);
1062
+ const sourceValidation = validatePath(sanitizedSource);
1063
+ if (!sourceValidation.isValid) {
1064
+ throw new AtomixCLIError(
1065
+ sourceValidation.error,
1066
+ 'INVALID_PATH',
1067
+ [
1068
+ 'Ensure source path is within the project directory',
1069
+ 'Avoid sensitive or absolute system paths',
1070
+ 'Example: --source ./src'
1071
+ ]
1072
+ );
1073
+ }
1074
+ const sourcePath = resolve(sourceValidation.safePath);
1254
1075
  if (!existsSync(sourcePath)) {
1255
1076
  throw new AtomixCLIError(
1256
1077
  `Source directory not found: ${sourcePath}`,
@@ -1262,9 +1083,9 @@ program
1262
1083
  ]
1263
1084
  );
1264
1085
  }
1265
-
1086
+
1266
1087
  spinner.stop();
1267
-
1088
+
1268
1089
  // Show migration preview
1269
1090
  console.log(boxen(
1270
1091
  chalk.bold.cyan(`🔄 Migration Preview\n\n`) +
@@ -1279,7 +1100,7 @@ program
1279
1100
  borderColor: 'cyan'
1280
1101
  }
1281
1102
  ));
1282
-
1103
+
1283
1104
  // Confirm migration
1284
1105
  if (!options.dryRun) {
1285
1106
  const { confirmMigration } = await inquirer.prompt([
@@ -1290,18 +1111,18 @@ program
1290
1111
  default: false
1291
1112
  }
1292
1113
  ]);
1293
-
1114
+
1294
1115
  if (!confirmMigration) {
1295
1116
  console.log(chalk.yellow('\n Migration cancelled.'));
1296
1117
  return;
1297
1118
  }
1298
1119
  }
1299
-
1120
+
1300
1121
  // Create backup if requested
1301
1122
  if (options.createBackup && !options.dryRun) {
1302
1123
  const backupSpinner = ora('Creating backup...').start();
1303
1124
  const backupDir = `${sourcePath}.backup.${Date.now()}`;
1304
-
1125
+
1305
1126
  try {
1306
1127
  const { execSync } = await import('child_process');
1307
1128
  execSync(`cp -r "${sourcePath}" "${backupDir}"`, { stdio: 'ignore' });
@@ -1310,27 +1131,27 @@ program
1310
1131
  backupSpinner.warn(chalk.yellow('Could not create backup, continuing anyway...'));
1311
1132
  }
1312
1133
  }
1313
-
1134
+
1314
1135
  // Run migration
1315
1136
  let report;
1316
-
1137
+
1317
1138
  switch (from.toLowerCase()) {
1318
1139
  case 'tailwind':
1319
1140
  report = await migrateTailwind(sourcePath, options);
1320
1141
  break;
1321
-
1142
+
1322
1143
  case 'bootstrap':
1323
1144
  report = await migrateBootstrap(sourcePath, options);
1324
1145
  break;
1325
-
1146
+
1326
1147
  case 'scss-variables':
1327
1148
  report = await migrateSCSSVariables(sourcePath, options);
1328
1149
  break;
1329
1150
  }
1330
-
1151
+
1331
1152
  // Display report
1332
1153
  displayMigrationReport(report);
1333
-
1154
+
1334
1155
  // Next steps
1335
1156
  if (!options.dryRun && report.filesProcessed > 0) {
1336
1157
  console.log(chalk.cyan('\n📝 Next Steps:'));
@@ -1340,7 +1161,7 @@ program
1340
1161
  console.log(chalk.gray(' 4. Test your application thoroughly'));
1341
1162
  console.log(chalk.gray(' 5. Customize with your theme: atomix create-theme custom'));
1342
1163
  }
1343
-
1164
+
1344
1165
  } catch (error) {
1345
1166
  handleError(error, spinner);
1346
1167
  }
@@ -1359,7 +1180,7 @@ program
1359
1180
  if (options.skipInstall) {
1360
1181
  process.env.ATOMIX_SKIP_INSTALL = 'true';
1361
1182
  }
1362
-
1183
+
1363
1184
  // Run the interactive wizard
1364
1185
  await runInitWizard();
1365
1186
  } catch (error) {
@@ -1380,21 +1201,22 @@ program
1380
1201
  .action(async (action, options) => {
1381
1202
  try {
1382
1203
  debug(`Token action: ${action}`, options);
1383
-
1204
+
1384
1205
  switch (action.toLowerCase()) {
1385
1206
  case 'list':
1386
1207
  case 'ls':
1387
1208
  await listTokens(options.category);
1388
1209
  break;
1389
-
1210
+
1390
1211
  case 'validate':
1391
- case 'check':
1212
+ case 'check': {
1392
1213
  const validationResult = await validateTokens(options);
1393
1214
  if (validationResult.issues.length > 0) {
1394
1215
  process.exit(1); // Exit with error if issues found
1395
1216
  }
1396
1217
  break;
1397
-
1218
+ }
1219
+
1398
1220
  case 'export':
1399
1221
  if (!options.format) {
1400
1222
  throw new AtomixCLIError(
@@ -1407,9 +1229,24 @@ program
1407
1229
  ]
1408
1230
  );
1409
1231
  }
1410
- await exportTokens(options.format, options.output);
1232
+ if (options.output) {
1233
+ const outValidation = validatePath(sanitizeInput(options.output));
1234
+ if (!outValidation.isValid) {
1235
+ throw new AtomixCLIError(
1236
+ outValidation.error,
1237
+ 'INVALID_PATH',
1238
+ [
1239
+ 'Use a project-relative output file path',
1240
+ 'Example: --output ./tokens.json'
1241
+ ]
1242
+ );
1243
+ }
1244
+ await exportTokens(options.format, outValidation.safePath);
1245
+ } else {
1246
+ await exportTokens(options.format, options.output);
1247
+ }
1411
1248
  break;
1412
-
1249
+
1413
1250
  case 'import':
1414
1251
  if (!options.output) {
1415
1252
  throw new AtomixCLIError(
@@ -1421,9 +1258,22 @@ program
1421
1258
  ]
1422
1259
  );
1423
1260
  }
1424
- await importTokens(options.output, { dryRun: options.dryRun });
1261
+ {
1262
+ const inValidation = validatePath(sanitizeInput(options.output));
1263
+ if (!inValidation.isValid) {
1264
+ throw new AtomixCLIError(
1265
+ inValidation.error,
1266
+ 'INVALID_PATH',
1267
+ [
1268
+ 'Use a project-relative input file path',
1269
+ 'Example: --output ./tokens.json'
1270
+ ]
1271
+ );
1272
+ }
1273
+ await importTokens(inValidation.safePath, { dryRun: options.dryRun });
1274
+ }
1425
1275
  break;
1426
-
1276
+
1427
1277
  default:
1428
1278
  throw new AtomixCLIError(
1429
1279
  `Unknown token action: ${action}`,
@@ -1435,7 +1285,7 @@ program
1435
1285
  ]
1436
1286
  );
1437
1287
  }
1438
-
1288
+
1439
1289
  } catch (error) {
1440
1290
  handleError(error);
1441
1291
  }
@@ -1526,12 +1376,13 @@ themeCommand
1526
1376
  .option('--template <name>', 'Use template (dark|light|high-contrast)')
1527
1377
  .option('--interactive', 'Interactive mode', false)
1528
1378
  .option('-o, --output <path>', 'Output directory', './themes')
1379
+ .option('-f, --force', 'Overwrite existing theme', false)
1529
1380
  .action(async (name, options) => {
1530
1381
  const spinner = ora('Creating theme...').start();
1531
-
1382
+
1532
1383
  try {
1533
1384
  debug(`Creating theme: ${name}`, options);
1534
-
1385
+
1535
1386
  // Validate name
1536
1387
  const nameValidation = validateThemeName(name);
1537
1388
  if (!nameValidation.isValid) {
@@ -1546,25 +1397,31 @@ themeCommand
1546
1397
  ]
1547
1398
  );
1548
1399
  }
1549
-
1400
+
1550
1401
  const themePath = join(options.output, name);
1551
-
1402
+
1552
1403
  // Check if theme already exists
1553
1404
  if (existsSync(themePath)) {
1554
- throw new AtomixCLIError(
1555
- `Theme ${name} already exists`,
1556
- 'THEME_EXISTS',
1557
- [
1558
- `Delete the existing theme at ${themePath}`,
1559
- 'Choose a different theme name',
1560
- 'Use --force flag to overwrite (not yet implemented)'
1561
- ]
1562
- );
1405
+ if (options.force) {
1406
+ await rm(themePath, { recursive: true, force: true });
1407
+ await mkdir(themePath, { recursive: true });
1408
+ spinner.info(chalk.yellow(`Overwriting existing theme: ${name}`));
1409
+ } else {
1410
+ throw new AtomixCLIError(
1411
+ `Theme ${name} already exists`,
1412
+ 'THEME_EXISTS',
1413
+ [
1414
+ `Delete the existing theme at ${themePath}`,
1415
+ 'Choose a different theme name',
1416
+ 'Use --force flag to overwrite'
1417
+ ]
1418
+ );
1419
+ }
1420
+ } else {
1421
+ // Create theme directory
1422
+ await mkdir(themePath, { recursive: true });
1563
1423
  }
1564
-
1565
- // Create theme directory
1566
- await mkdir(themePath, { recursive: true });
1567
-
1424
+
1568
1425
  // Generate theme files based on type
1569
1426
  if (options.type === 'css') {
1570
1427
  // Create SCSS theme
@@ -1616,10 +1473,10 @@ themeCommand
1616
1473
  }
1617
1474
  }
1618
1475
  `;
1619
-
1476
+
1620
1477
  await writeFile(join(themePath, 'index.scss'), scssContent, 'utf8');
1621
1478
  spinner.succeed(chalk.green(`✓ Created ${name}/index.scss`));
1622
-
1479
+
1623
1480
  } else if (options.type === 'js') {
1624
1481
  // Create JavaScript theme
1625
1482
  const jsContent = `/**
@@ -1689,11 +1546,11 @@ export const ${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme = crea
1689
1546
 
1690
1547
  export default ${name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Theme;
1691
1548
  `;
1692
-
1549
+
1693
1550
  await writeFile(join(themePath, 'index.ts'), jsContent, 'utf8');
1694
1551
  spinner.succeed(chalk.green(`✓ Created ${name}/index.ts`));
1695
1552
  }
1696
-
1553
+
1697
1554
  // Create README
1698
1555
  const readmeContent = `# ${name} Theme
1699
1556
 
@@ -1734,17 +1591,17 @@ Edit the theme variables in \`index.${options.type === 'css' ? 'scss' : 'ts'}\`
1734
1591
  atomix build-theme themes/${name}
1735
1592
  \`\`\`
1736
1593
  `;
1737
-
1594
+
1738
1595
  await writeFile(join(themePath, 'README.md'), readmeContent, 'utf8');
1739
1596
  console.log(chalk.green(` ✓ Created ${name}/README.md`));
1740
-
1597
+
1741
1598
  // Success message
1742
1599
  console.log(boxen(
1743
1600
  chalk.bold.green(`🎨 Theme "${name}" created successfully!\n\n`) +
1744
1601
  chalk.cyan('Next steps:\n') +
1745
1602
  chalk.gray(`1. Customize your theme:\n`) +
1746
1603
  chalk.white(` Edit ${themePath}/index.${options.type === 'css' ? 'scss' : 'ts'}\n\n`) +
1747
- (options.type === 'css'
1604
+ (options.type === 'css'
1748
1605
  ? chalk.gray(`2. Build your theme:\n`) + chalk.white(` atomix build-theme ${themePath}\n\n`)
1749
1606
  : chalk.gray(`2. Use in your app:\n`) + chalk.white(` import theme from './themes/${name}';\n\n`)
1750
1607
  ) +
@@ -1757,7 +1614,62 @@ atomix build-theme themes/${name}
1757
1614
  borderColor: 'green'
1758
1615
  }
1759
1616
  ));
1760
-
1617
+
1618
+ } catch (error) {
1619
+ handleError(error, spinner);
1620
+ }
1621
+ });
1622
+
1623
+ /**
1624
+ * Docs Command Group - NEW (Documentation Management)
1625
+ */
1626
+ const docsCommand = program
1627
+ .command('docs')
1628
+ .description('Documentation management commands');
1629
+
1630
+ // Docs sync
1631
+ docsCommand
1632
+ .command('sync')
1633
+ .description('Sync documentation with component guidelines')
1634
+ .option('--validate', 'Validate documentation after sync', true)
1635
+ .action(async (options) => {
1636
+ const spinner = ora('Syncing documentation...').start();
1637
+
1638
+ try {
1639
+ await syncDocumentation();
1640
+ spinner.stop();
1641
+
1642
+ if (options.validate) {
1643
+ await validateDocumentation();
1644
+ }
1645
+ } catch (error) {
1646
+ handleError(error, spinner);
1647
+ }
1648
+ });
1649
+
1650
+ // Docs validate
1651
+ docsCommand
1652
+ .command('validate')
1653
+ .description('Validate documentation completeness')
1654
+ .action(async () => {
1655
+ try {
1656
+ await validateDocumentation();
1657
+ } catch (error) {
1658
+ handleError(error);
1659
+ }
1660
+ });
1661
+
1662
+ // Docs generate CLI
1663
+ docsCommand
1664
+ .command('generate-cli')
1665
+ .description('Generate CLI documentation from commands')
1666
+ .action(async () => {
1667
+ const spinner = ora('Generating CLI documentation...').start();
1668
+
1669
+ try {
1670
+ const result = await generateCLIDocumentation();
1671
+ spinner.succeed(chalk.green('CLI documentation generated'));
1672
+ console.log(chalk.gray(` → ${result.file}`));
1761
1673
  } catch (error) {
1762
1674
  handleError(error, spinner);
1763
1675
  }
@@ -1771,21 +1683,20 @@ program
1771
1683
  .description('Diagnose common issues with your Atomix setup')
1772
1684
  .action(async () => {
1773
1685
  const spinner = ora('Running diagnostics...').start();
1774
-
1686
+
1775
1687
  try {
1776
1688
  const checks = [];
1777
-
1689
+
1778
1690
  // Check Node version
1779
- const nodeVersion = process.version;
1780
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
1691
+ const versionCheck = checkNodeVersion('18.0.0');
1781
1692
  checks.push({
1782
1693
  name: 'Node.js Version',
1783
- status: majorVersion >= 16 ? '✅' : '❌',
1784
- message: majorVersion >= 16
1785
- ? `${nodeVersion} (supported)`
1786
- : `${nodeVersion} (requires Node 16+)`,
1694
+ status: versionCheck.compatible ? '✅' : '❌',
1695
+ message: versionCheck.compatible
1696
+ ? `v${versionCheck.current} (supported)`
1697
+ : `v${versionCheck.current} (requires Node ${versionCheck.required}+)`,
1787
1698
  });
1788
-
1699
+
1789
1700
  // Check Atomix installation
1790
1701
  const atomixPath = join(process.cwd(), 'node_modules', '@shohojdhara', 'atomix');
1791
1702
  checks.push({
@@ -1795,7 +1706,7 @@ program
1795
1706
  ? 'Installed correctly'
1796
1707
  : 'Not found - run: npm install @shohojdhara/atomix',
1797
1708
  });
1798
-
1709
+
1799
1710
  // Check for required dependencies
1800
1711
  const requiredDeps = ['react', 'react-dom', 'sass'];
1801
1712
  for (const dep of requiredDeps) {
@@ -1808,7 +1719,7 @@ program
1808
1719
  : 'Missing (may be required for some features)',
1809
1720
  });
1810
1721
  }
1811
-
1722
+
1812
1723
  // Check for configuration files
1813
1724
  const configFiles = ['.atomixrc', 'atomix.config.js', 'atomix.config.json', 'theme.config.ts'];
1814
1725
  let hasConfig = false;
@@ -1820,7 +1731,7 @@ program
1820
1731
  break;
1821
1732
  }
1822
1733
  }
1823
-
1734
+
1824
1735
  checks.push({
1825
1736
  name: 'Configuration File',
1826
1737
  status: hasConfig ? '✅' : '💡',
@@ -1828,12 +1739,12 @@ program
1828
1739
  ? `Configuration found (${configFile})`
1829
1740
  : 'No config file (using defaults)',
1830
1741
  });
1831
-
1742
+
1832
1743
  // Check theme CLI availability
1833
1744
  const themeCLIAvailable = await import('./cli/theme-bridge.js')
1834
1745
  .then(m => m.isThemeCLIAvailable())
1835
1746
  .catch(() => false);
1836
-
1747
+
1837
1748
  checks.push({
1838
1749
  name: 'Theme CLI',
1839
1750
  status: themeCLIAvailable ? '✅' : '⚠️',
@@ -1841,21 +1752,21 @@ program
1841
1752
  ? 'Available'
1842
1753
  : 'Theme devtools not found',
1843
1754
  });
1844
-
1755
+
1845
1756
  spinner.stop();
1846
-
1757
+
1847
1758
  // Display results
1848
1759
  console.log(chalk.bold('\n🏥 Atomix Doctor Report\n'));
1849
- console.log(chalk.gray('=' .repeat(50)));
1850
-
1760
+ console.log(chalk.gray('='.repeat(50)));
1761
+
1851
1762
  checks.forEach(check => {
1852
1763
  console.log(`${check.status} ${chalk.bold(check.name)}`);
1853
1764
  console.log(` ${chalk.gray(check.message)}\n`);
1854
1765
  });
1855
-
1766
+
1856
1767
  const hasIssues = checks.some(c => c.status === '❌');
1857
1768
  const hasWarnings = checks.some(c => c.status === '⚠️');
1858
-
1769
+
1859
1770
  if (hasIssues) {
1860
1771
  console.log(chalk.red('\n❌ Some issues need attention'));
1861
1772
  } else if (hasWarnings) {
@@ -1863,425 +1774,13 @@ program
1863
1774
  } else {
1864
1775
  console.log(chalk.green('\n✅ Everything looks good!'));
1865
1776
  }
1866
-
1777
+
1867
1778
  } catch (error) {
1868
1779
  handleError(error, spinner);
1869
1780
  }
1870
1781
  });
1871
1782
 
1872
- // Token generation functions
1873
- function generateColorTokens() {
1874
- return `// Custom Color Tokens
1875
- // Generated by Atomix CLI
1876
- // =============================================================================
1877
-
1878
- // Brand Colors
1879
- // Customize these to match your brand identity
1880
- $custom-primary-1: #fff9e6 !default;
1881
- $custom-primary-2: #fff4cc !default;
1882
- $custom-primary-3: #ffe699 !default;
1883
- $custom-primary-4: #ffd966 !default;
1884
- $custom-primary-5: #ffcc33 !default;
1885
- $custom-primary-6: #ffb800 !default; // Main brand color
1886
- $custom-primary-7: #e6a600 !default;
1887
- $custom-primary-8: #cc9400 !default;
1888
- $custom-primary-9: #b38200 !default;
1889
- $custom-primary-10: #997000 !default;
1890
-
1891
- // Semantic Colors
1892
- $custom-success: #22c55e !default;
1893
- $custom-warning: #eab308 !default;
1894
- $custom-error: #ef4444 !default;
1895
- $custom-info: #3b82f6 !default;
1896
-
1897
- // Neutral Colors
1898
- $custom-gray-1: #f9fafb !default;
1899
- $custom-gray-2: #f3f4f6 !default;
1900
- $custom-gray-3: #e5e7eb !default;
1901
- $custom-gray-4: #d1d5db !default;
1902
- $custom-gray-5: #9ca3af !default;
1903
- $custom-gray-6: #6b7280 !default;
1904
- $custom-gray-7: #4b5563 !default;
1905
- $custom-gray-8: #374151 !default;
1906
- $custom-gray-9: #1f2937 !default;
1907
- $custom-gray-10: #111827 !default;
1908
-
1909
- // Background Colors
1910
- $custom-body-bg: #ffffff !default;
1911
- $custom-body-bg-dark: #1f2937 !default;
1912
-
1913
- // Text Colors
1914
- $custom-body-color: $custom-gray-10 !default;
1915
- $custom-body-color-dark: #ffffff !default;
1916
-
1917
- // Link Colors
1918
- $custom-link-color: $custom-primary-6 !default;
1919
- $custom-link-hover-color: $custom-primary-7 !default;
1920
-
1921
- // Border Colors
1922
- $custom-border-color: $custom-gray-3 !default;
1923
- $custom-border-color-dark: $custom-gray-7 !default;
1924
-
1925
- // Focus Colors
1926
- $custom-focus-color: $custom-primary-5 !default;
1927
- $custom-focus-color-dark: $custom-primary-4 !default;
1928
-
1929
- // Export custom colors to override defaults
1930
- $primary: $custom-primary-6 !default;
1931
- $success: $custom-success !default;
1932
- $warning: $custom-warning !default;
1933
- $error: $custom-error !default;
1934
- $info: $custom-info !default;
1935
-
1936
- // Dark mode overrides
1937
- $body-bg-dark: $custom-body-bg-dark !default;
1938
- $body-color-dark: $custom-body-color-dark !default;
1939
- $border-color-dark: $custom-border-color-dark !default;
1940
- `;
1941
- }
1942
-
1943
- function generateSpacingTokens() {
1944
- return `// Custom Spacing Tokens
1945
- // Generated by Atomix CLI
1946
- // =============================================================================
1947
-
1948
- // Base spacing unit (change this to scale all spacing)
1949
- $custom-spacing-base: 0.25rem !default; // 4px
1950
-
1951
- // Spacing scale
1952
- $custom-spacing-0: 0 !default;
1953
- $custom-spacing-1: $custom-spacing-base !default; // 4px
1954
- $custom-spacing-2: calc($custom-spacing-base * 2) !default; // 8px
1955
- $custom-spacing-3: calc($custom-spacing-base * 3) !default; // 12px
1956
- $custom-spacing-4: calc($custom-spacing-base * 4) !default; // 16px
1957
- $custom-spacing-5: calc($custom-spacing-base * 5) !default; // 20px
1958
- $custom-spacing-6: calc($custom-spacing-base * 6) !default; // 24px
1959
- $custom-spacing-7: calc($custom-spacing-base * 7) !default; // 28px
1960
- $custom-spacing-8: calc($custom-spacing-base * 8) !default; // 32px
1961
- $custom-spacing-9: calc($custom-spacing-base * 9) !default; // 36px
1962
- $custom-spacing-10: calc($custom-spacing-base * 10) !default; // 40px
1963
- $custom-spacing-11: calc($custom-spacing-base * 11) !default; // 44px
1964
- $custom-spacing-12: calc($custom-spacing-base * 12) !default; // 48px
1965
- $custom-spacing-14: calc($custom-spacing-base * 14) !default; // 56px
1966
- $custom-spacing-16: calc($custom-spacing-base * 16) !default; // 64px
1967
- $custom-spacing-20: calc($custom-spacing-base * 20) !default; // 80px
1968
- $custom-spacing-24: calc($custom-spacing-base * 24) !default; // 96px
1969
- $custom-spacing-28: calc($custom-spacing-base * 28) !default; // 112px
1970
- $custom-spacing-32: calc($custom-spacing-base * 32) !default; // 128px
1971
- $custom-spacing-36: calc($custom-spacing-base * 36) !default; // 144px
1972
- $custom-spacing-40: calc($custom-spacing-base * 40) !default; // 160px
1973
- $custom-spacing-44: calc($custom-spacing-base * 44) !default; // 176px
1974
- $custom-spacing-48: calc($custom-spacing-base * 48) !default; // 192px
1975
- $custom-spacing-52: calc($custom-spacing-base * 52) !default; // 208px
1976
- $custom-spacing-56: calc($custom-spacing-base * 56) !default; // 224px
1977
- $custom-spacing-60: calc($custom-spacing-base * 60) !default; // 240px
1978
- $custom-spacing-64: calc($custom-spacing-base * 64) !default; // 256px
1979
-
1980
- // Component-specific spacing
1981
- $custom-button-padding-x: $custom-spacing-4 !default;
1982
- $custom-button-padding-y: $custom-spacing-2 !default;
1983
- $custom-card-padding: $custom-spacing-6 !default;
1984
- $custom-modal-padding: $custom-spacing-8 !default;
1985
-
1986
- // Layout spacing
1987
- $custom-container-padding: $custom-spacing-4 !default;
1988
- $custom-grid-gap: $custom-spacing-6 !default;
1989
- $custom-section-spacing: $custom-spacing-16 !default;
1990
-
1991
- // Export to override defaults
1992
- $spacing-sizes: (
1993
- 0: $custom-spacing-0,
1994
- 1: $custom-spacing-1,
1995
- 2: $custom-spacing-2,
1996
- 3: $custom-spacing-3,
1997
- 4: $custom-spacing-4,
1998
- 5: $custom-spacing-5,
1999
- 6: $custom-spacing-6,
2000
- 7: $custom-spacing-7,
2001
- 8: $custom-spacing-8,
2002
- 9: $custom-spacing-9,
2003
- 10: $custom-spacing-10,
2004
- 12: $custom-spacing-12,
2005
- 16: $custom-spacing-16,
2006
- 20: $custom-spacing-20,
2007
- 24: $custom-spacing-24,
2008
- 32: $custom-spacing-32,
2009
- 40: $custom-spacing-40,
2010
- 48: $custom-spacing-48,
2011
- 56: $custom-spacing-56,
2012
- 64: $custom-spacing-64,
2013
- ) !default;
2014
- `;
2015
- }
2016
-
2017
- function generateTypographyTokens() {
2018
- return `// Custom Typography Tokens
2019
- // Generated by Atomix CLI
2020
- // =============================================================================
2021
-
2022
- // Font Families
2023
- $custom-font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
2024
- $custom-font-family-serif: Georgia, "Times New Roman", Times, serif !default;
2025
- $custom-font-family-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
2026
-
2027
- // Font Size Scale
2028
- $custom-font-size-xs: 0.75rem !default; // 12px
2029
- $custom-font-size-sm: 0.875rem !default; // 14px
2030
- $custom-font-size-base: 1rem !default; // 16px
2031
- $custom-font-size-lg: 1.125rem !default; // 18px
2032
- $custom-font-size-xl: 1.25rem !default; // 20px
2033
- $custom-font-size-2xl: 1.5rem !default; // 24px
2034
- $custom-font-size-3xl: 1.875rem !default; // 30px
2035
- $custom-font-size-4xl: 2.25rem !default; // 36px
2036
- $custom-font-size-5xl: 3rem !default; // 48px
2037
- $custom-font-size-6xl: 3.75rem !default; // 60px
2038
- $custom-font-size-7xl: 4.5rem !default; // 72px
2039
- $custom-font-size-8xl: 6rem !default; // 96px
2040
-
2041
- // Line Heights
2042
- $custom-line-height-tight: 1.2 !default;
2043
- $custom-line-height-base: 1.5 !default;
2044
- $custom-line-height-relaxed: 1.75 !default;
2045
- $custom-line-height-loose: 2 !default;
2046
-
2047
- // Font Weights
2048
- $custom-font-weight-light: 300 !default;
2049
- $custom-font-weight-normal: 400 !default;
2050
- $custom-font-weight-medium: 500 !default;
2051
- $custom-font-weight-semibold: 600 !default;
2052
- $custom-font-weight-bold: 700 !default;
2053
- $custom-font-weight-heavy: 800 !default;
2054
- $custom-font-weight-black: 900 !default;
2055
-
2056
- // Letter Spacing
2057
- $custom-letter-spacing-tight: -0.05em !default;
2058
- $custom-letter-spacing-normal: 0 !default;
2059
- $custom-letter-spacing-wide: 0.025em !default;
2060
- $custom-letter-spacing-wider: 0.05em !default;
2061
- $custom-letter-spacing-widest: 0.1em !default;
2062
-
2063
- // Heading Sizes
2064
- $custom-h1-font-size: $custom-font-size-5xl !default;
2065
- $custom-h2-font-size: $custom-font-size-4xl !default;
2066
- $custom-h3-font-size: $custom-font-size-3xl !default;
2067
- $custom-h4-font-size: $custom-font-size-2xl !default;
2068
- $custom-h5-font-size: $custom-font-size-xl !default;
2069
- $custom-h6-font-size: $custom-font-size-lg !default;
2070
-
2071
- // Export to override defaults
2072
- $font-family-base: $custom-font-family-sans !default;
2073
- $font-family-monospace: $custom-font-family-mono !default;
2074
- $font-size-base: $custom-font-size-base !default;
2075
- $font-size-sm: $custom-font-size-sm !default;
2076
- $font-size-lg: $custom-font-size-lg !default;
2077
- $line-height-base: $custom-line-height-base !default;
2078
- $font-weight-base: $custom-font-weight-normal !default;
2079
-
2080
- // Heading overrides
2081
- $h1-font-size: $custom-h1-font-size !default;
2082
- $h2-font-size: $custom-h2-font-size !default;
2083
- $h3-font-size: $custom-h3-font-size !default;
2084
- $h4-font-size: $custom-h4-font-size !default;
2085
- $h5-font-size: $custom-h5-font-size !default;
2086
- $h6-font-size: $custom-h6-font-size !default;
2087
- `;
2088
- }
2089
-
2090
- function generateShadowTokens() {
2091
- return `// Custom Box Shadow Tokens
2092
- // Generated by Atomix CLI
2093
- // =============================================================================
2094
-
2095
- // Shadow Colors
2096
- $custom-shadow-color: rgba(0, 0, 0, 0.1) !default;
2097
- $custom-shadow-color-dark: rgba(0, 0, 0, 0.2) !default;
2098
-
2099
- // Shadow Sizes
2100
- $custom-shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !default;
2101
- $custom-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !default;
2102
- $custom-shadow-base: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !default;
2103
- $custom-shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !default;
2104
- $custom-shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !default;
2105
- $custom-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !default;
2106
- $custom-shadow-2xl: 0 35px 60px -15px rgba(0, 0, 0, 0.3) !default;
2107
- $custom-shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06) !default;
2108
- $custom-shadow-none: none !default;
2109
-
2110
- // Component-specific shadows
2111
- $custom-button-shadow: $custom-shadow-sm !default;
2112
- $custom-button-shadow-hover: $custom-shadow-md !default;
2113
- $custom-card-shadow: $custom-shadow-base !default;
2114
- $custom-dropdown-shadow: $custom-shadow-lg !default;
2115
- $custom-modal-shadow: $custom-shadow-xl !default;
2116
- $custom-popover-shadow: $custom-shadow-lg !default;
2117
- $custom-tooltip-shadow: $custom-shadow-md !default;
2118
-
2119
- // Dark mode shadows
2120
- $custom-shadow-xs-dark: 0 1px 2px 0 rgba(0, 0, 0, 0.3) !default;
2121
- $custom-shadow-sm-dark: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px 0 rgba(0, 0, 0, 0.3) !default;
2122
- $custom-shadow-base-dark: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3) !default;
2123
- $custom-shadow-lg-dark: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4) !default;
2124
- $custom-shadow-xl-dark: 0 25px 50px -12px rgba(0, 0, 0, 0.6) !default;
2125
-
2126
- // Export to override defaults
2127
- $box-shadow: $custom-shadow-base !default;
2128
- $box-shadow-xs: $custom-shadow-xs !default;
2129
- $box-shadow-sm: $custom-shadow-sm !default;
2130
- $box-shadow-lg: $custom-shadow-lg !default;
2131
- $box-shadow-xl: $custom-shadow-xl !default;
2132
- $box-shadow-inset: $custom-shadow-inner !default;
2133
-
2134
- // Dark mode exports
2135
- $box-shadow-dark: $custom-shadow-base-dark !default;
2136
- $box-shadow-xs-dark: $custom-shadow-xs-dark !default;
2137
- $box-shadow-sm-dark: $custom-shadow-sm-dark !default;
2138
- $box-shadow-lg-dark: $custom-shadow-lg-dark !default;
2139
- $box-shadow-xl-dark: $custom-shadow-xl-dark !default;
2140
- `;
2141
- }
2142
-
2143
- function generateRadiusTokens() {
2144
- return `// Custom Border Radius Tokens
2145
- // Generated by Atomix CLI
2146
- // =============================================================================
2147
-
2148
- // Base radius unit
2149
- $custom-radius-base: 0.25rem !default; // 4px
2150
-
2151
- // Radius Scale
2152
- $custom-radius-none: 0 !default;
2153
- $custom-radius-sm: calc($custom-radius-base * 0.5) !default; // 2px
2154
- $custom-radius-base: $custom-radius-base !default; // 4px
2155
- $custom-radius-md: calc($custom-radius-base * 1.5) !default; // 6px
2156
- $custom-radius-lg: calc($custom-radius-base * 2) !default; // 8px
2157
- $custom-radius-xl: calc($custom-radius-base * 3) !default; // 12px
2158
- $custom-radius-2xl: calc($custom-radius-base * 4) !default; // 16px
2159
- $custom-radius-3xl: calc($custom-radius-base * 6) !default; // 24px
2160
- $custom-radius-4xl: calc($custom-radius-base * 8) !default; // 32px
2161
- $custom-radius-full: 9999px !default; // Fully rounded
2162
-
2163
- // Component-specific radius
2164
- $custom-button-radius: $custom-radius-md !default;
2165
- $custom-button-radius-sm: $custom-radius-sm !default;
2166
- $custom-button-radius-lg: $custom-radius-lg !default;
2167
- $custom-card-radius: $custom-radius-lg !default;
2168
- $custom-input-radius: $custom-radius-md !default;
2169
- $custom-badge-radius: $custom-radius-full !default;
2170
- $custom-chip-radius: $custom-radius-full !default;
2171
- $custom-tooltip-radius: $custom-radius-md !default;
2172
- $custom-modal-radius: $custom-radius-xl !default;
2173
- $custom-dropdown-radius: $custom-radius-lg !default;
2174
-
2175
- // Export to override defaults
2176
- $border-radius: $custom-radius-md !default;
2177
- $border-radius-sm: $custom-radius-sm !default;
2178
- $border-radius-lg: $custom-radius-lg !default;
2179
- $border-radius-xl: $custom-radius-xl !default;
2180
- $border-radius-xxl: $custom-radius-2xl !default;
2181
- $border-radius-3xl: $custom-radius-3xl !default;
2182
- $border-radius-4xl: $custom-radius-4xl !default;
2183
- $border-radius-pill: $custom-radius-full !default;
2184
-
2185
- // Component radius exports
2186
- $btn-border-radius: $custom-button-radius !default;
2187
- $btn-border-radius-sm: $custom-button-radius-sm !default;
2188
- $btn-border-radius-lg: $custom-button-radius-lg !default;
2189
- $card-border-radius: $custom-card-radius !default;
2190
- $input-border-radius: $custom-input-radius !default;
2191
- $badge-border-radius: $custom-badge-radius !default;
2192
- `;
2193
- }
2194
-
2195
- function generateAnimationTokens() {
2196
- return `// Custom Animation Tokens
2197
- // Generated by Atomix CLI
2198
- // =============================================================================
2199
-
2200
- // Transition Durations
2201
- $custom-duration-instant: 0s !default;
2202
- $custom-duration-fast: 0.15s !default;
2203
- $custom-duration-base: 0.3s !default;
2204
- $custom-duration-slow: 0.5s !default;
2205
- $custom-duration-slower: 0.7s !default;
2206
- $custom-duration-slowest: 1s !default;
2207
-
2208
- // Easing Functions
2209
- $custom-ease-linear: linear !default;
2210
- $custom-ease-in: cubic-bezier(0.4, 0, 1, 1) !default;
2211
- $custom-ease-out: cubic-bezier(0, 0, 0.2, 1) !default;
2212
- $custom-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1) !default;
2213
- $custom-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55) !default;
2214
- $custom-ease-smooth: cubic-bezier(0.23, 1, 0.32, 1) !default;
2215
-
2216
- // Transition Properties
2217
- $custom-transition-all: all $custom-duration-base $custom-ease-smooth !default;
2218
- $custom-transition-colors: background-color $custom-duration-base $custom-ease-smooth,
2219
- border-color $custom-duration-base $custom-ease-smooth,
2220
- color $custom-duration-base $custom-ease-smooth,
2221
- fill $custom-duration-base $custom-ease-smooth,
2222
- stroke $custom-duration-base $custom-ease-smooth !default;
2223
- $custom-transition-opacity: opacity $custom-duration-base $custom-ease-smooth !default;
2224
- $custom-transition-shadow: box-shadow $custom-duration-base $custom-ease-smooth !default;
2225
- $custom-transition-transform: transform $custom-duration-base $custom-ease-smooth !default;
2226
-
2227
- // Component-specific transitions
2228
- $custom-button-transition: $custom-transition-colors, $custom-transition-shadow, $custom-transition-transform !default;
2229
- $custom-link-transition: $custom-transition-colors, text-decoration-color $custom-duration-base $custom-ease-smooth !default;
2230
- $custom-input-transition: $custom-transition-colors, $custom-transition-shadow !default;
2231
- $custom-card-transition: $custom-transition-shadow, $custom-transition-transform !default;
2232
- $custom-modal-transition: $custom-transition-opacity, $custom-transition-transform !default;
2233
- $custom-dropdown-transition: $custom-transition-opacity, $custom-transition-transform !default;
2234
-
2235
- // Animation Keyframes (examples)
2236
- @keyframes custom-fade-in {
2237
- from { opacity: 0; }
2238
- to { opacity: 1; }
2239
- }
2240
-
2241
- @keyframes custom-slide-in-up {
2242
- from {
2243
- transform: translateY(10px);
2244
- opacity: 0;
2245
- }
2246
- to {
2247
- transform: translateY(0);
2248
- opacity: 1;
2249
- }
2250
- }
2251
-
2252
- @keyframes custom-scale-in {
2253
- from {
2254
- transform: scale(0.95);
2255
- opacity: 0;
2256
- }
2257
- to {
2258
- transform: scale(1);
2259
- opacity: 1;
2260
- }
2261
- }
2262
-
2263
- @keyframes custom-spin {
2264
- from { transform: rotate(0deg); }
2265
- to { transform: rotate(360deg); }
2266
- }
2267
1783
 
2268
- // Export to override defaults
2269
- $transition-fast: $custom-transition-all !default;
2270
- $transition-base: $custom-transition-all !default;
2271
- $transition-slow: all $custom-duration-slow $custom-ease-smooth !default;
2272
-
2273
- // Duration exports
2274
- $transition-duration-fast: $custom-duration-fast !default;
2275
- $transition-duration-base: $custom-duration-base !default;
2276
- $transition-duration-slow: $custom-duration-slow !default;
2277
-
2278
- // Easing exports
2279
- $easing-base: $custom-ease-smooth !default;
2280
- $easing-ease-in-out: $custom-ease-in-out !default;
2281
- $easing-ease-out: $custom-ease-out !default;
2282
- $easing-ease-in: $custom-ease-in !default;
2283
- `;
2284
- }
2285
1784
 
2286
1785
  // Parse arguments
2287
1786
  program.parse(process.argv);