@shohojdhara/atomix 0.5.1 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/atomix.config.ts +45 -33
  2. package/build-tools/webpack-loader.js +5 -4
  3. package/dist/atomix.css +138 -17
  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 +23 -23
  9. package/dist/charts.js +40 -37
  10. package/dist/charts.js.map +1 -1
  11. package/dist/config.d.ts +699 -0
  12. package/dist/config.js +17 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core.d.ts +2 -2
  15. package/dist/core.js +111 -50
  16. package/dist/core.js.map +1 -1
  17. package/dist/forms.d.ts +3 -6
  18. package/dist/forms.js +2 -2
  19. package/dist/forms.js.map +1 -1
  20. package/dist/heavy.d.ts +1 -1
  21. package/dist/heavy.js +173 -111
  22. package/dist/heavy.js.map +1 -1
  23. package/dist/index.d.ts +1881 -790
  24. package/dist/index.esm.js +2713 -816
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +2693 -780
  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.d.ts +1390 -276
  33. package/dist/theme.js +2133 -625
  34. package/dist/theme.js.map +1 -1
  35. package/package.json +14 -9
  36. package/scripts/atomix-cli.js +15 -1
  37. package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
  38. package/scripts/cli/__tests__/detector.test.js +50 -0
  39. package/scripts/cli/__tests__/template-engine.test.js +23 -0
  40. package/scripts/cli/__tests__/test-setup.js +3 -0
  41. package/scripts/cli/commands/doctor.js +15 -3
  42. package/scripts/cli/commands/generate.js +113 -51
  43. package/scripts/cli/internal/ai-engine.js +30 -10
  44. package/scripts/cli/internal/complexity-utils.js +60 -0
  45. package/scripts/cli/internal/component-validator.js +49 -16
  46. package/scripts/cli/internal/config-loader.js +30 -20
  47. package/scripts/cli/internal/generator.js +89 -36
  48. package/scripts/cli/internal/hook-generator.js +5 -2
  49. package/scripts/cli/internal/itcss-generator.js +16 -12
  50. package/scripts/cli/templates/next-templates.js +81 -30
  51. package/scripts/cli/templates/storybook-templates.js +12 -2
  52. package/scripts/cli/utils/detector.js +45 -7
  53. package/scripts/cli/utils/diagnostics.js +78 -0
  54. package/scripts/cli/utils/telemetry.js +13 -0
  55. package/src/components/Accordion/Accordion.stories.tsx +4 -0
  56. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
  57. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +219 -0
  58. package/src/components/AtomixGlass/glass-utils.ts +1 -1
  59. package/src/components/Button/Button.tsx +114 -57
  60. package/src/components/Callout/Callout.tsx +4 -4
  61. package/src/components/Chart/ChartRenderer.tsx +1 -1
  62. package/src/components/Chart/DonutChart.tsx +11 -8
  63. package/src/components/EdgePanel/EdgePanel.tsx +119 -115
  64. package/src/components/Form/Select.tsx +4 -4
  65. package/src/components/List/List.tsx +4 -4
  66. package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
  67. package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
  68. package/src/components/ProductReview/ProductReview.tsx +4 -2
  69. package/src/components/Rating/Rating.tsx +4 -2
  70. package/src/components/SectionIntro/SectionIntro.tsx +4 -2
  71. package/src/components/Steps/Steps.tsx +1 -1
  72. package/src/components/Tabs/Tabs.tsx +5 -5
  73. package/src/components/Testimonial/Testimonial.tsx +4 -2
  74. package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
  75. package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
  76. package/src/layouts/CssGrid/CssGrid.tsx +215 -0
  77. package/src/layouts/CssGrid/index.ts +8 -0
  78. package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
  79. package/src/layouts/CssGrid/scripts/index.js +43 -0
  80. package/src/layouts/Grid/scripts/Container.js +139 -0
  81. package/src/layouts/Grid/scripts/Grid.js +184 -0
  82. package/src/layouts/Grid/scripts/GridCol.js +273 -0
  83. package/src/layouts/Grid/scripts/Row.js +154 -0
  84. package/src/layouts/Grid/scripts/index.js +48 -0
  85. package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
  86. package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
  87. package/src/lib/composables/useAccordion.ts +5 -5
  88. package/src/lib/composables/useAtomixGlass.ts +3 -3
  89. package/src/lib/composables/useBarChart.ts +2 -2
  90. package/src/lib/composables/useChart.ts +3 -2
  91. package/src/lib/composables/useChartToolbar.ts +48 -66
  92. package/src/lib/composables/useDataTable.ts +1 -1
  93. package/src/lib/composables/useDatePicker.ts +2 -2
  94. package/src/lib/composables/useEdgePanel.ts +45 -54
  95. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
  96. package/src/lib/composables/usePhotoViewer.ts +2 -3
  97. package/src/lib/composables/usePieChart.ts +1 -1
  98. package/src/lib/composables/usePopover.ts +151 -139
  99. package/src/lib/composables/useSideMenu.ts +28 -41
  100. package/src/lib/composables/useSlider.ts +2 -6
  101. package/src/lib/composables/useTooltip.ts +2 -2
  102. package/src/lib/config/index.ts +38 -323
  103. package/src/lib/config/loader.ts +419 -0
  104. package/src/lib/config/public-api.ts +43 -0
  105. package/src/lib/config/types.ts +389 -0
  106. package/src/lib/config/validator.ts +305 -0
  107. package/src/lib/theme/adapters/index.ts +1 -1
  108. package/src/lib/theme/adapters/themeAdapter.ts +358 -229
  109. package/src/lib/theme/components/ThemeToggle.tsx +276 -0
  110. package/src/lib/theme/config/configLoader.ts +351 -0
  111. package/src/lib/theme/config/loader.ts +221 -0
  112. package/src/lib/theme/core/createTheme.ts +126 -50
  113. package/src/lib/theme/core/createThemeObject.ts +7 -4
  114. package/src/lib/theme/devtools/Comparator.tsx +1 -1
  115. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  116. package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
  117. package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
  118. package/src/lib/theme/index.ts +322 -38
  119. package/src/lib/theme/runtime/ThemeProvider.tsx +45 -11
  120. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
  121. package/src/lib/theme/runtime/useTheme.ts +1 -0
  122. package/src/lib/theme/tokens/tokens.ts +101 -1
  123. package/src/lib/theme/types.ts +91 -0
  124. package/src/lib/theme/utils/performanceMonitor.ts +315 -0
  125. package/src/lib/theme/utils/responsive.ts +280 -0
  126. package/src/lib/theme/utils/themeUtils.ts +531 -117
  127. package/src/styles/01-settings/_index.scss +1 -0
  128. package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
  129. package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
  130. package/src/styles/02-tools/_tools.glass.scss +6 -0
  131. package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
  132. package/src/styles/06-components/_components.atomix-glass.scss +4 -4
  133. package/src/lib/composables/useBreadcrumb.ts +0 -81
  134. package/src/lib/composables/useChartInteractions.ts +0 -123
  135. package/src/lib/composables/useChartPerformance.ts +0 -347
  136. package/src/lib/composables/useDropdown.ts +0 -338
  137. package/src/lib/composables/useModal.ts +0 -110
  138. package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
  139. package/src/lib/utils/displacement-generator.ts +0 -92
  140. package/src/lib/utils/memoryMonitor.ts +0 -191
  141. package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
  142. package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
  143. package/src/styles/06-components/_components.testbutton.scss +0 -212
  144. package/src/styles/06-components/_components.testtypecheck.scss +0 -212
  145. 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.1",
3
+ "version": "0.5.4",
4
4
  "description": "Atomix Design System - A modern component library for web applications",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -19,7 +19,7 @@
19
19
  "jsdelivr": "dist/atomix.umd.min.js",
20
20
  "browser": "dist/atomix.umd.min.js",
21
21
  "bin": {
22
- "atomix": "./scripts/atomix-cli.js"
22
+ "atomix": "scripts/atomix-cli.js"
23
23
  },
24
24
  "exports": {
25
25
  ".": {
@@ -125,6 +125,11 @@
125
125
  "require": "./build-tools/index.js",
126
126
  "default": "./build-tools/index.js"
127
127
  },
128
+ "./config": {
129
+ "types": "./dist/config.d.ts",
130
+ "import": "./dist/config.js",
131
+ "default": "./dist/config.js"
132
+ },
128
133
  "./package.json": "./package.json"
129
134
  },
130
135
  "dependencies": {
@@ -180,8 +185,8 @@
180
185
  "@testing-library/react": "^14.0.0",
181
186
  "@testing-library/user-event": "^14.0.0",
182
187
  "@types/node": "^20.0.0",
183
- "@types/react": "^18.0.0",
184
- "@types/react-dom": "^18.0.0",
188
+ "@types/react": "^19.0.0",
189
+ "@types/react-dom": "^19.0.0",
185
190
  "@typescript-eslint/eslint-plugin": "^6.0.0",
186
191
  "@typescript-eslint/parser": "^6.0.0",
187
192
  "@vitejs/plugin-react": "^4.0.0",
@@ -202,8 +207,8 @@
202
207
  "postcss-import": "^15.0.0",
203
208
  "postcss-preset-env": "^9.0.0",
204
209
  "prettier": "^3.0.0",
205
- "react": "^18.0.0",
206
- "react-dom": "^18.0.0",
210
+ "react": "^19.0.0",
211
+ "react-dom": "^19.0.0",
207
212
  "rimraf": "^5.0.0",
208
213
  "rollup": "^3.0.0",
209
214
  "rollup-plugin-dts": "^6.0.0",
@@ -216,7 +221,7 @@
216
221
  "ts-node": "^10.9.0",
217
222
  "tslib": "^2.6.0",
218
223
  "tsx": "^4.21.0",
219
- "typescript": "^5.0.0",
224
+ "typescript": "^6.0.2",
220
225
  "vite": "^4.0.0",
221
226
  "vitest": "^0.34.0"
222
227
  },
@@ -305,8 +310,8 @@
305
310
  "homepage": "https://github.com/Shohojdhara/atomix#readme",
306
311
  "peerDependencies": {
307
312
  "@phosphor-icons/react": "2.1.10",
308
- "react": "^18.0.0",
309
- "react-dom": "^18.0.0"
313
+ "react": ">=18.0.0",
314
+ "react-dom": ">=18.0.0"
310
315
  },
311
316
  "engines": {
312
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
+ });
@@ -0,0 +1,3 @@
1
+ /**
2
+ * CLI test setup (node environment)
3
+ */
@@ -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
  }
@@ -17,9 +17,15 @@ export class AIEngine {
17
17
 
18
18
  /**
19
19
  * Generate component code based on prompt
20
+ * @param {string} name - Component name
21
+ * @param {string} prompt - User prompt for AI
22
+ * @param {Object} options - Override options (provider, model, temperature, maxTokens)
20
23
  */
21
- async generateComponent(name, prompt) {
22
- const provider = this.config.provider || 'openai';
24
+ async generateComponent(name, prompt, options = {}) {
25
+ const provider = options.provider || this.config.provider || 'openai';
26
+ const model = options.model || this.config.model;
27
+ const temperature = options.temperature ?? this.config.temperature ?? 0.7;
28
+ const maxTokens = options.maxTokens || this.config.maxTokens || 4000;
23
29
  const apiKey = this.config.apiKey || process.env.ATOMIX_AI_API_KEY;
24
30
 
25
31
  if (!apiKey) {
@@ -31,9 +37,9 @@ export class AIEngine {
31
37
  try {
32
38
  let response;
33
39
  if (provider === 'openai') {
34
- response = await this.callOpenAI(name, prompt, apiKey);
40
+ response = await this.callOpenAI(name, prompt, apiKey, model, temperature, maxTokens);
35
41
  } else if (provider === 'anthropic') {
36
- response = await this.callAnthropic(name, prompt, apiKey);
42
+ response = await this.callAnthropic(name, prompt, apiKey, model, temperature, maxTokens);
37
43
  } else {
38
44
  throw new Error(`Unsupported AI provider: ${provider}`);
39
45
  }
@@ -47,9 +53,15 @@ export class AIEngine {
47
53
 
48
54
  /**
49
55
  * Call OpenAI API
56
+ * @param {string} name - Component name
57
+ * @param {string} prompt - User prompt
58
+ * @param {string} apiKey - API key
59
+ * @param {string} [modelOverride] - Model override
60
+ * @param {number} [temperature] - Temperature (0.0-1.0)
61
+ * @param {number} [maxTokens] - Max tokens
50
62
  */
51
- async callOpenAI(name, prompt, apiKey) {
52
- const model = this.config.model || 'gpt-4';
63
+ async callOpenAI(name, prompt, apiKey, modelOverride, temperature, maxTokens) {
64
+ const model = modelOverride || 'gpt-4';
53
65
  const systemPrompt = this.getSystemPrompt(name);
54
66
 
55
67
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
@@ -64,7 +76,8 @@ export class AIEngine {
64
76
  { role: 'system', content: systemPrompt },
65
77
  { role: 'user', content: prompt }
66
78
  ],
67
- temperature: 0.7
79
+ temperature: temperature,
80
+ max_tokens: maxTokens
68
81
  })
69
82
  });
70
83
 
@@ -79,9 +92,15 @@ export class AIEngine {
79
92
 
80
93
  /**
81
94
  * Call Anthropic API
95
+ * @param {string} name - Component name
96
+ * @param {string} prompt - User prompt
97
+ * @param {string} apiKey - API key
98
+ * @param {string} [modelOverride] - Model override
99
+ * @param {number} [temperature] - Temperature (0.0-1.0)
100
+ * @param {number} [maxTokens] - Max tokens
82
101
  */
83
- async callAnthropic(name, prompt, apiKey) {
84
- const model = this.config.model || 'claude-3-sonnet-20240229';
102
+ async callAnthropic(name, prompt, apiKey, modelOverride, temperature, maxTokens) {
103
+ const model = modelOverride || 'claude-3-sonnet-20240229';
85
104
  const systemPrompt = this.getSystemPrompt(name);
86
105
 
87
106
  const response = await fetch('https://api.anthropic.com/v1/messages', {
@@ -93,7 +112,8 @@ export class AIEngine {
93
112
  },
94
113
  body: JSON.stringify({
95
114
  model: model,
96
- max_tokens: 4000,
115
+ max_tokens: maxTokens,
116
+ temperature: temperature,
97
117
  system: systemPrompt,
98
118
  messages: [
99
119
  { role: 'user', content: prompt }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Framework-aware default complexity and normalization for component templates.
3
+ */
4
+
5
+ /** @typedef {'react' | 'next' | 'vanilla'} Framework */
6
+
7
+ /**
8
+ * Default complexity when the user does not pass --complexity (framework-specific).
9
+ * @param {Framework} framework
10
+ * @returns {'simple' | 'medium' | 'complex' | 'client'}
11
+ */
12
+ export function resolveDefaultComplexity(framework) {
13
+ const f = String(framework).toLowerCase();
14
+ if (f === 'next') return 'simple';
15
+ if (f === 'vanilla') return 'medium';
16
+ return 'medium';
17
+ }
18
+
19
+ /**
20
+ * Map requested complexity to a value valid for selectTemplate (Next has no "medium").
21
+ * @param {Framework} framework
22
+ * @param {string} [complexity]
23
+ * @returns {string}
24
+ */
25
+ export function normalizeComplexityForFramework(framework, complexity) {
26
+ const f = String(framework).toLowerCase();
27
+ const c = (complexity || resolveDefaultComplexity(f)).toLowerCase();
28
+
29
+ if (f === 'vanilla') {
30
+ return c;
31
+ }
32
+
33
+ if (f === 'next') {
34
+ const valid = ['simple', 'client', 'complex'];
35
+ if (valid.includes(c)) return c;
36
+ if (c === 'medium') return 'simple';
37
+ return 'simple';
38
+ }
39
+
40
+ if (f === 'react') {
41
+ const valid = ['simple', 'medium', 'complex'];
42
+ if (valid.includes(c)) return c;
43
+ return 'medium';
44
+ }
45
+
46
+ return c;
47
+ }
48
+
49
+ /**
50
+ * Resolve effective complexity: use explicit value when provided, else framework default.
51
+ * @param {Framework} framework
52
+ * @param {string|undefined} explicitComplexity - undefined when CLI did not pass --complexity
53
+ */
54
+ export function resolveEffectiveComplexity(framework, explicitComplexity) {
55
+ const f = String(framework).toLowerCase();
56
+ if (explicitComplexity === undefined || explicitComplexity === '') {
57
+ return normalizeComplexityForFramework(f, resolveDefaultComplexity(f));
58
+ }
59
+ return normalizeComplexityForFramework(f, explicitComplexity);
60
+ }