@shohojdhara/atomix 0.5.0 → 0.5.2

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 (168) hide show
  1. package/atomix.config.ts +12 -0
  2. package/build-tools/webpack-loader.js +5 -4
  3. package/dist/atomix.css +230 -83
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +1 -1
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/webpack-loader.js +5 -4
  8. package/dist/charts.d.ts +24 -23
  9. package/dist/charts.js +271 -369
  10. package/dist/charts.js.map +1 -1
  11. package/dist/config.d.ts +624 -0
  12. package/dist/config.js +59 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core.d.ts +3 -2
  15. package/dist/core.js +342 -382
  16. package/dist/core.js.map +1 -1
  17. package/dist/forms.d.ts +4 -6
  18. package/dist/forms.js +233 -334
  19. package/dist/forms.js.map +1 -1
  20. package/dist/heavy.d.ts +11 -2
  21. package/dist/heavy.js +406 -445
  22. package/dist/heavy.js.map +1 -1
  23. package/dist/index.d.ts +109 -65
  24. package/dist/index.esm.js +654 -748
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +621 -717
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.min.js +1 -1
  29. package/dist/index.min.js.map +1 -1
  30. package/dist/layout.js +59 -60
  31. package/dist/layout.js.map +1 -1
  32. package/dist/theme.js +4 -4
  33. package/dist/theme.js.map +1 -1
  34. package/package.json +24 -9
  35. package/scripts/atomix-cli.js +15 -1
  36. package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
  37. package/scripts/cli/__tests__/detector.test.js +50 -0
  38. package/scripts/cli/__tests__/template-engine.test.js +23 -0
  39. package/scripts/cli/__tests__/test-setup.js +1 -133
  40. package/scripts/cli/commands/doctor.js +15 -3
  41. package/scripts/cli/commands/generate.js +113 -51
  42. package/scripts/cli/internal/ai-engine.js +30 -10
  43. package/scripts/cli/internal/complexity-utils.js +60 -0
  44. package/scripts/cli/internal/component-validator.js +49 -16
  45. package/scripts/cli/internal/generator.js +89 -36
  46. package/scripts/cli/internal/hook-generator.js +5 -2
  47. package/scripts/cli/internal/itcss-generator.js +16 -12
  48. package/scripts/cli/templates/next-templates.js +81 -30
  49. package/scripts/cli/templates/storybook-templates.js +12 -2
  50. package/scripts/cli/utils/detector.js +45 -7
  51. package/scripts/cli/utils/diagnostics.js +78 -0
  52. package/scripts/cli/utils/telemetry.js +13 -0
  53. package/src/components/Accordion/Accordion.stories.tsx +4 -0
  54. package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
  55. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
  56. package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
  57. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
  58. package/src/components/AtomixGlass/glass-utils.ts +51 -1
  59. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
  60. package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
  61. package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
  62. package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
  63. package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
  64. package/src/components/AtomixGlass/stories/types.ts +3 -3
  65. package/src/components/Button/Button.tsx +114 -57
  66. package/src/components/Callout/Callout.tsx +4 -4
  67. package/src/components/Chart/ChartRenderer.tsx +1 -1
  68. package/src/components/Chart/DonutChart.tsx +11 -8
  69. package/src/components/EdgePanel/EdgePanel.tsx +119 -115
  70. package/src/components/Form/Select.tsx +4 -4
  71. package/src/components/List/List.tsx +4 -4
  72. package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
  73. package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
  74. package/src/components/ProductReview/ProductReview.tsx +4 -2
  75. package/src/components/Rating/Rating.tsx +4 -2
  76. package/src/components/SectionIntro/SectionIntro.tsx +4 -2
  77. package/src/components/Steps/Steps.tsx +1 -1
  78. package/src/components/Tabs/Tabs.tsx +5 -5
  79. package/src/components/Testimonial/Testimonial.tsx +4 -2
  80. package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
  81. package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
  82. package/src/layouts/CssGrid/CssGrid.tsx +215 -0
  83. package/src/layouts/CssGrid/index.ts +8 -0
  84. package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
  85. package/src/layouts/CssGrid/scripts/index.js +43 -0
  86. package/src/layouts/Grid/scripts/Container.js +139 -0
  87. package/src/layouts/Grid/scripts/Grid.js +184 -0
  88. package/src/layouts/Grid/scripts/GridCol.js +273 -0
  89. package/src/layouts/Grid/scripts/Row.js +154 -0
  90. package/src/layouts/Grid/scripts/index.js +48 -0
  91. package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
  92. package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
  93. package/src/lib/composables/useAccordion.ts +5 -5
  94. package/src/lib/composables/useAtomixGlass.ts +111 -74
  95. package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
  96. package/src/lib/composables/useBarChart.ts +2 -2
  97. package/src/lib/composables/useChart.ts +3 -2
  98. package/src/lib/composables/useChartToolbar.ts +48 -66
  99. package/src/lib/composables/useDataTable.ts +1 -1
  100. package/src/lib/composables/useDatePicker.ts +2 -2
  101. package/src/lib/composables/useEdgePanel.ts +45 -54
  102. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
  103. package/src/lib/composables/usePhotoViewer.ts +2 -3
  104. package/src/lib/composables/usePieChart.ts +1 -1
  105. package/src/lib/composables/usePopover.ts +151 -139
  106. package/src/lib/composables/useSideMenu.ts +28 -41
  107. package/src/lib/composables/useSlider.ts +2 -6
  108. package/src/lib/composables/useTooltip.ts +2 -2
  109. package/src/lib/config/index.ts +39 -0
  110. package/src/lib/constants/components.ts +1 -0
  111. package/src/lib/theme/devtools/Comparator.tsx +1 -1
  112. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  113. package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
  114. package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
  115. package/src/lib/types/components.ts +1 -0
  116. package/src/styles/01-settings/_index.scss +1 -0
  117. package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
  118. package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
  119. package/src/styles/02-tools/_tools.glass.scss +6 -0
  120. package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
  121. package/src/styles/06-components/_components.atomix-glass.scss +160 -99
  122. package/scripts/cli/__tests__/README.md +0 -81
  123. package/scripts/cli/__tests__/basic.test.js +0 -116
  124. package/scripts/cli/__tests__/clean.test.js +0 -278
  125. package/scripts/cli/__tests__/component-generator.test.js +0 -332
  126. package/scripts/cli/__tests__/component-validator.test.js +0 -433
  127. package/scripts/cli/__tests__/generator.test.js +0 -613
  128. package/scripts/cli/__tests__/glass-motion.test.js +0 -256
  129. package/scripts/cli/__tests__/integration.test.js +0 -938
  130. package/scripts/cli/__tests__/migrate.test.js +0 -74
  131. package/scripts/cli/__tests__/security.test.js +0 -206
  132. package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
  133. package/scripts/cli/__tests__/token-manager.test.js +0 -251
  134. package/scripts/cli/__tests__/token-provider.test.js +0 -361
  135. package/scripts/cli/__tests__/utils.test.js +0 -165
  136. package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
  137. package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
  138. package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
  139. package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
  140. package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
  141. package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
  142. package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
  143. package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
  144. package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
  145. package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
  146. package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
  147. package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
  148. package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
  149. package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
  150. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
  151. package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
  152. package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
  153. package/src/components/TypedButton/TypedButton.tsx +0 -39
  154. package/src/components/TypedButton/index.ts +0 -2
  155. package/src/lib/composables/useBreadcrumb.ts +0 -81
  156. package/src/lib/composables/useChartInteractions.ts +0 -123
  157. package/src/lib/composables/useChartPerformance.ts +0 -347
  158. package/src/lib/composables/useDropdown.ts +0 -338
  159. package/src/lib/composables/useModal.ts +0 -110
  160. package/src/lib/composables/useTypedButton.ts +0 -66
  161. package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
  162. package/src/lib/utils/displacement-generator.ts +0 -92
  163. package/src/lib/utils/memoryMonitor.ts +0 -191
  164. package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
  165. package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
  166. package/src/styles/06-components/_components.testbutton.scss +0 -212
  167. package/src/styles/06-components/_components.testtypecheck.scss +0 -212
  168. package/src/styles/06-components/_components.typedbutton.scss +0 -212
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shohojdhara/atomix",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Atomix Design System - A modern component library for web applications",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -15,8 +15,11 @@
15
15
  "types": "dist/index.d.ts",
16
16
  "style": "dist/atomix.css",
17
17
  "sass": "src/styles/index.scss",
18
+ "unpkg": "dist/atomix.umd.min.js",
19
+ "jsdelivr": "dist/atomix.umd.min.js",
20
+ "browser": "dist/atomix.umd.min.js",
18
21
  "bin": {
19
- "atomix": "./scripts/atomix-cli.js"
22
+ "atomix": "scripts/atomix-cli.js"
20
23
  },
21
24
  "exports": {
22
25
  ".": {
@@ -111,12 +114,22 @@
111
114
  "import": "./dist/heavy.js",
112
115
  "default": "./dist/heavy.js"
113
116
  },
117
+ "./cdn": {
118
+ "import": "./dist/atomix.umd.min.js",
119
+ "require": "./dist/atomix.umd.min.js",
120
+ "default": "./dist/atomix.umd.min.js"
121
+ },
114
122
  "./build-tools": {
115
123
  "types": "./build-tools/index.d.ts",
116
124
  "import": "./build-tools/index.js",
117
125
  "require": "./build-tools/index.js",
118
126
  "default": "./build-tools/index.js"
119
127
  },
128
+ "./config": {
129
+ "types": "./dist/config.d.ts",
130
+ "import": "./dist/config.js",
131
+ "default": "./dist/config.js"
132
+ },
120
133
  "./package.json": "./package.json"
121
134
  },
122
135
  "dependencies": {
@@ -172,13 +185,14 @@
172
185
  "@testing-library/react": "^14.0.0",
173
186
  "@testing-library/user-event": "^14.0.0",
174
187
  "@types/node": "^20.0.0",
175
- "@types/react": "^18.0.0",
176
- "@types/react-dom": "^18.0.0",
188
+ "@types/react": "^19.0.0",
189
+ "@types/react-dom": "^19.0.0",
177
190
  "@typescript-eslint/eslint-plugin": "^6.0.0",
178
191
  "@typescript-eslint/parser": "^6.0.0",
179
192
  "@vitejs/plugin-react": "^4.0.0",
180
193
  "@vitest/coverage-v8": "^4.0.18",
181
194
  "browserslist": "^4.23.0",
195
+ "chromatic": "^15.3.1",
182
196
  "concurrently": "^8.0.0",
183
197
  "eslint": "^8.0.0",
184
198
  "eslint-plugin-import": "^2.29.0",
@@ -193,8 +207,8 @@
193
207
  "postcss-import": "^15.0.0",
194
208
  "postcss-preset-env": "^9.0.0",
195
209
  "prettier": "^3.0.0",
196
- "react": "^18.0.0",
197
- "react-dom": "^18.0.0",
210
+ "react": "^19.0.0",
211
+ "react-dom": "^19.0.0",
198
212
  "rimraf": "^5.0.0",
199
213
  "rollup": "^3.0.0",
200
214
  "rollup-plugin-dts": "^6.0.0",
@@ -207,7 +221,7 @@
207
221
  "ts-node": "^10.9.0",
208
222
  "tslib": "^2.6.0",
209
223
  "tsx": "^4.21.0",
210
- "typescript": "^5.0.0",
224
+ "typescript": "^6.0.2",
211
225
  "vite": "^4.0.0",
212
226
  "vitest": "^0.34.0"
213
227
  },
@@ -220,6 +234,7 @@
220
234
  "build:styles": "rollup -c rollup.config.styles.js",
221
235
  "build:themes": "rollup -c rollup/config/themes.js",
222
236
  "build:parallel": "concurrently \"npm:build:js\" \"npm:build:types\" \"npm:build:styles\"",
237
+ "build:umd": "rollup -c rollup.config.umd.js",
223
238
  "build:cli": "rollup -c rollup.config.cli.js",
224
239
  "build:cli:dev": "rollup -c rollup.config.cli.js --environment NODE_ENV:development",
225
240
  "build:analyze": "ANALYZE=true rollup -c",
@@ -295,8 +310,8 @@
295
310
  "homepage": "https://github.com/Shohojdhara/atomix#readme",
296
311
  "peerDependencies": {
297
312
  "@phosphor-icons/react": "2.1.10",
298
- "react": "^18.0.0",
299
- "react-dom": "^18.0.0"
313
+ "react": ">=18.0.0",
314
+ "react-dom": ">=18.0.0"
300
315
  },
301
316
  "engines": {
302
317
  "node": ">=18.0.0",
@@ -137,8 +137,22 @@ program
137
137
  .option('-i, --interactive', 'Interactive mode', false)
138
138
  .option('-p, --path <path>', 'Output path', './src/components')
139
139
  .option('--prompt <prompt>', 'AI prompt for generating component')
140
- .option('--complexity <level>', 'Complexity (simple|medium|complex)', 'medium')
140
+ .option('--ai-provider <provider>', 'AI provider override (openai|anthropic)')
141
+ .option('--ai-model <model>', 'AI model override')
142
+ .option('--ai-temperature <temp>', 'AI creativity level 0.0-1.0', parseFloat, 0.7)
143
+ .option('--ai-preview', 'Preview AI output before writing files', false)
144
+ .option('--ai-max-tokens <tokens>', 'Maximum tokens for AI response', parseInt, 4000)
145
+ .option(
146
+ '--complexity <level>',
147
+ 'Template complexity (React: simple|medium|complex; Next: simple|client|complex). Default is framework-specific.'
148
+ )
149
+ .option('--framework <name>', 'Force framework: react, next, or vanilla (skips detection)')
150
+ .option('--storybook', 'Generate Storybook story', true)
151
+ .option('--hook', 'Generate composable hook', true)
152
+ .option('--styles', 'Generate ITCSS styles', true)
153
+ .option('--tests', 'Generate unit test file', false)
141
154
  .option('--validate', 'Validate after generation', true)
155
+ .option('--skip-validate', 'Skip validation after generation', false)
142
156
  .action(async (type, name, options) => {
143
157
  try {
144
158
  await generateAction(type, name, options);
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ resolveDefaultComplexity,
4
+ normalizeComplexityForFramework,
5
+ resolveEffectiveComplexity
6
+ } from '../internal/complexity-utils.js';
7
+
8
+ describe('complexity-utils', () => {
9
+ it('resolveDefaultComplexity: next is simple, react is medium', () => {
10
+ expect(resolveDefaultComplexity('next')).toBe('simple');
11
+ expect(resolveDefaultComplexity('react')).toBe('medium');
12
+ expect(resolveDefaultComplexity('vanilla')).toBe('medium');
13
+ });
14
+
15
+ it('normalizeComplexityForFramework maps next+medium to simple', () => {
16
+ expect(normalizeComplexityForFramework('next', 'medium')).toBe('simple');
17
+ expect(normalizeComplexityForFramework('next', 'client')).toBe('client');
18
+ });
19
+
20
+ it('resolveEffectiveComplexity uses explicit CLI value when set', () => {
21
+ expect(resolveEffectiveComplexity('react', 'simple')).toBe('simple');
22
+ expect(resolveEffectiveComplexity('next', undefined)).toBe('simple');
23
+ });
24
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { detectFramework } from '../utils/detector.js';
6
+
7
+ function writePackage(root, deps = {}, devDeps = {}) {
8
+ writeFileSync(
9
+ join(root, 'package.json'),
10
+ JSON.stringify({ name: 'fixture', dependencies: deps, devDependencies: devDeps }, null, 2)
11
+ );
12
+ }
13
+
14
+ describe('detectFramework', () => {
15
+ let dir;
16
+
17
+ beforeEach(() => {
18
+ dir = mkdtempSync(join(tmpdir(), 'atomix-det-'));
19
+ });
20
+
21
+ afterEach(() => {
22
+ rmSync(dir, { recursive: true, force: true });
23
+ });
24
+
25
+ it('returns vanilla without package.json', async () => {
26
+ expect(await detectFramework(join(dir, 'empty'))).toBe('vanilla');
27
+ });
28
+
29
+ it('next dependency wins over react', async () => {
30
+ writePackage(dir, { react: '^18', next: '^14' });
31
+ expect(await detectFramework(dir)).toBe('next');
32
+ });
33
+
34
+ it('vite + react is react (not next) when next.config absent', async () => {
35
+ writePackage(dir, { react: '^18', vite: '^5' });
36
+ writeFileSync(join(dir, 'vite.config.ts'), 'export default {}');
37
+ expect(await detectFramework(dir)).toBe('react');
38
+ });
39
+
40
+ it('next.config.ts implies next without next in deps', async () => {
41
+ writePackage(dir, { react: '^18' });
42
+ writeFileSync(join(dir, 'next.config.ts'), 'export default {}');
43
+ expect(await detectFramework(dir)).toBe('next');
44
+ });
45
+
46
+ it('respects framework override', async () => {
47
+ writePackage(dir, { react: '^18', next: '^14' });
48
+ expect(await detectFramework(dir, { framework: 'react' })).toBe('react');
49
+ });
50
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { templateEngine } from '../internal/template-engine.js';
3
+
4
+ describe('templateEngine.selectTemplate', () => {
5
+ it('resolves react × medium × component', () => {
6
+ const fn = templateEngine.selectTemplate('react', 'medium', 'component');
7
+ expect(typeof fn).toBe('function');
8
+ });
9
+
10
+ it('resolves next × simple × component', () => {
11
+ const fn = templateEngine.selectTemplate('next', 'simple', 'component');
12
+ expect(typeof fn).toBe('function');
13
+ });
14
+
15
+ it('throws for next × medium', () => {
16
+ expect(() => templateEngine.selectTemplate('next', 'medium', 'component')).toThrow();
17
+ });
18
+
19
+ it('resolves vanilla component without complexity validation', () => {
20
+ const fn = templateEngine.selectTemplate('vanilla', 'anything', 'component');
21
+ expect(typeof fn).toBe('function');
22
+ });
23
+ });
@@ -1,135 +1,3 @@
1
1
  /**
2
- * Test Setup for CLI Tests
2
+ * CLI test setup (node environment)
3
3
  */
4
-
5
- import { vi } from 'vitest';
6
-
7
- // Mock external dependencies
8
- vi.mock('ora', () => ({
9
- default: vi.fn(() => ({
10
- start: vi.fn(() => ({
11
- succeed: vi.fn(),
12
- fail: vi.fn(),
13
- stop: vi.fn(),
14
- text: ''
15
- }))
16
- }))
17
- }));
18
-
19
- vi.mock('inquirer', () => ({
20
- prompt: vi.fn()
21
- }));
22
-
23
- vi.mock('chalk', () => ({
24
- default: {
25
- green: vi.fn((text) => text),
26
- red: vi.fn((text) => text),
27
- yellow: vi.fn((text) => text),
28
- cyan: vi.fn((text) => text),
29
- gray: vi.fn((text) => text),
30
- blue: vi.fn((text) => text),
31
- bold: {
32
- green: vi.fn((text) => text),
33
- red: vi.fn((text) => text),
34
- yellow: vi.fn((text) => text),
35
- cyan: vi.fn((text) => text),
36
- blue: vi.fn((text) => text)
37
- }
38
- }
39
- }));
40
-
41
- vi.mock('boxen', () => ({
42
- default: vi.fn((text) => text)
43
- }));
44
-
45
- vi.mock('chokidar', () => ({
46
- default: vi.fn(() => ({
47
- on: vi.fn(),
48
- close: vi.fn()
49
- }))
50
- }));
51
-
52
- // Mock file system operations
53
- vi.mock('fs/promises', async () => {
54
- const actual = await vi.importActual('fs/promises');
55
- return {
56
- ...actual,
57
- writeFile: vi.fn(actual.writeFile),
58
- readFile: vi.fn(actual.readFile),
59
- mkdir: vi.fn(actual.mkdir),
60
- access: vi.fn(actual.access),
61
- stat: vi.fn(actual.stat),
62
- rm: vi.fn(actual.rm)
63
- };
64
- });
65
-
66
- // Mock process.cwd for consistent test environment
67
- const originalCwd = process.cwd;
68
-
69
- beforeEach(() => {
70
- // Reset all mocks before each test
71
- vi.clearAllMocks();
72
- });
73
-
74
- afterEach(() => {
75
- // Restore original process.cwd
76
- process.cwd = originalCwd;
77
- });
78
-
79
- // Global test utilities
80
- global.createMockTempDir = () => '/tmp/atomix-test-' + Math.random().toString(36).substr(2, 9);
81
-
82
- global.mockComponentStructure = (componentName, content = '') => ({
83
- [`${componentName}.tsx`]: content || `
84
- import React, { forwardRef } from 'react';
85
-
86
- export const ${componentName} = forwardRef<HTMLDivElement, ${componentName}Props>(
87
- ({ children, className = '', ...props }, ref) => {
88
- return (
89
- <div ref={ref} className={className} {...props}>
90
- {children}
91
- </div>
92
- );
93
- }
94
- );
95
-
96
- interface ${componentName}Props {
97
- children?: React.ReactNode;
98
- className?: string;
99
- }
100
- `,
101
- 'index.ts': `export { ${componentName} } from './${componentName}';`,
102
- [`${componentName}.stories.tsx`]: `
103
- import type { Meta, StoryObj } from '@storybook/react';
104
- import { ${componentName} } from './${componentName}';
105
-
106
- const meta: Meta<typeof ${componentName}> = {
107
- title: 'Components/${componentName}',
108
- component: ${componentName},
109
- parameters: {
110
- layout: 'centered',
111
- },
112
- tags: ['autodocs'],
113
- };
114
-
115
- export default meta;
116
- type Story = StoryObj<typeof meta>;
117
-
118
- export const Default: Story = {
119
- args: {
120
- children: 'Test ${componentName}',
121
- },
122
- };
123
- `,
124
- [`${componentName}.test.tsx`]: `
125
- import { render, screen } from '@testing-library/react';
126
- import { ${componentName} } from './${componentName}';
127
-
128
- describe('${componentName}', () => {
129
- it('renders correctly', () => {
130
- render(<${componentName}>Test</${componentName}>);
131
- expect(screen.getByText('Test')).toBeInTheDocument();
132
- });
133
- });
134
- `
135
- });
@@ -4,7 +4,15 @@
4
4
  */
5
5
 
6
6
  import { logger } from '../utils/logger.js';
7
- import { checkRuntimes, checkProjectStructure, checkConfig, checkPermissions, checkPlugins, checkTokens } from '../utils/diagnostics.js';
7
+ import {
8
+ checkRuntimes,
9
+ checkProjectStructure,
10
+ checkConfig,
11
+ checkPermissions,
12
+ checkPlugins,
13
+ checkTokens,
14
+ checkGenerator
15
+ } from '../utils/diagnostics.js';
8
16
  import chalk from 'chalk';
9
17
 
10
18
  /** Short descriptions for each doctor check (for --explain) */
@@ -19,7 +27,10 @@ const DOCTOR_CHECK_DESCRIPTIONS = {
19
27
  'Permissions: .': 'Read/write access to project root.',
20
28
  'Permissions: src': 'Read/write access to src directory.',
21
29
  'Plugins': 'Reports plugins registered in atomix.config.',
22
- 'Tokens': 'Design token discovery: src/styles/01-settings/_settings.*.scss (optional).'
30
+ 'Tokens': 'Design token discovery: src/styles/01-settings/_settings.*.scss (optional).',
31
+ 'Generator: output path': 'Writable directory for atomix generate (from config or ./src/components).',
32
+ 'Storybook (optional)': 'Detects Storybook-related dependencies for generated stories.',
33
+ 'Generator: template registry': 'Ensures CLI template engine exposes react, next, and vanilla.'
23
34
  };
24
35
 
25
36
  /**
@@ -46,6 +57,7 @@ export async function doctorAction(options = {}) {
46
57
  const permissions = await checkPermissions();
47
58
  const plugins = await checkPlugins();
48
59
  const tokens = await checkTokens();
60
+ const generator = await checkGenerator();
49
61
 
50
62
  spinner.stop();
51
63
 
@@ -55,7 +67,7 @@ export async function doctorAction(options = {}) {
55
67
  margin: 1
56
68
  });
57
69
 
58
- const allResults = [...runtimes, ...structure, ...config, ...permissions, ...plugins, ...tokens];
70
+ const allResults = [...runtimes, ...structure, ...config, ...permissions, ...plugins, ...tokens, ...generator];
59
71
  let issuesFound = false;
60
72
 
61
73
  for (const result of allResults) {
@@ -11,6 +11,45 @@ import { filesystem } from '../internal/filesystem.js';
11
11
  import { validateComponentName } from '../utils/validation.js';
12
12
  import { hookManager } from '../internal/hooks.js';
13
13
  import { validateComponent } from '../internal/validator.js';
14
+ import { configLoader } from '../internal/config-loader.js';
15
+ import { detectFramework } from '../utils/detector.js';
16
+ import { resolveDefaultComplexity } from '../internal/complexity-utils.js';
17
+ import { telemetry } from '../utils/telemetry.js';
18
+
19
+ /**
20
+ * Merge CLI options with atomix.config generator section (CLI wins).
21
+ * @param {Object} options - Commander options
22
+ * @returns {Promise<Object>}
23
+ */
24
+ async function buildGenerateConfig(name, options) {
25
+ const cwd = process.cwd();
26
+ const loaded = await configLoader.load(cwd);
27
+ const gen = loaded.generator || {};
28
+
29
+ const outputPath = options.path ?? gen.outputPath ?? './src/components';
30
+ const framework = options.framework ?? gen.framework;
31
+ const prefix = loaded.prefix ?? 'atomix';
32
+ const storybookCssImport = gen.storybookCssImport;
33
+ const hookOutputDir = gen.hookOutputDir;
34
+
35
+ const complexity =
36
+ options.complexity !== undefined && options.complexity !== null && options.complexity !== ''
37
+ ? options.complexity
38
+ : undefined;
39
+
40
+ const features = determineFeatures(options, gen.features || {});
41
+
42
+ return {
43
+ name,
44
+ complexity,
45
+ features,
46
+ outputPath,
47
+ framework,
48
+ prefix,
49
+ storybookCssImport,
50
+ hookOutputDir
51
+ };
52
+ }
14
53
 
15
54
  /**
16
55
  * Action logic for generating components
@@ -19,16 +58,18 @@ import { validateComponent } from '../internal/validator.js';
19
58
  * @param {Object} options - CLI options
20
59
  */
21
60
  export async function generateAction(type, name, options) {
22
- let config = {
23
- name,
24
- complexity: options.complexity || 'medium',
25
- features: determineFeatures(options),
26
- outputPath: options.path || './src/components'
27
- };
61
+ let config = await buildGenerateConfig(name, options);
28
62
 
29
63
  if (options.interactive) {
30
- config = await promptInteractive();
31
- if (!config) return;
64
+ const interactive = await promptInteractive();
65
+ if (!interactive) return;
66
+ config = {
67
+ ...config,
68
+ ...interactive,
69
+ name: interactive.name,
70
+ complexity: interactive.complexity,
71
+ features: interactive.features
72
+ };
32
73
  }
33
74
 
34
75
  // Pre-generation hook
@@ -49,7 +90,6 @@ export async function generateAction(type, name, options) {
49
90
  const spinner = logger.spinner(`Generating ${type}: ${config.name}...`).start();
50
91
 
51
92
  try {
52
- // Validation
53
93
  const nameValidation = await validateComponentName(config.name);
54
94
  if (!nameValidation.isValid) {
55
95
  throw new AtomixCLIError(
@@ -76,12 +116,23 @@ export async function generateAction(type, name, options) {
76
116
  );
77
117
  }
78
118
 
79
- // Execution
119
+ const detectedFw = await detectFramework(process.cwd(), { framework: config.framework });
120
+ telemetry.recordExtra({
121
+ generateType: type,
122
+ framework: detectedFw,
123
+ componentName: config.name
124
+ });
125
+
80
126
  let path;
81
127
  if (options.prompt) {
82
128
  path = await generator.generateAIComponent(config.name, options.prompt, {
83
129
  ...config,
84
- logger: { debug: (msg) => logger.debug(msg) }
130
+ logger: { debug: (msg) => logger.debug(msg) },
131
+ aiProvider: options.aiProvider,
132
+ aiModel: options.aiModel,
133
+ aiTemperature: options.aiTemperature,
134
+ aiPreview: options.aiPreview,
135
+ aiMaxTokens: options.aiMaxTokens
85
136
  });
86
137
  } else {
87
138
  path = await generator.generateComponent(config.name, {
@@ -92,9 +143,8 @@ export async function generateAction(type, name, options) {
92
143
 
93
144
  spinner.succeed(`Generated component ${config.name} at ${path}`);
94
145
 
95
- if (options.validate) {
146
+ if (!options.skipValidate && options.validate !== false) {
96
147
  let report = await generator.validate(config.name, path);
97
- // Component-scoped A11y and token validation (Phase 2: design system creator)
98
148
  if (type === 'component') {
99
149
  const componentReport = await validateComponent(config.name, process.cwd());
100
150
  const componentIssues = componentReport.issues.map(
@@ -104,7 +154,6 @@ export async function generateAction(type, name, options) {
104
154
  report.valid = report.valid && componentReport.valid;
105
155
  }
106
156
 
107
- // Validation hook
108
157
  try {
109
158
  report = await hookManager.trigger('onValidate', report);
110
159
  } catch (error) {
@@ -117,7 +166,6 @@ export async function generateAction(type, name, options) {
117
166
  }
118
167
  }
119
168
 
120
- // Post-build hook (using generated path as asset)
121
169
  try {
122
170
  await hookManager.trigger('postBuild', [path]);
123
171
  } catch (error) {
@@ -125,11 +173,9 @@ export async function generateAction(type, name, options) {
125
173
  }
126
174
 
127
175
  logger.box(`🎉 Component ${config.name} ready!\nRun: atomix validate component ${config.name}`);
128
-
129
176
  } catch (error) {
130
177
  spinner.fail('Generation failed');
131
-
132
- // Enhance error context for known error types
178
+
133
179
  if (error.code === 'TEMPLATE_NOT_FOUND') {
134
180
  error.suggestions.push('Run `atomix doctor` to check template availability');
135
181
  } else if (error.code === 'FRAMEWORK_DETECTION_FAILED') {
@@ -137,48 +183,62 @@ export async function generateAction(type, name, options) {
137
183
  } else if (error.code === 'FILE_WRITE_FAILED') {
138
184
  error.suggestions.push('Check disk space and file permissions');
139
185
  }
140
-
186
+
141
187
  throw error;
142
188
  }
143
189
  }
144
190
 
145
191
  /**
146
- * Determine which features to enable based on CLI options
147
192
  * @param {Object} options - CLI options
148
- * @returns {string[]} Array of feature names
193
+ * @param {Object} genFeatures - config.generator.features
149
194
  */
150
- function determineFeatures(options) {
195
+ function determineFeatures(options, genFeatures = {}) {
196
+ let storybook = genFeatures.storybook !== false;
197
+ let hook = genFeatures.hook !== false;
198
+ let styles = genFeatures.styles !== false;
199
+ let tests = genFeatures.tests === true;
200
+
201
+ if (options.storybook === false) storybook = false;
202
+ if (options.hook === false) hook = false;
203
+ if (options.styles === false) styles = false;
204
+ if (options.tests) tests = true;
205
+
151
206
  const features = [];
152
-
153
- // Default features (always enabled unless explicitly disabled)
154
- if (options.storybook !== false) {
155
- features.push('storybook');
156
- }
157
-
158
- if (options.hook !== false) {
159
- features.push('hook');
160
- }
161
-
162
- if (options.styles !== false) {
163
- features.push('styles');
164
- }
165
-
166
- // Optional features (enabled by flag)
167
- if (options.tests === true) {
168
- features.push('tests');
169
- }
170
-
207
+ if (storybook) features.push('storybook');
208
+ if (hook) features.push('hook');
209
+ if (styles) features.push('styles');
210
+ if (tests) features.push('tests');
211
+
171
212
  return features;
172
213
  }
173
214
 
174
215
  /**
175
216
  * Interactive mode handler
176
- * Prompts user for component configuration
177
217
  * @returns {Promise<Object>} Configuration object
178
218
  */
179
219
  async function promptInteractive() {
180
220
  logger.info('🎨 Interactive Component Generator');
181
-
221
+
222
+ const cwd = process.cwd();
223
+ const loaded = await configLoader.load(cwd);
224
+ const gen = loaded.generator || {};
225
+
226
+ const framework = await detectFramework(cwd, {});
227
+ const defaultComplexity = resolveDefaultComplexity(framework);
228
+ const complexityChoices =
229
+ framework === 'next'
230
+ ? [
231
+ { name: 'simple', value: 'simple' },
232
+ { name: 'client', value: 'client' },
233
+ { name: 'complex', value: 'complex' }
234
+ ]
235
+ : framework === 'vanilla'
236
+ ? [{ name: 'default', value: 'medium' }]
237
+ : Object.keys(COMPLEXITY_LEVELS).map((k) => ({
238
+ name: k.toLowerCase(),
239
+ value: k.toLowerCase()
240
+ }));
241
+
182
242
  const answers = await inquirer.prompt([
183
243
  {
184
244
  type: 'input',
@@ -192,18 +252,15 @@ async function promptInteractive() {
192
252
  {
193
253
  type: 'list',
194
254
  name: 'complexity',
195
- message: 'Complexity level:',
196
- choices: Object.keys(COMPLEXITY_LEVELS).map(k => ({
197
- name: k.toLowerCase(),
198
- value: k.toLowerCase()
199
- })),
200
- default: 'medium'
255
+ message: `Complexity level (detected: ${framework}):`,
256
+ choices: complexityChoices,
257
+ default: defaultComplexity
201
258
  },
202
259
  {
203
260
  type: 'checkbox',
204
261
  name: 'features',
205
262
  message: 'Select features:',
206
- choices: Object.keys(COMPONENT_FEATURES).map(k => ({
263
+ choices: Object.keys(COMPONENT_FEATURES).map((k) => ({
207
264
  name: `${k.toLowerCase()}${COMPONENT_FEATURES[k].default ? ' (default)' : ''}`,
208
265
  value: COMPONENT_FEATURES[k].name,
209
266
  checked: COMPONENT_FEATURES[k].default
@@ -213,6 +270,11 @@ async function promptInteractive() {
213
270
 
214
271
  return {
215
272
  ...answers,
216
- outputPath: './src/components'
273
+ outputPath: gen.outputPath || './src/components',
274
+ framework: undefined,
275
+ complexity: answers.complexity,
276
+ prefix: loaded.prefix || 'atomix',
277
+ storybookCssImport: gen.storybookCssImport,
278
+ hookOutputDir: gen.hookOutputDir
217
279
  };
218
280
  }