@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.
- package/atomix.config.ts +45 -33
- package/build-tools/webpack-loader.js +5 -4
- package/dist/atomix.css +138 -17
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/webpack-loader.js +5 -4
- package/dist/charts.d.ts +23 -23
- package/dist/charts.js +40 -37
- package/dist/charts.js.map +1 -1
- package/dist/config.d.ts +699 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +2 -2
- package/dist/core.js +111 -50
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +3 -6
- package/dist/forms.js +2 -2
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +173 -111
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +1881 -790
- package/dist/index.esm.js +2713 -816
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2693 -780
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js +59 -60
- package/dist/layout.js.map +1 -1
- package/dist/theme.d.ts +1390 -276
- package/dist/theme.js +2133 -625
- package/dist/theme.js.map +1 -1
- package/package.json +14 -9
- package/scripts/atomix-cli.js +15 -1
- package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
- package/scripts/cli/__tests__/detector.test.js +50 -0
- package/scripts/cli/__tests__/template-engine.test.js +23 -0
- package/scripts/cli/__tests__/test-setup.js +3 -0
- package/scripts/cli/commands/doctor.js +15 -3
- package/scripts/cli/commands/generate.js +113 -51
- package/scripts/cli/internal/ai-engine.js +30 -10
- package/scripts/cli/internal/complexity-utils.js +60 -0
- package/scripts/cli/internal/component-validator.js +49 -16
- package/scripts/cli/internal/config-loader.js +30 -20
- package/scripts/cli/internal/generator.js +89 -36
- package/scripts/cli/internal/hook-generator.js +5 -2
- package/scripts/cli/internal/itcss-generator.js +16 -12
- package/scripts/cli/templates/next-templates.js +81 -30
- package/scripts/cli/templates/storybook-templates.js +12 -2
- package/scripts/cli/utils/detector.js +45 -7
- package/scripts/cli/utils/diagnostics.js +78 -0
- package/scripts/cli/utils/telemetry.js +13 -0
- package/src/components/Accordion/Accordion.stories.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +219 -0
- package/src/components/AtomixGlass/glass-utils.ts +1 -1
- package/src/components/Button/Button.tsx +114 -57
- package/src/components/Callout/Callout.tsx +4 -4
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +11 -8
- package/src/components/EdgePanel/EdgePanel.tsx +119 -115
- package/src/components/Form/Select.tsx +4 -4
- package/src/components/List/List.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +4 -2
- package/src/components/Rating/Rating.tsx +4 -2
- package/src/components/SectionIntro/SectionIntro.tsx +4 -2
- package/src/components/Steps/Steps.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +5 -5
- package/src/components/Testimonial/Testimonial.tsx +4 -2
- package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
- package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
- package/src/layouts/CssGrid/CssGrid.tsx +215 -0
- package/src/layouts/CssGrid/index.ts +8 -0
- package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
- package/src/layouts/CssGrid/scripts/index.js +43 -0
- package/src/layouts/Grid/scripts/Container.js +139 -0
- package/src/layouts/Grid/scripts/Grid.js +184 -0
- package/src/layouts/Grid/scripts/GridCol.js +273 -0
- package/src/layouts/Grid/scripts/Row.js +154 -0
- package/src/layouts/Grid/scripts/index.js +48 -0
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
- package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
- package/src/lib/composables/useAccordion.ts +5 -5
- package/src/lib/composables/useAtomixGlass.ts +3 -3
- package/src/lib/composables/useBarChart.ts +2 -2
- package/src/lib/composables/useChart.ts +3 -2
- package/src/lib/composables/useChartToolbar.ts +48 -66
- package/src/lib/composables/useDataTable.ts +1 -1
- package/src/lib/composables/useDatePicker.ts +2 -2
- package/src/lib/composables/useEdgePanel.ts +45 -54
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
- package/src/lib/composables/usePhotoViewer.ts +2 -3
- package/src/lib/composables/usePieChart.ts +1 -1
- package/src/lib/composables/usePopover.ts +151 -139
- package/src/lib/composables/useSideMenu.ts +28 -41
- package/src/lib/composables/useSlider.ts +2 -6
- package/src/lib/composables/useTooltip.ts +2 -2
- package/src/lib/config/index.ts +38 -323
- package/src/lib/config/loader.ts +419 -0
- package/src/lib/config/public-api.ts +43 -0
- package/src/lib/config/types.ts +389 -0
- package/src/lib/config/validator.ts +305 -0
- package/src/lib/theme/adapters/index.ts +1 -1
- package/src/lib/theme/adapters/themeAdapter.ts +358 -229
- package/src/lib/theme/components/ThemeToggle.tsx +276 -0
- package/src/lib/theme/config/configLoader.ts +351 -0
- package/src/lib/theme/config/loader.ts +221 -0
- package/src/lib/theme/core/createTheme.ts +126 -50
- package/src/lib/theme/core/createThemeObject.ts +7 -4
- package/src/lib/theme/devtools/Comparator.tsx +1 -1
- package/src/lib/theme/devtools/Inspector.tsx +1 -1
- package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
- package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
- package/src/lib/theme/index.ts +322 -38
- package/src/lib/theme/runtime/ThemeProvider.tsx +45 -11
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
- package/src/lib/theme/runtime/useTheme.ts +1 -0
- package/src/lib/theme/tokens/tokens.ts +101 -1
- package/src/lib/theme/types.ts +91 -0
- package/src/lib/theme/utils/performanceMonitor.ts +315 -0
- package/src/lib/theme/utils/responsive.ts +280 -0
- package/src/lib/theme/utils/themeUtils.ts +531 -117
- package/src/styles/01-settings/_index.scss +1 -0
- package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
- package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
- package/src/styles/02-tools/_tools.glass.scss +6 -0
- package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
- package/src/styles/06-components/_components.atomix-glass.scss +4 -4
- package/src/lib/composables/useBreadcrumb.ts +0 -81
- package/src/lib/composables/useChartInteractions.ts +0 -123
- package/src/lib/composables/useChartPerformance.ts +0 -347
- package/src/lib/composables/useDropdown.ts +0 -338
- package/src/lib/composables/useModal.ts +0 -110
- package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
- package/src/lib/utils/displacement-generator.ts +0 -92
- package/src/lib/utils/memoryMonitor.ts +0 -191
- package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
- package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
- package/src/styles/06-components/_components.testbutton.scss +0 -212
- package/src/styles/06-components/_components.testtypecheck.scss +0 -212
- package/src/styles/06-components/_components.typedbutton.scss +0 -212
|
@@ -9,6 +9,26 @@ import { join } from 'path';
|
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
10
|
import { AtomixCLIError } from '../utils/error.js';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Next.js server components (no "use client") do not use forwardRef / client hooks.
|
|
14
|
+
* @param {string} content
|
|
15
|
+
*/
|
|
16
|
+
function isNextServerOnlyComponent(content) {
|
|
17
|
+
if (content.includes("'use client'") || content.includes('"use client"')) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (content.includes('forwardRef')) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (/export\s+default\s+async\s+function/.test(content) || /export\s+default\s+function/.test(content)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (/async\s+function\s+\w+/.test(content) && /export\s+default\s+\w+/.test(content)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
/**
|
|
13
33
|
* Component validation rule severity levels
|
|
14
34
|
*/
|
|
@@ -31,7 +51,11 @@ export const COMPONENT_RULES = {
|
|
|
31
51
|
severity: COMPONENT_SEVERITY.ERROR,
|
|
32
52
|
validate: (content) => {
|
|
33
53
|
const issues = [];
|
|
34
|
-
|
|
54
|
+
|
|
55
|
+
if (isNextServerOnlyComponent(content)) {
|
|
56
|
+
return issues;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
if (!content.includes('forwardRef')) {
|
|
36
60
|
issues.push({
|
|
37
61
|
rule: 'forward-ref-required',
|
|
@@ -40,7 +64,7 @@ export const COMPONENT_RULES = {
|
|
|
40
64
|
severity: COMPONENT_SEVERITY.ERROR
|
|
41
65
|
});
|
|
42
66
|
}
|
|
43
|
-
|
|
67
|
+
|
|
44
68
|
return issues;
|
|
45
69
|
}
|
|
46
70
|
},
|
|
@@ -200,7 +224,11 @@ export const COMPONENT_RULES = {
|
|
|
200
224
|
severity: COMPONENT_SEVERITY.INFO,
|
|
201
225
|
validate: (content) => {
|
|
202
226
|
const issues = [];
|
|
203
|
-
|
|
227
|
+
|
|
228
|
+
if (isNextServerOnlyComponent(content)) {
|
|
229
|
+
return issues;
|
|
230
|
+
}
|
|
231
|
+
|
|
204
232
|
if (!content.includes('memo') && !content.includes('React.memo')) {
|
|
205
233
|
issues.push({
|
|
206
234
|
rule: 'memo-usage',
|
|
@@ -209,7 +237,7 @@ export const COMPONENT_RULES = {
|
|
|
209
237
|
severity: COMPONENT_SEVERITY.INFO
|
|
210
238
|
});
|
|
211
239
|
}
|
|
212
|
-
|
|
240
|
+
|
|
213
241
|
return issues;
|
|
214
242
|
}
|
|
215
243
|
},
|
|
@@ -223,9 +251,13 @@ export const COMPONENT_RULES = {
|
|
|
223
251
|
severity: COMPONENT_SEVERITY.INFO,
|
|
224
252
|
validate: (content, componentName) => {
|
|
225
253
|
const issues = [];
|
|
226
|
-
|
|
254
|
+
|
|
255
|
+
if (isNextServerOnlyComponent(content)) {
|
|
256
|
+
return issues;
|
|
257
|
+
}
|
|
258
|
+
|
|
227
259
|
const hookPattern = new RegExp(`use${componentName}`);
|
|
228
|
-
|
|
260
|
+
|
|
229
261
|
if (!hookPattern.test(content)) {
|
|
230
262
|
issues.push({
|
|
231
263
|
rule: 'composable-hook-pattern',
|
|
@@ -234,7 +266,7 @@ export const COMPONENT_RULES = {
|
|
|
234
266
|
severity: COMPONENT_SEVERITY.INFO
|
|
235
267
|
});
|
|
236
268
|
}
|
|
237
|
-
|
|
269
|
+
|
|
238
270
|
return issues;
|
|
239
271
|
}
|
|
240
272
|
},
|
|
@@ -248,13 +280,14 @@ export const COMPONENT_RULES = {
|
|
|
248
280
|
severity: COMPONENT_SEVERITY.INFO,
|
|
249
281
|
validate: (content) => {
|
|
250
282
|
const issues = [];
|
|
251
|
-
|
|
252
|
-
|
|
283
|
+
|
|
284
|
+
if (isNextServerOnlyComponent(content)) {
|
|
285
|
+
return issues;
|
|
286
|
+
}
|
|
287
|
+
|
|
253
288
|
const hasThemeNaming = /ThemeNaming\./.test(content) || /themeNaming\./.test(content);
|
|
254
|
-
|
|
255
|
-
// Check for variantClass, sizeClass, stateClass patterns
|
|
256
289
|
const hasVariantPattern = /(variant|size|state)Class/.test(content);
|
|
257
|
-
|
|
290
|
+
|
|
258
291
|
if (!hasThemeNaming && !hasVariantPattern) {
|
|
259
292
|
issues.push({
|
|
260
293
|
rule: 'theme-naming-usage',
|
|
@@ -263,7 +296,7 @@ export const COMPONENT_RULES = {
|
|
|
263
296
|
severity: COMPONENT_SEVERITY.INFO
|
|
264
297
|
});
|
|
265
298
|
}
|
|
266
|
-
|
|
299
|
+
|
|
267
300
|
return issues;
|
|
268
301
|
}
|
|
269
302
|
}
|
|
@@ -339,10 +372,8 @@ export class ComponentValidator {
|
|
|
339
372
|
const issues = rule.validate(content, componentName);
|
|
340
373
|
|
|
341
374
|
if (issues.length > 0) {
|
|
342
|
-
results.valid = false;
|
|
343
375
|
results.issues.push(...issues);
|
|
344
|
-
|
|
345
|
-
// Update summary
|
|
376
|
+
|
|
346
377
|
for (const issue of issues) {
|
|
347
378
|
if (issue.severity === COMPONENT_SEVERITY.ERROR) {
|
|
348
379
|
results.summary.errors++;
|
|
@@ -363,6 +394,8 @@ export class ComponentValidator {
|
|
|
363
394
|
}
|
|
364
395
|
}
|
|
365
396
|
|
|
397
|
+
results.valid = results.summary.errors === 0;
|
|
398
|
+
|
|
366
399
|
return results;
|
|
367
400
|
}
|
|
368
401
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Atomix CLI Configuration Loader
|
|
3
|
-
* Supports loading atomix.config.ts and atomix.config.
|
|
3
|
+
* Supports loading atomix.config.ts, atomix.config.js and atomix.config.json
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync } from 'fs';
|
|
@@ -9,7 +9,7 @@ import { pathToFileURL } from 'url';
|
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
10
|
import { hookManager } from './hooks.js';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
class ConfigLoader {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.config = null;
|
|
15
15
|
this.configPath = null;
|
|
@@ -23,7 +23,11 @@ export class ConfigLoader {
|
|
|
23
23
|
async load(projectRoot = process.cwd()) {
|
|
24
24
|
if (this.config) return this.config;
|
|
25
25
|
|
|
26
|
-
const configFiles = [
|
|
26
|
+
const configFiles = [
|
|
27
|
+
'atomix.config.ts',
|
|
28
|
+
'atomix.config.js',
|
|
29
|
+
'atomix.config.json'
|
|
30
|
+
];
|
|
27
31
|
let foundFile = null;
|
|
28
32
|
|
|
29
33
|
for (const file of configFiles) {
|
|
@@ -43,23 +47,29 @@ export class ConfigLoader {
|
|
|
43
47
|
this.configPath = foundFile;
|
|
44
48
|
|
|
45
49
|
try {
|
|
46
|
-
//
|
|
47
|
-
if (foundFile.endsWith('.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
// Handle JSON files differently from JS/TS files
|
|
51
|
+
if (foundFile.endsWith('.json')) {
|
|
52
|
+
const fs = await import('fs');
|
|
53
|
+
this.config = JSON.parse(fs.readFileSync(foundFile, 'utf8'));
|
|
54
|
+
} else {
|
|
55
|
+
// If it's a TypeScript file, we need to register ts-node
|
|
56
|
+
if (foundFile.endsWith('.ts')) {
|
|
57
|
+
// Dynamic import to avoid issues in pure JS environments
|
|
58
|
+
const { register } = await import('ts-node');
|
|
59
|
+
register({
|
|
60
|
+
transpileOnly: true,
|
|
61
|
+
esm: true,
|
|
62
|
+
compilerOptions: {
|
|
63
|
+
module: 'ESNext',
|
|
64
|
+
target: 'ESNext'
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
// Use dynamic import for ESM compatibility
|
|
70
|
+
const configModule = await import(pathToFileURL(foundFile).href);
|
|
71
|
+
this.config = configModule.default || configModule;
|
|
72
|
+
}
|
|
63
73
|
|
|
64
74
|
logger.debug(`Loaded configuration from ${foundFile}`);
|
|
65
75
|
|
|
@@ -159,4 +169,4 @@ export class ConfigLoader {
|
|
|
159
169
|
}
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
export const configLoader = new ConfigLoader();
|
|
172
|
+
export const configLoader = new ConfigLoader();
|
|
@@ -7,6 +7,7 @@ import { readFile } from 'fs/promises';
|
|
|
7
7
|
import { existsSync } from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import { templateEngine, COMPLEXITY_LEVELS } from './template-engine.js';
|
|
10
|
+
import { resolveEffectiveComplexity } from './complexity-utils.js';
|
|
10
11
|
import { detectFramework } from '../utils/detector.js';
|
|
11
12
|
import { filesystem } from './filesystem.js';
|
|
12
13
|
import { aiEngine } from './ai-engine.js';
|
|
@@ -21,6 +22,7 @@ import { tokenValidator } from './tokens/token-validator.js';
|
|
|
21
22
|
import { componentValidator } from './component-validator.js';
|
|
22
23
|
import { generateComponentStylesPackage } from './itcss-generator.js';
|
|
23
24
|
import { generateHookFile } from './hook-generator.js';
|
|
25
|
+
import { configLoader } from './config-loader.js';
|
|
24
26
|
|
|
25
27
|
export { COMPLEXITY_LEVELS };
|
|
26
28
|
|
|
@@ -33,8 +35,23 @@ export const COMPONENT_FEATURES = {
|
|
|
33
35
|
ACCESSIBILITY: { name: 'accessibility', default: true }
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
// Global rate limiter for AI operations
|
|
37
|
-
|
|
38
|
+
// Global rate limiter for AI operations - initialized with defaults, will be updated from config
|
|
39
|
+
let aiRateLimiter = new RateLimiter(5, 60000); // 5 requests per minute default
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Initialize rate limiter from config
|
|
43
|
+
*/
|
|
44
|
+
async function initializeRateLimiter() {
|
|
45
|
+
const config = configLoader.get('ai') || {};
|
|
46
|
+
const rateLimitConfig = config.rateLimit;
|
|
47
|
+
|
|
48
|
+
if (rateLimitConfig) {
|
|
49
|
+
aiRateLimiter = new RateLimiter(
|
|
50
|
+
rateLimitConfig.requests || 5,
|
|
51
|
+
rateLimitConfig.windowMs || 60000
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
38
55
|
|
|
39
56
|
export const generator = {
|
|
40
57
|
/**
|
|
@@ -47,9 +64,13 @@ export const generator = {
|
|
|
47
64
|
async generateComponent(name, options = {}) {
|
|
48
65
|
const {
|
|
49
66
|
outputPath,
|
|
50
|
-
complexity
|
|
67
|
+
complexity: complexityInput,
|
|
51
68
|
features = [],
|
|
52
|
-
logger
|
|
69
|
+
logger,
|
|
70
|
+
framework: frameworkOverride,
|
|
71
|
+
prefix: designTokenPrefix = 'atomix',
|
|
72
|
+
storybookCssImport,
|
|
73
|
+
hookOutputDir
|
|
53
74
|
} = options;
|
|
54
75
|
|
|
55
76
|
// Sanitize and validate component name
|
|
@@ -67,10 +88,12 @@ export const generator = {
|
|
|
67
88
|
);
|
|
68
89
|
}
|
|
69
90
|
|
|
91
|
+
const projectRoot = process.cwd();
|
|
92
|
+
|
|
70
93
|
// Detect framework
|
|
71
94
|
let framework;
|
|
72
95
|
try {
|
|
73
|
-
framework = await detectFramework();
|
|
96
|
+
framework = await detectFramework(projectRoot, { framework: frameworkOverride });
|
|
74
97
|
if (logger) logger.debug(`Detected framework: ${framework}`);
|
|
75
98
|
} catch (error) {
|
|
76
99
|
throw new AtomixCLIError(
|
|
@@ -84,6 +107,8 @@ export const generator = {
|
|
|
84
107
|
);
|
|
85
108
|
}
|
|
86
109
|
|
|
110
|
+
const complexity = resolveEffectiveComplexity(framework, complexityInput);
|
|
111
|
+
|
|
87
112
|
// Load design tokens if available
|
|
88
113
|
let availableTokens = {};
|
|
89
114
|
try {
|
|
@@ -94,7 +119,7 @@ export const generator = {
|
|
|
94
119
|
];
|
|
95
120
|
|
|
96
121
|
for (const tokenPath of tokenPaths) {
|
|
97
|
-
if (existsSync(join(
|
|
122
|
+
if (existsSync(join(projectRoot, tokenPath))) {
|
|
98
123
|
availableTokens = await tokenProvider.loadTokens(tokenPath);
|
|
99
124
|
if (logger) logger.debug(`Loaded design tokens from ${tokenPath}`);
|
|
100
125
|
break;
|
|
@@ -171,7 +196,9 @@ export const generator = {
|
|
|
171
196
|
if (features.includes('storybook')) {
|
|
172
197
|
try {
|
|
173
198
|
const storyTemplateFn = templateEngine.selectTemplate(framework, complexity, 'story');
|
|
174
|
-
const storyContent = templateEngine.render(storyTemplateFn, sanitizedName
|
|
199
|
+
const storyContent = templateEngine.render(storyTemplateFn, sanitizedName, {
|
|
200
|
+
storybookCssImport
|
|
201
|
+
});
|
|
175
202
|
await filesystem.writeFile(join(componentPath, `${sanitizedName}.stories.tsx`), storyContent, 'utf8');
|
|
176
203
|
if (logger) logger.debug(`Created ${sanitizedName}.stories.tsx`);
|
|
177
204
|
} catch (error) {
|
|
@@ -206,31 +233,12 @@ export const generator = {
|
|
|
206
233
|
}
|
|
207
234
|
}
|
|
208
235
|
|
|
209
|
-
if (features.includes('hook') && framework !== 'vanilla') {
|
|
210
|
-
try {
|
|
211
|
-
const hookDir = join(outputPath, '..', 'lib', 'composables');
|
|
212
|
-
const hookTemplateFn = templateEngine.selectTemplate(framework, complexity, 'hook');
|
|
213
|
-
const hookContent = templateEngine.render(hookTemplateFn, sanitizedName);
|
|
214
|
-
await filesystem.writeFile(join(hookDir, `use${sanitizedName}.ts`), hookContent, 'utf8');
|
|
215
|
-
if (logger) logger.debug(`Created use${sanitizedName}.ts`);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
throw new AtomixCLIError(
|
|
218
|
-
`Failed to generate composable hook: ${error.message}`,
|
|
219
|
-
'HOOK_GENERATION_FAILED',
|
|
220
|
-
[
|
|
221
|
-
'Check hook template exists',
|
|
222
|
-
'Verify hook feature is supported for this framework',
|
|
223
|
-
'Try generating without --hook flag'
|
|
224
|
-
]
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
236
|
// 4. Styles (ITCSS) - Enhanced with auto-generation
|
|
230
237
|
if (features.includes('styles')) {
|
|
231
238
|
try {
|
|
232
|
-
const stylesResult = await generateComponentStylesPackage(sanitizedName,
|
|
233
|
-
force: options.force || false
|
|
239
|
+
const stylesResult = await generateComponentStylesPackage(sanitizedName, projectRoot, {
|
|
240
|
+
force: options.force || false,
|
|
241
|
+
prefix: designTokenPrefix
|
|
234
242
|
});
|
|
235
243
|
|
|
236
244
|
if (logger && stylesResult.created.length > 0) {
|
|
@@ -249,11 +257,12 @@ export const generator = {
|
|
|
249
257
|
}
|
|
250
258
|
}
|
|
251
259
|
|
|
252
|
-
// 5. Composable
|
|
260
|
+
// 5. Composable hook (single path: lib/composables + barrel updates)
|
|
253
261
|
if (features.includes('hook') && framework !== 'vanilla') {
|
|
254
262
|
try {
|
|
255
|
-
const hookResult = await generateHookFile(sanitizedName,
|
|
256
|
-
force: options.force || false
|
|
263
|
+
const hookResult = await generateHookFile(sanitizedName, projectRoot, {
|
|
264
|
+
force: options.force || false,
|
|
265
|
+
outputDir: hookOutputDir || 'src/lib/composables'
|
|
257
266
|
});
|
|
258
267
|
|
|
259
268
|
if (logger && hookResult.created.length > 0) {
|
|
@@ -279,12 +288,15 @@ export const generator = {
|
|
|
279
288
|
* Generates component files using AI based on a prompt
|
|
280
289
|
* @param {string} name - Component name
|
|
281
290
|
* @param {string} prompt - AI prompt
|
|
282
|
-
* @param {Object} options - Generation options
|
|
291
|
+
* @param {Object} options - Generation options (aiProvider, aiModel, aiTemperature, aiMaxTokens, aiPreview)
|
|
283
292
|
* @returns {Promise<string>} Path to generated component
|
|
284
293
|
* @throws {AtomixCLIError} If generation fails
|
|
285
294
|
*/
|
|
286
295
|
async generateAIComponent(name, prompt, options = {}) {
|
|
287
|
-
const { outputPath, logger } = options;
|
|
296
|
+
const { outputPath, logger, aiProvider, aiModel, aiTemperature, aiMaxTokens, aiPreview } = options;
|
|
297
|
+
|
|
298
|
+
// Initialize rate limiter from config
|
|
299
|
+
await initializeRateLimiter();
|
|
288
300
|
|
|
289
301
|
// Apply rate limiting for AI operations
|
|
290
302
|
const userId = process.env.USER || 'anonymous';
|
|
@@ -319,10 +331,15 @@ export const generator = {
|
|
|
319
331
|
|
|
320
332
|
const componentPath = join(outputPath, sanitizedName);
|
|
321
333
|
|
|
322
|
-
// Call AI Engine
|
|
334
|
+
// Call AI Engine with override options
|
|
323
335
|
let generated;
|
|
324
336
|
try {
|
|
325
|
-
generated = await aiEngine.generateComponent(name, prompt
|
|
337
|
+
generated = await aiEngine.generateComponent(name, prompt, {
|
|
338
|
+
provider: aiProvider,
|
|
339
|
+
model: aiModel,
|
|
340
|
+
temperature: aiTemperature,
|
|
341
|
+
maxTokens: aiMaxTokens
|
|
342
|
+
});
|
|
326
343
|
} catch (error) {
|
|
327
344
|
throw new AtomixCLIError(
|
|
328
345
|
`AI generation failed: ${error.message}`,
|
|
@@ -336,6 +353,42 @@ export const generator = {
|
|
|
336
353
|
);
|
|
337
354
|
}
|
|
338
355
|
|
|
356
|
+
// Preview mode: show output without writing files
|
|
357
|
+
if (aiPreview) {
|
|
358
|
+
logger.info('\n' + '='.repeat(60));
|
|
359
|
+
logger.info('๐ AI PREVIEW MODE - Generated content will not be saved');
|
|
360
|
+
logger.info('='.repeat(60));
|
|
361
|
+
|
|
362
|
+
logger.info('\n๐ Component File:');
|
|
363
|
+
logger.info(generated.component || 'No component generated');
|
|
364
|
+
|
|
365
|
+
if (generated.styles) {
|
|
366
|
+
logger.info('\n๐จ Styles File:');
|
|
367
|
+
logger.info(generated.styles);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (generated.tests) {
|
|
371
|
+
logger.info('\n๐งช Test File:');
|
|
372
|
+
logger.info(generated.tests);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (generated.stories) {
|
|
376
|
+
logger.info('\n๐ Stories File:');
|
|
377
|
+
logger.info(generated.stories);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (generated.readme) {
|
|
381
|
+
logger.info('\n๐ README:');
|
|
382
|
+
logger.info(generated.readme);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
logger.info('\n' + '='.repeat(60));
|
|
386
|
+
logger.info('Preview complete. Run without --ai-preview to save files.');
|
|
387
|
+
logger.info('='.repeat(60));
|
|
388
|
+
|
|
389
|
+
return componentPath; // Return path without writing
|
|
390
|
+
}
|
|
391
|
+
|
|
339
392
|
// Write component file
|
|
340
393
|
try {
|
|
341
394
|
await filesystem.writeFile(join(componentPath, `${sanitizedName}.tsx`), generated.component, 'utf8');
|
|
@@ -334,8 +334,11 @@ export async function generateHookFile(componentName, projectRoot, options = {})
|
|
|
334
334
|
await filesystem.writeFile(indexFile, updatedContent, 'utf8');
|
|
335
335
|
}
|
|
336
336
|
} else {
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
const indexContent = `/**
|
|
338
|
+
* Composable hooks (Atomix CLI)
|
|
339
|
+
*/
|
|
340
|
+
export { ${hookName} } from './${hookName}';
|
|
341
|
+
`;
|
|
339
342
|
await filesystem.writeFile(indexFile, indexContent, 'utf8');
|
|
340
343
|
created.push(`${outputDir}/index.ts`);
|
|
341
344
|
}
|
|
@@ -63,10 +63,11 @@ export const ITCSS_LAYERS = {
|
|
|
63
63
|
* Generate SCSS settings file for a component
|
|
64
64
|
*/
|
|
65
65
|
export function generateSettingsFile(componentName, options = {}) {
|
|
66
|
+
const dsPrefix = options.prefix || 'atomix';
|
|
66
67
|
const {
|
|
67
|
-
primaryColor =
|
|
68
|
-
spacingBase =
|
|
69
|
-
radiusBase =
|
|
68
|
+
primaryColor = `--${dsPrefix}-color-primary`,
|
|
69
|
+
spacingBase = `--${dsPrefix}-spacing-4`,
|
|
70
|
+
radiusBase = `--${dsPrefix}-radius-md`
|
|
70
71
|
} = options;
|
|
71
72
|
|
|
72
73
|
const componentPrefix = componentName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
|
|
@@ -97,19 +98,19 @@ export function generateSettingsFile(componentName, options = {}) {
|
|
|
97
98
|
--${componentPrefix}-radius: var(${radiusBase});
|
|
98
99
|
|
|
99
100
|
// Typography
|
|
100
|
-
--${componentPrefix}-font-size: var(
|
|
101
|
-
--${componentPrefix}-font-weight: var(
|
|
102
|
-
--${componentPrefix}-line-height: var(
|
|
101
|
+
--${componentPrefix}-font-size: var(--${dsPrefix}-font-size-base);
|
|
102
|
+
--${componentPrefix}-font-weight: var(--${dsPrefix}-font-weight-medium);
|
|
103
|
+
--${componentPrefix}-line-height: var(--${dsPrefix}-line-height-tight);
|
|
103
104
|
|
|
104
105
|
// States
|
|
105
106
|
--${componentPrefix}-disabled-opacity: 0.5;
|
|
106
|
-
--${componentPrefix}-focus-ring: 0 0 0 2px var(
|
|
107
|
+
--${componentPrefix}-focus-ring: 0 0 0 2px var(--${dsPrefix}-color-primary-200);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// Dark mode overrides
|
|
110
111
|
.dark {
|
|
111
112
|
:root {
|
|
112
|
-
--${componentPrefix}-focus-ring: 0 0 0 2px var(
|
|
113
|
+
--${componentPrefix}-focus-ring: 0 0 0 2px var(--${dsPrefix}-color-primary-800);
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -131,6 +132,7 @@ $${componentPrefix}-config: (
|
|
|
131
132
|
* Generate SCSS component styles file
|
|
132
133
|
*/
|
|
133
134
|
export function generateComponentStyles(componentName, options = {}) {
|
|
135
|
+
const dsPrefix = options.prefix || 'atomix';
|
|
134
136
|
const {
|
|
135
137
|
baseClass = `c-${componentName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '')}`,
|
|
136
138
|
hasVariants = true,
|
|
@@ -141,7 +143,7 @@ export function generateComponentStyles(componentName, options = {}) {
|
|
|
141
143
|
|
|
142
144
|
const componentPrefix = componentName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
|
|
143
145
|
|
|
144
|
-
|
|
146
|
+
const scss = `// ${componentName} Component Styles
|
|
145
147
|
// Generated by Atomix CLI
|
|
146
148
|
// ITCSS Layer: Components
|
|
147
149
|
// =============================================================================
|
|
@@ -354,6 +356,7 @@ export function generateComponentStyles(componentName, options = {}) {
|
|
|
354
356
|
}
|
|
355
357
|
}
|
|
356
358
|
`;
|
|
359
|
+
return scss.replaceAll('--atomix-', `--${dsPrefix}-`);
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
/**
|
|
@@ -483,7 +486,8 @@ export async function generateComponentStylesPackage(componentName, projectRoot,
|
|
|
483
486
|
const {
|
|
484
487
|
skipSettings = false,
|
|
485
488
|
skipComponents = false,
|
|
486
|
-
force = false
|
|
489
|
+
force = false,
|
|
490
|
+
prefix = 'atomix'
|
|
487
491
|
} = options;
|
|
488
492
|
|
|
489
493
|
const created = [];
|
|
@@ -498,7 +502,7 @@ export async function generateComponentStylesPackage(componentName, projectRoot,
|
|
|
498
502
|
const settingsFile = join(settingsPath, `_settings.${componentPrefix}.scss`);
|
|
499
503
|
|
|
500
504
|
if (force || !existsSync(settingsFile)) {
|
|
501
|
-
const settingsContent = generateSettingsFile(componentName);
|
|
505
|
+
const settingsContent = generateSettingsFile(componentName, { prefix });
|
|
502
506
|
await filesystem.writeFile(settingsFile, settingsContent, 'utf8');
|
|
503
507
|
created.push(`src/styles/01-settings/_settings.${componentPrefix}.scss`);
|
|
504
508
|
logger.debug(`Created settings file: ${settingsFile}`);
|
|
@@ -513,7 +517,7 @@ export async function generateComponentStylesPackage(componentName, projectRoot,
|
|
|
513
517
|
const componentFile = join(componentsPath, `_components.${componentPrefix}.scss`);
|
|
514
518
|
|
|
515
519
|
if (force || !existsSync(componentFile)) {
|
|
516
|
-
const componentContent = generateComponentStyles(componentName);
|
|
520
|
+
const componentContent = generateComponentStyles(componentName, { prefix });
|
|
517
521
|
await filesystem.writeFile(componentFile, componentContent, 'utf8');
|
|
518
522
|
created.push(`src/styles/06-components/_components.${componentPrefix}.scss`);
|
|
519
523
|
logger.debug(`Created component styles: ${componentFile}`);
|