@shohojdhara/atomix 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +2 -0
- package/dist/atomix.css +101 -88
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +5 -15258
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +1 -1
- package/dist/charts.js +17 -19
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +41 -11
- package/dist/core.js +55 -41
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +28 -11
- package/dist/forms.js +25 -24
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +32 -25
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +122 -46
- package/dist/index.esm.js +865 -200
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +870 -204
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +27 -2
- package/dist/theme.js +721 -108
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/scripts/atomix-cli.js +610 -1111
- package/scripts/cli/component-generator.js +610 -0
- package/scripts/cli/documentation-sync.js +542 -0
- package/scripts/cli/interactive-init.js +84 -288
- package/scripts/cli/mappings.js +211 -0
- package/scripts/cli/migration-tools.js +95 -288
- package/scripts/cli/template-manager.js +107 -0
- package/scripts/cli/templates/README.md +123 -0
- package/scripts/cli/templates/composable-templates.js +149 -0
- package/scripts/cli/templates/config-templates.js +126 -0
- package/scripts/cli/templates/index.js +95 -0
- package/scripts/cli/templates/project-templates.js +214 -0
- package/scripts/cli/templates/react-templates.js +261 -0
- package/scripts/cli/templates/scss-templates.js +156 -0
- package/scripts/cli/templates/storybook-templates.js +236 -0
- package/scripts/cli/templates/testing-templates.js +45 -0
- package/scripts/cli/templates/token-templates.js +447 -0
- package/scripts/cli/templates/types-templates.js +133 -0
- package/scripts/cli/templates-original-backup.js +1655 -0
- package/scripts/cli/templates.js +35 -0
- package/scripts/cli/templates_backup.js +684 -0
- package/scripts/cli/theme-bridge.js +20 -14
- package/scripts/cli/token-manager.js +150 -77
- package/scripts/cli/utils.js +37 -25
- package/src/components/Accordion/Accordion.stories.tsx +5 -5
- package/src/components/Accordion/Accordion.test.tsx +57 -0
- package/src/components/Accordion/Accordion.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +41 -44
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +37 -37
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -51
- package/src/components/Avatar/Avatar.stories.tsx +26 -26
- package/src/components/Badge/Badge.stories.tsx +31 -31
- package/src/components/Badge/Badge.test.tsx +51 -0
- package/src/components/Badge/Badge.tsx +20 -1
- package/src/components/Block/Block.stories.tsx +5 -5
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/Button/Button.stories.tsx +13 -13
- package/src/components/Button/Button.tsx +4 -4
- package/src/components/Button/ButtonGroup.stories.tsx +2 -2
- package/src/components/Button/README.md +5 -0
- package/src/components/Callout/Callout.stories.tsx +11 -11
- package/src/components/Callout/Callout.test.tsx +10 -10
- package/src/components/Callout/Callout.tsx +7 -7
- package/src/components/Callout/README.md +9 -8
- package/src/components/Card/Card.tsx +2 -2
- package/src/components/Chart/Chart.stories.tsx +6 -6
- package/src/components/Chart/Chart.tsx +1 -1
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +14 -12
- package/src/components/DatePicker/DatePicker.stories.tsx +6 -6
- package/src/components/Dropdown/Dropdown.stories.tsx +4 -4
- package/src/components/Form/Checkbox.stories.tsx +3 -3
- package/src/components/Form/Checkbox.tsx +4 -2
- package/src/components/Form/Form.stories.tsx +3 -3
- package/src/components/Form/FormGroup.stories.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +28 -16
- package/src/components/Form/Input.test.tsx +59 -0
- package/src/components/Form/Input.tsx +97 -95
- package/src/components/Form/Radio.stories.tsx +94 -94
- package/src/components/Form/Radio.tsx +2 -2
- package/src/components/Form/Select.stories.tsx +4 -4
- package/src/components/Form/Select.tsx +2 -2
- package/src/components/Form/Textarea.stories.tsx +22 -7
- package/src/components/Form/Textarea.test.tsx +45 -0
- package/src/components/Form/Textarea.tsx +88 -86
- package/src/components/List/List.stories.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +4 -4
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/README.md +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +5 -2
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +1 -1
- package/src/components/Progress/Progress.tsx +46 -46
- package/src/components/Rating/Rating.stories.tsx +4 -4
- package/src/components/Rating/Rating.tsx +8 -8
- package/src/components/Slider/Slider.stories.tsx +63 -63
- package/src/components/Spinner/Spinner.stories.tsx +2 -2
- package/src/components/Spinner/Spinner.test.tsx +35 -0
- package/src/components/Spinner/Spinner.tsx +9 -2
- package/src/components/Testimonial/Testimonial.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.stories.tsx +32 -9
- package/src/components/Toggle/Toggle.test.tsx +91 -0
- package/src/components/Toggle/Toggle.tsx +44 -27
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/layouts/Grid/Grid.stories.tsx +49 -49
- package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
- package/src/lib/composables/useAccordion.ts +12 -3
- package/src/lib/composables/useBreadcrumb.ts +2 -2
- package/src/lib/composables/useCallout.ts +7 -7
- package/src/lib/composables/useNavbar.ts +1 -1
- package/src/lib/constants/components.ts +1 -1
- package/src/lib/storybook/InteractiveDemo.tsx +113 -0
- package/src/lib/storybook/PreviewContainer.tsx +36 -0
- package/src/lib/storybook/VariantsGrid.tsx +21 -0
- package/src/lib/storybook/index.ts +3 -0
- package/src/lib/theme/core/createThemeObject.ts +9 -5
- package/src/lib/theme/devtools/CLI.ts +155 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
- package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
- package/src/lib/theme/devtools/index.ts +3 -0
- package/src/lib/theme/errors/errors.ts +8 -0
- package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
- package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
- package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
- package/src/lib/theme/utils/index.ts +1 -0
- package/src/lib/theme/utils/themeValidation.ts +501 -0
- package/src/lib/theme-tools.ts +32 -3
- package/src/lib/types/components.ts +81 -26
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +14 -15
- package/src/styles/06-components/_components.callout.scss +29 -33
- package/src/styles/06-components/_index.scss +1 -1
- package/src/styles/99-utilities/_utilities.display.scss +14 -3
- package/src/styles/99-utilities/_utilities.flex.scss +10 -10
- package/src/styles/99-utilities/_utilities.text.scss +28 -8
- package/scripts/cli/__tests__/cli-commands.test.js +0 -204
- package/scripts/cli/__tests__/utils.test.js +0 -201
- package/scripts/cli/__tests__/vitest.config.js +0 -26
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Component Generator
|
|
3
|
+
* Supports template variants, interactive generation, and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { readFile, writeFile, mkdir, access } from 'fs/promises';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { join, dirname, basename } from 'path';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import boxen from 'boxen';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
validatePath,
|
|
16
|
+
validateComponentName,
|
|
17
|
+
sanitizeInput,
|
|
18
|
+
fileExists,
|
|
19
|
+
AtomixCLIError
|
|
20
|
+
} from './utils.js';
|
|
21
|
+
import { componentTemplates } from './templates.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Component complexity levels
|
|
25
|
+
*/
|
|
26
|
+
export const COMPLEXITY_LEVELS = {
|
|
27
|
+
SIMPLE: {
|
|
28
|
+
name: 'simple',
|
|
29
|
+
description: 'Basic presentational component with minimal state',
|
|
30
|
+
features: [
|
|
31
|
+
'Props interface',
|
|
32
|
+
'Basic styling',
|
|
33
|
+
'No internal state',
|
|
34
|
+
'No complex interactions'
|
|
35
|
+
],
|
|
36
|
+
template: 'simple'
|
|
37
|
+
},
|
|
38
|
+
MEDIUM: {
|
|
39
|
+
name: 'medium',
|
|
40
|
+
description: 'Component with some state and interactions',
|
|
41
|
+
features: [
|
|
42
|
+
'Props interface',
|
|
43
|
+
'Internal state management',
|
|
44
|
+
'Event handlers',
|
|
45
|
+
'Composable hook',
|
|
46
|
+
'Full styling system'
|
|
47
|
+
],
|
|
48
|
+
template: 'medium'
|
|
49
|
+
},
|
|
50
|
+
COMPLEX: {
|
|
51
|
+
name: 'complex',
|
|
52
|
+
description: 'Advanced component with rich functionality',
|
|
53
|
+
features: [
|
|
54
|
+
'All medium features',
|
|
55
|
+
'Context integration',
|
|
56
|
+
'Advanced interactions',
|
|
57
|
+
'Accessibility features',
|
|
58
|
+
'Validation logic',
|
|
59
|
+
'Animation support'
|
|
60
|
+
],
|
|
61
|
+
template: 'complex'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Component feature options
|
|
67
|
+
*/
|
|
68
|
+
export const COMPONENT_FEATURES = {
|
|
69
|
+
TYPESCRIPT: {
|
|
70
|
+
name: 'typescript',
|
|
71
|
+
description: 'Include TypeScript definitions',
|
|
72
|
+
default: true
|
|
73
|
+
},
|
|
74
|
+
STORYBOOK: {
|
|
75
|
+
name: 'storybook',
|
|
76
|
+
description: 'Generate Storybook stories',
|
|
77
|
+
default: true
|
|
78
|
+
},
|
|
79
|
+
TESTS: {
|
|
80
|
+
name: 'tests',
|
|
81
|
+
description: 'Include unit tests',
|
|
82
|
+
default: false
|
|
83
|
+
},
|
|
84
|
+
HOOK: {
|
|
85
|
+
name: 'hook',
|
|
86
|
+
description: 'Create composable hook',
|
|
87
|
+
default: true
|
|
88
|
+
},
|
|
89
|
+
STYLES: {
|
|
90
|
+
name: 'styles',
|
|
91
|
+
description: 'Generate SCSS styles (ITCSS architecture)',
|
|
92
|
+
default: true
|
|
93
|
+
},
|
|
94
|
+
ACCESSIBILITY: {
|
|
95
|
+
name: 'accessibility',
|
|
96
|
+
description: 'Include accessibility features (ARIA, keyboard)',
|
|
97
|
+
default: true
|
|
98
|
+
},
|
|
99
|
+
ANIMATIONS: {
|
|
100
|
+
name: 'animations',
|
|
101
|
+
description: 'Add animation support',
|
|
102
|
+
default: false
|
|
103
|
+
},
|
|
104
|
+
CONTEXT: {
|
|
105
|
+
name: 'context',
|
|
106
|
+
description: 'Support context integration',
|
|
107
|
+
default: false
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Simple component template
|
|
113
|
+
*/
|
|
114
|
+
function getSimpleTemplate(name) {
|
|
115
|
+
return `import React from 'react';
|
|
116
|
+
import type { ${name}Props } from './${name}.types';
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ${name} - Simple Presentational Component
|
|
120
|
+
*
|
|
121
|
+
* A basic component for rendering content with minimal overhead.
|
|
122
|
+
*/
|
|
123
|
+
export const ${name} = ({ children, className, ...props }: ${name}Props) => {
|
|
124
|
+
return (
|
|
125
|
+
<div className={\`c-${name.toLowerCase()} \${className || ''}\`} {...props}>
|
|
126
|
+
{children}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
${name}.displayName = '${name}';
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Medium component template
|
|
137
|
+
*/
|
|
138
|
+
function getMediumTemplate(name) {
|
|
139
|
+
return `import React, { useState, useCallback } from 'react';
|
|
140
|
+
import { cn } from '../../lib/utils';
|
|
141
|
+
import type { ${name}Props, ${name}State } from './${name}.types';
|
|
142
|
+
import { use${name} } from '../../lib/composables/use${name}';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* ${name} - Component with State and Interactions
|
|
146
|
+
*
|
|
147
|
+
* A component with internal state management and event handling.
|
|
148
|
+
*/
|
|
149
|
+
export const ${name} = React.forwardRef<HTMLDivElement, ${name}Props>(
|
|
150
|
+
({
|
|
151
|
+
children,
|
|
152
|
+
className,
|
|
153
|
+
defaultOpen = false,
|
|
154
|
+
onOpenChange,
|
|
155
|
+
disabled = false,
|
|
156
|
+
...props
|
|
157
|
+
}, ref) => {
|
|
158
|
+
const {
|
|
159
|
+
isOpen,
|
|
160
|
+
toggle,
|
|
161
|
+
setIsOpen
|
|
162
|
+
} = use${name}({
|
|
163
|
+
defaultOpen,
|
|
164
|
+
onOpenChange
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
168
|
+
if (disabled) return;
|
|
169
|
+
|
|
170
|
+
switch (e.key) {
|
|
171
|
+
case 'Enter':
|
|
172
|
+
case ' ':
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
toggle();
|
|
175
|
+
break;
|
|
176
|
+
case 'Escape':
|
|
177
|
+
setIsOpen(false);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}, [disabled, toggle, setIsOpen]);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
ref={ref}
|
|
185
|
+
className={cn(
|
|
186
|
+
'c-' + '${name.toLowerCase()}',
|
|
187
|
+
isOpen && 'is-open',
|
|
188
|
+
disabled && 'is-disabled',
|
|
189
|
+
className
|
|
190
|
+
)}
|
|
191
|
+
data-state={isOpen ? 'open' : 'closed'}
|
|
192
|
+
data-disabled={disabled}
|
|
193
|
+
onKeyDown={handleKeyDown}
|
|
194
|
+
tabIndex={disabled ? -1 : 0}
|
|
195
|
+
role="button"
|
|
196
|
+
aria-pressed={isOpen}
|
|
197
|
+
aria-disabled={disabled}
|
|
198
|
+
{...props}
|
|
199
|
+
>
|
|
200
|
+
{children}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
${name}.displayName = '${name}';
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Complex component template
|
|
212
|
+
*/
|
|
213
|
+
function getComplexTemplate(name) {
|
|
214
|
+
return `import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
215
|
+
import { cn } from '../../lib/utils';
|
|
216
|
+
import type { ${name}Props, ${name}ContextValue } from './${name}.types';
|
|
217
|
+
import { use${name} } from '../../lib/composables/use${name}';
|
|
218
|
+
import { ${name}Context } from './${name}.context';
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* ${name} - Advanced Component with Context and Animations
|
|
222
|
+
*
|
|
223
|
+
* A feature-rich component with context integration, animations,
|
|
224
|
+
* accessibility, and validation.
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
// Root Component
|
|
228
|
+
export const ${name} = React.forwardRef<HTMLDivElement, ${name}Props>(
|
|
229
|
+
({
|
|
230
|
+
children,
|
|
231
|
+
className,
|
|
232
|
+
defaultOpen = false,
|
|
233
|
+
onOpenChange,
|
|
234
|
+
disabled = false,
|
|
235
|
+
required = false,
|
|
236
|
+
validate,
|
|
237
|
+
animation = true,
|
|
238
|
+
...props
|
|
239
|
+
}, ref) => {
|
|
240
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
241
|
+
const [isValid, setIsValid] = useState(true);
|
|
242
|
+
const [validationMessage, setValidationMessage] = useState('');
|
|
243
|
+
const elementRef = useRef<HTMLDivElement>(null);
|
|
244
|
+
|
|
245
|
+
const toggle = useCallback(() => {
|
|
246
|
+
if (disabled) return;
|
|
247
|
+
|
|
248
|
+
const newState = !isOpen;
|
|
249
|
+
|
|
250
|
+
// Validation before opening
|
|
251
|
+
if (newState && validate) {
|
|
252
|
+
const result = validate();
|
|
253
|
+
setIsValid(result.valid);
|
|
254
|
+
setValidationMessage(result.message || '');
|
|
255
|
+
|
|
256
|
+
if (!result.valid) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
setIsOpen(newState);
|
|
262
|
+
onOpenChange?.(newState);
|
|
263
|
+
}, [isOpen, disabled, validate, onOpenChange]);
|
|
264
|
+
|
|
265
|
+
const contextValue: ${name}ContextValue = React.useMemo(() => ({
|
|
266
|
+
isOpen,
|
|
267
|
+
toggle,
|
|
268
|
+
setIsOpen,
|
|
269
|
+
disabled,
|
|
270
|
+
required,
|
|
271
|
+
isValid,
|
|
272
|
+
validationMessage
|
|
273
|
+
}), [isOpen, toggle, disabled, required, isValid, validationMessage]);
|
|
274
|
+
|
|
275
|
+
// Focus management
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (isOpen && elementRef.current) {
|
|
278
|
+
elementRef.current.focus();
|
|
279
|
+
}
|
|
280
|
+
}, [isOpen]);
|
|
281
|
+
|
|
282
|
+
// Keyboard navigation
|
|
283
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
284
|
+
if (disabled) return;
|
|
285
|
+
|
|
286
|
+
switch (e.key) {
|
|
287
|
+
case 'Enter':
|
|
288
|
+
case ' ':
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
toggle();
|
|
291
|
+
break;
|
|
292
|
+
case 'Escape':
|
|
293
|
+
setIsOpen(false);
|
|
294
|
+
break;
|
|
295
|
+
case 'ArrowDown':
|
|
296
|
+
if (isOpen) e.preventDefault();
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}, [disabled, isOpen, toggle]);
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<${name}Context.Provider value={contextValue}>
|
|
303
|
+
<div
|
|
304
|
+
ref={(node) => {
|
|
305
|
+
elementRef.current = node;
|
|
306
|
+
if (typeof ref === 'function') ref(node);
|
|
307
|
+
else if (ref) ref.current = node;
|
|
308
|
+
}}
|
|
309
|
+
className={cn(
|
|
310
|
+
'c-${name.toLowerCase()}',
|
|
311
|
+
isOpen && 'is-open',
|
|
312
|
+
disabled && 'is-disabled',
|
|
313
|
+
!isValid && 'is-invalid',
|
|
314
|
+
animation && 'has-animation',
|
|
315
|
+
className
|
|
316
|
+
)}
|
|
317
|
+
data-state={isOpen ? 'open' : 'closed'}
|
|
318
|
+
data-disabled={disabled}
|
|
319
|
+
data-valid={isValid}
|
|
320
|
+
onKeyDown={handleKeyDown}
|
|
321
|
+
role="region"
|
|
322
|
+
aria-expanded={isOpen}
|
|
323
|
+
aria-disabled={disabled}
|
|
324
|
+
aria-invalid={!isValid}
|
|
325
|
+
aria-describedby={!isValid ? \`${name.toLowerCase()}-error\` : undefined}
|
|
326
|
+
{...props}
|
|
327
|
+
>
|
|
328
|
+
{children}
|
|
329
|
+
|
|
330
|
+
{!isValid && validationMessage && (
|
|
331
|
+
<div
|
|
332
|
+
id={\`${name.toLowerCase()}-error\`}
|
|
333
|
+
className="c-${name.toLowerCase()}__error"
|
|
334
|
+
role="alert"
|
|
335
|
+
aria-live="polite"
|
|
336
|
+
>
|
|
337
|
+
{validationMessage}
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
</${name}Context.Provider>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
${name}.displayName = '${name}';
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Generate component based on complexity level
|
|
352
|
+
*/
|
|
353
|
+
export function generateComponentByComplexity(name, complexity, options = {}) {
|
|
354
|
+
const level = COMPLEXITY_LEVELS[complexity.toUpperCase()];
|
|
355
|
+
|
|
356
|
+
if (!level) {
|
|
357
|
+
throw new AtomixCLIError(
|
|
358
|
+
`Unknown complexity level: ${complexity}`,
|
|
359
|
+
'INVALID_COMPLEXITY',
|
|
360
|
+
[
|
|
361
|
+
'Valid levels: simple, medium, complex',
|
|
362
|
+
'Example: atomix generate component MyButton --complexity medium'
|
|
363
|
+
]
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
switch (level.template) {
|
|
368
|
+
case 'simple':
|
|
369
|
+
return getSimpleTemplate(name);
|
|
370
|
+
case 'medium':
|
|
371
|
+
return getMediumTemplate(name);
|
|
372
|
+
case 'complex':
|
|
373
|
+
return getComplexTemplate(name);
|
|
374
|
+
default:
|
|
375
|
+
return componentTemplates.react.component(name, options);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Interactive component generation
|
|
381
|
+
*/
|
|
382
|
+
export async function interactiveComponentGeneration() {
|
|
383
|
+
console.log(boxen(
|
|
384
|
+
chalk.bold.cyan('🎨 Interactive Component Generator\n\n') +
|
|
385
|
+
chalk.gray('Let\'s create a new component for your design system'),
|
|
386
|
+
{
|
|
387
|
+
padding: 1,
|
|
388
|
+
margin: 1,
|
|
389
|
+
borderStyle: 'round',
|
|
390
|
+
borderColor: 'cyan'
|
|
391
|
+
}
|
|
392
|
+
));
|
|
393
|
+
|
|
394
|
+
// Step 1: Component name
|
|
395
|
+
const { componentName } = await inquirer.prompt([
|
|
396
|
+
{
|
|
397
|
+
type: 'input',
|
|
398
|
+
name: 'componentName',
|
|
399
|
+
message: 'What is your component name?',
|
|
400
|
+
validate: (input) => {
|
|
401
|
+
const validation = validateComponentName(input);
|
|
402
|
+
return validation.isValid || validation.error;
|
|
403
|
+
},
|
|
404
|
+
filter: (input) => sanitizeInput(input)
|
|
405
|
+
}
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
// Step 2: Complexity level
|
|
409
|
+
const { complexity } = await inquirer.prompt([
|
|
410
|
+
{
|
|
411
|
+
type: 'list',
|
|
412
|
+
name: 'complexity',
|
|
413
|
+
message: 'What is the complexity level?',
|
|
414
|
+
choices: Object.values(COMPLEXITY_LEVELS).map(level => ({
|
|
415
|
+
name: `${chalk.bold(level.name.charAt(0).toUpperCase() + level.name.slice(1))} - ${level.description}`,
|
|
416
|
+
value: level.name,
|
|
417
|
+
short: level.name
|
|
418
|
+
})),
|
|
419
|
+
default: 'medium'
|
|
420
|
+
}
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
// Step 3: Features
|
|
424
|
+
const { features } = await inquirer.prompt([
|
|
425
|
+
{
|
|
426
|
+
type: 'checkbox',
|
|
427
|
+
name: 'features',
|
|
428
|
+
message: 'Select features to include:',
|
|
429
|
+
choices: Object.values(COMPONENT_FEATURES).map(feature => ({
|
|
430
|
+
name: `${feature.description}`,
|
|
431
|
+
value: feature.name,
|
|
432
|
+
checked: feature.default
|
|
433
|
+
}))
|
|
434
|
+
}
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
// Step 4: Output path
|
|
438
|
+
const { outputPath } = await inquirer.prompt([
|
|
439
|
+
{
|
|
440
|
+
type: 'input',
|
|
441
|
+
name: 'outputPath',
|
|
442
|
+
message: 'Output directory:',
|
|
443
|
+
default: './src/components',
|
|
444
|
+
validate: (input) => {
|
|
445
|
+
const validation = validatePath(sanitizeInput(input));
|
|
446
|
+
return validation.isValid || validation.error;
|
|
447
|
+
},
|
|
448
|
+
filter: (input) => sanitizeInput(input)
|
|
449
|
+
}
|
|
450
|
+
]);
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
name: componentName,
|
|
454
|
+
complexity,
|
|
455
|
+
features,
|
|
456
|
+
outputPath
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Validate generated component against guidelines
|
|
462
|
+
*/
|
|
463
|
+
export async function validateGeneratedComponent(name, componentPath) {
|
|
464
|
+
const issues = [];
|
|
465
|
+
const warnings = [];
|
|
466
|
+
|
|
467
|
+
// Check component file
|
|
468
|
+
const componentFile = join(componentPath, `${name}.tsx`);
|
|
469
|
+
if (existsSync(componentFile)) {
|
|
470
|
+
const content = await readFile(componentFile, 'utf8');
|
|
471
|
+
|
|
472
|
+
// Check for proper TypeScript types
|
|
473
|
+
if (!content.includes('export interface') && !content.includes('export type')) {
|
|
474
|
+
issues.push({
|
|
475
|
+
file: `${name}.tsx`,
|
|
476
|
+
issue: 'Missing TypeScript type definitions',
|
|
477
|
+
suggestion: 'Add proper type interfaces for component props'
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Check for displayName
|
|
482
|
+
if (!content.includes('displayName')) {
|
|
483
|
+
warnings.push({
|
|
484
|
+
file: `${name}.tsx`,
|
|
485
|
+
issue: 'Missing displayName property',
|
|
486
|
+
suggestion: 'Add Component.displayName = "ComponentName" for better debugging'
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Check for proper documentation
|
|
491
|
+
if (!content.includes('/**') && !content.includes('*')) {
|
|
492
|
+
warnings.push({
|
|
493
|
+
file: `${name}.tsx`,
|
|
494
|
+
issue: 'Missing JSDoc comments',
|
|
495
|
+
suggestion: 'Add JSDoc comments to document the component API'
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Check for accessibility features
|
|
500
|
+
if (!content.includes('aria-') && !content.includes('role=')) {
|
|
501
|
+
warnings.push({
|
|
502
|
+
file: `${name}.tsx`,
|
|
503
|
+
issue: 'Missing accessibility attributes',
|
|
504
|
+
suggestion: 'Add ARIA attributes and roles for better accessibility'
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
issues.push({
|
|
509
|
+
file: `${name}.tsx`,
|
|
510
|
+
issue: 'Component file not found',
|
|
511
|
+
suggestion: 'Ensure the component was generated successfully'
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Check for SCSS file
|
|
516
|
+
const scssFile = join(componentPath, `${name}.scss`);
|
|
517
|
+
if (existsSync(componentPath)) {
|
|
518
|
+
// Check in styles directory
|
|
519
|
+
const globalScss = join(process.cwd(), 'src/styles/06-components', `_components.${name.toLowerCase()}.scss`);
|
|
520
|
+
if (!existsSync(globalScss)) {
|
|
521
|
+
warnings.push({
|
|
522
|
+
file: 'styles',
|
|
523
|
+
issue: 'SCSS styles file not found',
|
|
524
|
+
suggestion: 'Generate SCSS styles following ITCSS architecture'
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Check for Storybook story
|
|
530
|
+
const storyFile = join(componentPath, `${name}.stories.tsx`);
|
|
531
|
+
if (!existsSync(storyFile)) {
|
|
532
|
+
warnings.push({
|
|
533
|
+
file: `${name}.stories.tsx`,
|
|
534
|
+
issue: 'Storybook story not found',
|
|
535
|
+
suggestion: 'Generate Storybook stories for component documentation'
|
|
536
|
+
});
|
|
537
|
+
} else {
|
|
538
|
+
const storyContent = await readFile(storyFile, 'utf8');
|
|
539
|
+
|
|
540
|
+
if (!storyContent.includes('autodocs')) {
|
|
541
|
+
warnings.push({
|
|
542
|
+
file: `${name}.stories.tsx`,
|
|
543
|
+
issue: 'Missing autodocs tag',
|
|
544
|
+
suggestion: 'Add tags: [\'autodocs\'] for automatic documentation'
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!storyContent.match(/export const (Default|Primary|Basic)/)) {
|
|
549
|
+
warnings.push({
|
|
550
|
+
file: `${name}.stories.tsx`,
|
|
551
|
+
issue: 'Missing default story',
|
|
552
|
+
suggestion: 'Add a default story to showcase the basic component usage'
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
valid: issues.length === 0,
|
|
559
|
+
issues,
|
|
560
|
+
warnings
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Display validation report
|
|
566
|
+
*/
|
|
567
|
+
export function displayValidationReport(result) {
|
|
568
|
+
if (result.valid && result.warnings.length === 0) {
|
|
569
|
+
console.log(boxen(
|
|
570
|
+
chalk.bold.green('✅ Component validation passed!\n\n') +
|
|
571
|
+
chalk.gray('Your component follows all Atomix design system guidelines.'),
|
|
572
|
+
{
|
|
573
|
+
padding: 1,
|
|
574
|
+
margin: 1,
|
|
575
|
+
borderStyle: 'round',
|
|
576
|
+
borderColor: 'green'
|
|
577
|
+
}
|
|
578
|
+
));
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (result.issues.length > 0) {
|
|
583
|
+
console.log(chalk.bold.red(`\n❌ Found ${result.issues.length} issue(s):\n`));
|
|
584
|
+
result.issues.forEach((issue, index) => {
|
|
585
|
+
console.log(chalk.red(` ${index + 1}. ${issue.file}`));
|
|
586
|
+
console.log(chalk.gray(` Issue: ${issue.issue}`));
|
|
587
|
+
console.log(chalk.yellow(` Suggestion: ${issue.suggestion}\n`));
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (result.warnings.length > 0) {
|
|
592
|
+
console.log(chalk.bold.yellow(`\n⚠️ Found ${result.warnings.length} warning(s):\n`));
|
|
593
|
+
result.warnings.forEach((warning, index) => {
|
|
594
|
+
console.log(chalk.yellow(` ${index + 1}. ${warning.file}`));
|
|
595
|
+
console.log(chalk.gray(` Warning: ${warning.warning}`));
|
|
596
|
+
console.log(chalk.cyan(` Suggestion: ${warning.suggestion}\n`));
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export default {
|
|
604
|
+
COMPLEXITY_LEVELS,
|
|
605
|
+
COMPONENT_FEATURES,
|
|
606
|
+
generateComponentByComplexity,
|
|
607
|
+
interactiveComponentGeneration,
|
|
608
|
+
validateGeneratedComponent,
|
|
609
|
+
displayValidationReport
|
|
610
|
+
};
|