@shohojdhara/atomix 0.4.7 → 0.4.9
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 +58 -1
- package/dist/atomix.css +172 -157
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +33 -0
- package/dist/charts.js +1274 -164
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +33 -10
- package/dist/core.js +1099 -83
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +33 -0
- package/dist/forms.js +2106 -1050
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +42 -1
- package/dist/heavy.js +1663 -638
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +442 -270
- package/dist/index.esm.js +1947 -680
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1982 -712
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +6 -3
- package/scripts/atomix-cli.js +136 -1827
- package/scripts/cli/__tests__/basic.test.js +3 -2
- package/scripts/cli/__tests__/clean.test.js +278 -0
- package/scripts/cli/__tests__/component-validator.test.js +433 -0
- package/scripts/cli/__tests__/generator.test.js +613 -0
- package/scripts/cli/__tests__/glass-motion.test.js +256 -0
- package/scripts/cli/__tests__/integration.test.js +719 -108
- package/scripts/cli/__tests__/migrate.test.js +74 -0
- package/scripts/cli/__tests__/security.test.js +206 -0
- package/scripts/cli/__tests__/test-setup.js +3 -1
- package/scripts/cli/__tests__/theme-bridge.test.js +507 -0
- package/scripts/cli/__tests__/token-provider.test.js +361 -0
- package/scripts/cli/__tests__/utils.test.js +5 -5
- package/scripts/cli/commands/benchmark.js +105 -0
- package/scripts/cli/commands/build-theme.js +115 -0
- package/scripts/cli/commands/clean.js +109 -0
- package/scripts/cli/commands/doctor.js +88 -0
- package/scripts/cli/commands/generate.js +218 -0
- package/scripts/cli/commands/init.js +73 -0
- package/scripts/cli/commands/migrate.js +106 -0
- package/scripts/cli/commands/sync-tokens.js +206 -0
- package/scripts/cli/commands/theme-bridge.js +248 -0
- package/scripts/cli/commands/tokens.js +157 -0
- package/scripts/cli/commands/validate.js +194 -0
- package/scripts/cli/internal/ai-engine.js +156 -0
- package/scripts/cli/internal/compiler.js +114 -0
- package/scripts/cli/internal/component-validator.js +443 -0
- package/scripts/cli/internal/config-loader.js +162 -0
- package/scripts/cli/internal/filesystem.js +158 -0
- package/scripts/cli/internal/generator.js +430 -0
- package/scripts/cli/internal/glass-generator.js +398 -0
- package/scripts/cli/internal/hook-generator.js +369 -0
- package/scripts/cli/internal/hooks.js +61 -0
- package/scripts/cli/internal/itcss-generator.js +565 -0
- package/scripts/cli/internal/motion-generator.js +679 -0
- package/scripts/cli/internal/template-engine.js +301 -0
- package/scripts/cli/internal/theme-bridge.js +664 -0
- package/scripts/cli/internal/tokens/engine.js +122 -0
- package/scripts/cli/internal/tokens/provider.js +34 -0
- package/scripts/cli/internal/tokens/providers/figma.js +50 -0
- package/scripts/cli/internal/tokens/providers/style-dictionary.js +48 -0
- package/scripts/cli/internal/tokens/providers/w3c.js +48 -0
- package/scripts/cli/internal/tokens/token-provider.js +443 -0
- package/scripts/cli/internal/tokens/token-validator.js +513 -0
- package/scripts/cli/internal/validator.js +276 -0
- package/scripts/cli/internal/wizard.js +115 -0
- package/scripts/cli/mappings.js +23 -0
- package/scripts/cli/migration-tools.js +164 -94
- package/scripts/cli/plugins/style-dictionary.js +46 -0
- package/scripts/cli/templates/README.md +525 -95
- package/scripts/cli/templates/common-templates.js +40 -14
- package/scripts/cli/templates/components/react-component.ts +282 -0
- package/scripts/cli/templates/config/project-config.ts +112 -0
- package/scripts/cli/templates/hooks/use-component.ts +477 -0
- package/scripts/cli/templates/index.js +19 -4
- package/scripts/cli/templates/index.ts +171 -0
- package/scripts/cli/templates/next-templates.js +72 -0
- package/scripts/cli/templates/react-templates.js +70 -126
- package/scripts/cli/templates/scss-templates.js +35 -35
- package/scripts/cli/templates/stories/storybook-story.ts +241 -0
- package/scripts/cli/templates/styles/scss-component.ts +255 -0
- package/scripts/cli/templates/tests/vitest-test.ts +229 -0
- package/scripts/cli/templates/token-templates.js +337 -1
- package/scripts/cli/templates/tokens/token-generators.ts +1088 -0
- package/scripts/cli/templates/types/component-types.ts +145 -0
- package/scripts/cli/templates/utils/testing-utils.ts +144 -0
- package/scripts/cli/templates/vanilla-templates.js +39 -0
- package/scripts/cli/token-manager.js +8 -2
- package/scripts/cli/utils/cache-manager.js +240 -0
- package/scripts/cli/utils/detector.js +46 -0
- package/scripts/cli/utils/diagnostics.js +289 -0
- package/scripts/cli/utils/error.js +89 -0
- package/scripts/cli/utils/helpers.js +67 -0
- package/scripts/cli/utils/logger.js +75 -0
- package/scripts/cli/utils/security.js +302 -0
- package/scripts/cli/utils/telemetry.js +115 -0
- package/scripts/cli/utils/validation.js +37 -0
- package/scripts/cli/utils.js +28 -341
- package/src/components/Accordion/Accordion.stories.tsx +0 -18
- package/src/components/Accordion/Accordion.test.tsx +0 -17
- package/src/components/Accordion/Accordion.tsx +0 -4
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +37 -3
- package/src/components/AtomixGlass/AtomixGlass.tsx +143 -31
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +129 -31
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +219 -0
- package/src/components/AtomixGlass/README.md +25 -10
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +216 -0
- package/src/components/AtomixGlass/animation-system.ts +578 -0
- package/src/components/AtomixGlass/shader-utils.ts +4 -1
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +157 -6
- package/src/components/AtomixGlass/stories/Phase1-Animation.stories.tsx +653 -0
- package/src/components/AtomixGlass/stories/Phase1-Test.stories.tsx +95 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +51 -51
- package/src/components/AtomixGlass/stories/shared-components.tsx +6 -0
- package/src/components/Avatar/Avatar.tsx +1 -1
- package/src/components/Button/Button.stories.disabled-link.tsx +10 -0
- package/src/components/Button/Button.stories.tsx +10 -0
- package/src/components/Button/Button.test.tsx +16 -11
- package/src/components/Button/Button.tsx +4 -4
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +12 -12
- package/src/components/Form/Select.tsx +62 -3
- package/src/components/Modal/Modal.tsx +14 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +44 -0
- package/src/components/Slider/Slider.stories.tsx +3 -3
- package/src/components/Slider/Slider.tsx +38 -0
- package/src/components/Steps/Steps.tsx +3 -3
- package/src/components/Tabs/Tabs.tsx +77 -8
- package/src/components/Testimonial/Testimonial.tsx +1 -1
- package/src/components/TypedButton/TypedButton.stories.tsx +59 -0
- package/src/components/TypedButton/TypedButton.tsx +39 -0
- package/src/components/TypedButton/index.ts +2 -0
- package/src/components/VideoPlayer/VideoPlayer.tsx +11 -4
- package/src/lib/composables/index.ts +4 -7
- package/src/lib/composables/types.ts +45 -0
- package/src/lib/composables/useAccordion.ts +0 -7
- package/src/lib/composables/useAtomixGlass.ts +148 -6
- package/src/lib/composables/useAtomixGlassStyles.ts +9 -7
- package/src/lib/composables/useChartExport.ts +3 -13
- package/src/lib/composables/useDropdown.ts +66 -0
- package/src/lib/composables/useFocusTrap.ts +80 -0
- package/src/lib/composables/usePerformanceMonitor.ts +448 -0
- package/src/lib/composables/useResponsiveGlass.presets.ts +192 -0
- package/src/lib/composables/useResponsiveGlass.ts +441 -0
- package/src/lib/composables/useTooltip.ts +16 -0
- package/src/lib/composables/useTypedButton.ts +66 -0
- package/src/lib/config/index.ts +62 -5
- package/src/lib/constants/components.ts +62 -7
- package/src/lib/theme/devtools/__tests__/useHistory.test.tsx +150 -0
- package/src/lib/theme/tokens/centralized-tokens.ts +120 -0
- package/src/lib/theme/utils/__tests__/domUtils.test.ts +101 -0
- package/src/lib/types/components.ts +37 -11
- package/src/lib/types/glass.ts +35 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/utils/displacement-generator.ts +1 -1
- package/src/styles/01-settings/_settings.testtypecheck.scss +53 -0
- package/src/styles/01-settings/_settings.typedbutton.scss +53 -0
- package/src/styles/06-components/_components.atomix-glass.scss +17 -21
- package/src/styles/06-components/_components.edge-panel.scss +1 -5
- package/src/styles/06-components/_components.modal.scss +1 -4
- package/src/styles/06-components/_components.navbar.scss +1 -1
- package/src/styles/06-components/_components.testbutton.scss +212 -0
- package/src/styles/06-components/_components.testtypecheck.scss +212 -0
- package/src/styles/06-components/_components.tooltip.scss +9 -5
- package/src/styles/06-components/_components.typedbutton.scss +212 -0
- package/src/styles/99-utilities/_index.scss +1 -0
- package/src/styles/99-utilities/_utilities.text.scss +1 -1
- package/src/styles/99-utilities/_utilities.touch-target.scss +36 -0
- package/scripts/cli/component-generator.js +0 -564
- package/scripts/cli/interactive-init.js +0 -357
- package/src/styles/06-components/old.chart.styles.scss +0 -2788
package/scripts/cli/utils.js
CHANGED
|
@@ -1,359 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI
|
|
3
|
-
*
|
|
2
|
+
* Atomix CLI Utils Barrel
|
|
3
|
+
* Re-exports for tests and backward compatibility. Prefer importing from utils/*.js in command code.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { validateSecurePath, validateComponentNameSecure } from './utils/security.js';
|
|
7
|
+
import { validateThemeName } from './utils/validation.js';
|
|
8
|
+
import { sanitizeInput, fileExists } from './utils/helpers.js';
|
|
9
|
+
import { AtomixCLIError, ErrorCategory } from './utils/error.js';
|
|
10
|
+
import { resolve, normalize } from 'path';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
13
|
+
* Validates path: security check and sensitive file check.
|
|
14
|
+
* @param {string} inputPath - Path to validate
|
|
15
|
+
* @param {string} basePath - Base directory (defaults to process.cwd())
|
|
16
|
+
* @returns {{ isValid: boolean, error?: string, safePath?: string }}
|
|
11
17
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this.suggestions = suggestions;
|
|
18
|
+
function validatePath(inputPath, basePath = process.cwd()) {
|
|
19
|
+
const normalized = normalize(resolve(basePath, inputPath));
|
|
20
|
+
const sensitive = ['.env', '.npmrc', '.env.local', '.env.production', 'id_rsa', '.ssh'];
|
|
21
|
+
if (sensitive.some(s => normalized.includes(s))) {
|
|
22
|
+
return { isValid: false, error: 'Path targets a sensitive path and is not allowed.' };
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* @param {string} inputPath - The path to validate
|
|
24
|
-
* @param {string} basePath - The base directory (defaults to process.cwd())
|
|
25
|
-
* @returns {Object} { isValid: boolean, safePath: string, error?: string }
|
|
26
|
-
*/
|
|
27
|
-
export function validatePath(inputPath, basePath = process.cwd()) {
|
|
28
|
-
try {
|
|
29
|
-
// Normalize the paths to remove any '..' or '.' segments
|
|
30
|
-
const normalizedBase = normalize(resolve(basePath));
|
|
31
|
-
const normalizedInput = normalize(isAbsolute(inputPath)
|
|
32
|
-
? inputPath
|
|
33
|
-
: resolve(basePath, inputPath));
|
|
34
|
-
|
|
35
|
-
// Check if the resolved path is within the base directory
|
|
36
|
-
const relativePath = relative(normalizedBase, normalizedInput);
|
|
37
|
-
|
|
38
|
-
// If the relative path starts with '..', it's outside the base directory
|
|
39
|
-
if (relativePath.startsWith('..')) {
|
|
40
|
-
return {
|
|
41
|
-
isValid: false,
|
|
42
|
-
safePath: null,
|
|
43
|
-
error: 'Path is outside the project directory'
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Additional checks for sensitive paths
|
|
48
|
-
const sensitivePatterns = [
|
|
49
|
-
/^\.git/,
|
|
50
|
-
/node_modules/,
|
|
51
|
-
/^\.env/,
|
|
52
|
-
/\.pem$/,
|
|
53
|
-
/\.key$/,
|
|
54
|
-
/private/i,
|
|
55
|
-
/secret/i
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
for (const pattern of sensitivePatterns) {
|
|
59
|
-
if (pattern.test(relativePath)) {
|
|
60
|
-
return {
|
|
61
|
-
isValid: false,
|
|
62
|
-
safePath: null,
|
|
63
|
-
error: `Access to sensitive path is restricted: ${pattern}`
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
isValid: true,
|
|
70
|
-
safePath: normalizedInput,
|
|
71
|
-
error: null
|
|
72
|
-
};
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
isValid: false,
|
|
76
|
-
safePath: null,
|
|
77
|
-
error: `Invalid path: ${error.message}`
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Validates component names according to PascalCase convention
|
|
84
|
-
* @param {string} name - The component name to validate
|
|
85
|
-
* @returns {Object} { isValid: boolean, error?: string }
|
|
86
|
-
*/
|
|
87
|
-
export function validateComponentName(name) {
|
|
88
|
-
if (!name || typeof name !== 'string') {
|
|
89
|
-
return {
|
|
90
|
-
isValid: false,
|
|
91
|
-
error: 'Component name must be a non-empty string'
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Check PascalCase: starts with uppercase, only contains letters and numbers
|
|
96
|
-
if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
97
|
-
return {
|
|
98
|
-
isValid: false,
|
|
99
|
-
error: 'Component name must be in PascalCase (e.g., Button, CardHeader)'
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Check for reserved words
|
|
104
|
-
const reservedWords = [
|
|
105
|
-
'Component', 'React', 'Fragment', 'Suspense', 'StrictMode',
|
|
106
|
-
'Error', 'Loading', 'App', 'Root', 'Document', 'Html'
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
if (reservedWords.includes(name)) {
|
|
110
|
-
return {
|
|
111
|
-
isValid: false,
|
|
112
|
-
error: `"${name}" is a reserved word. Please choose a different name.`
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check minimum length
|
|
117
|
-
if (name.length < 2) {
|
|
118
|
-
return {
|
|
119
|
-
isValid: false,
|
|
120
|
-
error: 'Component name must be at least 2 characters long'
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return { isValid: true };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Validates theme names according to kebab-case convention
|
|
129
|
-
* @param {string} name - The theme name to validate
|
|
130
|
-
* @returns {Object} { isValid: boolean, error?: string }
|
|
131
|
-
*/
|
|
132
|
-
export function validateThemeName(name) {
|
|
133
|
-
if (!name || typeof name !== 'string') {
|
|
134
|
-
return {
|
|
135
|
-
isValid: false,
|
|
136
|
-
error: 'Theme name must be a non-empty string'
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Check kebab-case: lowercase letters, numbers, and hyphens
|
|
141
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
142
|
-
return {
|
|
143
|
-
isValid: false,
|
|
144
|
-
error: 'Theme name must be lowercase and use hyphens (e.g., dark-theme)'
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Check for consecutive hyphens
|
|
149
|
-
if (/--/.test(name)) {
|
|
150
|
-
return {
|
|
151
|
-
isValid: false,
|
|
152
|
-
error: 'Theme name cannot contain consecutive hyphens'
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Check for trailing hyphen
|
|
157
|
-
if (name.endsWith('-')) {
|
|
158
|
-
return {
|
|
159
|
-
isValid: false,
|
|
160
|
-
error: 'Theme name cannot end with a hyphen'
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return { isValid: true };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Sanitizes user input to prevent injection attacks
|
|
169
|
-
* @param {string} input - The user input to sanitize
|
|
170
|
-
* @returns {string} Sanitized input
|
|
171
|
-
*/
|
|
172
|
-
export function sanitizeInput(input) {
|
|
173
|
-
if (typeof input !== 'string') {
|
|
174
|
-
return String(input);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Remove any shell metacharacters that could be dangerous
|
|
178
|
-
// Added single and double quotes to the blacklist to prevent shell injection
|
|
179
|
-
return input
|
|
180
|
-
.replace(/[;&|`$<>\\"']/g, '')
|
|
181
|
-
.replace(/\0/g, '') // Remove null bytes
|
|
182
|
-
.trim();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Checks if a file exists and is accessible
|
|
187
|
-
* @param {string} filePath - Path to check
|
|
188
|
-
* @returns {Promise<boolean>}
|
|
189
|
-
*/
|
|
190
|
-
export async function fileExists(filePath) {
|
|
191
|
-
try {
|
|
192
|
-
await access(filePath);
|
|
193
|
-
return true;
|
|
194
|
-
} catch {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Checks if running in CI environment
|
|
201
|
-
* @returns {boolean}
|
|
202
|
-
*/
|
|
203
|
-
export function isCI() {
|
|
204
|
-
return !!(
|
|
205
|
-
process.env.CI ||
|
|
206
|
-
process.env.CONTINUOUS_INTEGRATION ||
|
|
207
|
-
process.env.GITHUB_ACTIONS ||
|
|
208
|
-
process.env.GITLAB_CI ||
|
|
209
|
-
process.env.CIRCLECI ||
|
|
210
|
-
process.env.TRAVIS ||
|
|
211
|
-
process.env.JENKINS_URL
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Checks if running in debug mode
|
|
217
|
-
* @returns {boolean}
|
|
218
|
-
*/
|
|
219
|
-
export function isDebug() {
|
|
220
|
-
return process.env.ATOMIX_DEBUG === 'true' ||
|
|
221
|
-
process.argv.includes('--debug') ||
|
|
222
|
-
process.argv.includes('-d');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Formats file size in human readable format
|
|
227
|
-
* @param {number} bytes - File size in bytes
|
|
228
|
-
* @returns {string} Formatted size
|
|
229
|
-
*/
|
|
230
|
-
export function formatFileSize(bytes) {
|
|
231
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
232
|
-
if (bytes === 0) return '0 B';
|
|
233
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
234
|
-
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Debounce function for watch mode
|
|
239
|
-
* @param {Function} func - Function to debounce
|
|
240
|
-
* @param {number} wait - Wait time in milliseconds
|
|
241
|
-
* @returns {Function} Debounced function
|
|
242
|
-
*/
|
|
243
|
-
export function debounce(func, wait) {
|
|
244
|
-
let timeout;
|
|
245
|
-
return function executedFunction(...args) {
|
|
246
|
-
const later = () => {
|
|
247
|
-
clearTimeout(timeout);
|
|
248
|
-
func(...args);
|
|
249
|
-
};
|
|
250
|
-
clearTimeout(timeout);
|
|
251
|
-
timeout = setTimeout(later, wait);
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Creates a safe file path for cross-platform compatibility
|
|
257
|
-
* @param {...string} segments - Path segments
|
|
258
|
-
* @returns {string} Safe file path
|
|
259
|
-
*/
|
|
260
|
-
export function safePath(...segments) {
|
|
261
|
-
// Filter out empty segments and join with proper separator
|
|
262
|
-
return segments
|
|
263
|
-
.filter(Boolean)
|
|
264
|
-
.join('/')
|
|
265
|
-
.replace(/\/+/g, '/') // Remove duplicate slashes
|
|
266
|
-
.replace(/\\/g, '/'); // Convert Windows backslashes
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Validates SCSS/CSS color values
|
|
271
|
-
* @param {string} color - Color value to validate
|
|
272
|
-
* @returns {boolean}
|
|
273
|
-
*/
|
|
274
|
-
export function isValidColor(color) {
|
|
275
|
-
const patterns = [
|
|
276
|
-
/^#[0-9A-F]{3}$/i, // #RGB
|
|
277
|
-
/^#[0-9A-F]{4}$/i, // #RGBA
|
|
278
|
-
/^#[0-9A-F]{6}$/i, // #RRGGBB
|
|
279
|
-
/^#[0-9A-F]{8}$/i, // #RRGGBBAA
|
|
280
|
-
/^rgb\(/i, // rgb()
|
|
281
|
-
/^rgba\(/i, // rgba()
|
|
282
|
-
/^hsl\(/i, // hsl()
|
|
283
|
-
/^hsla\(/i, // hsla()
|
|
284
|
-
/^var\(--/ // CSS custom property
|
|
285
|
-
];
|
|
286
|
-
|
|
287
|
-
return patterns.some(pattern => pattern.test(color));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Extracts and validates npm scripts from package.json
|
|
292
|
-
* @param {Object} packageJson - Parsed package.json content
|
|
293
|
-
* @param {Array<string>} requiredScripts - List of required script names
|
|
294
|
-
* @returns {Object} { valid: boolean, missing: Array<string> }
|
|
295
|
-
*/
|
|
296
|
-
export function validateNpmScripts(packageJson, requiredScripts = []) {
|
|
297
|
-
const scripts = packageJson.scripts || {};
|
|
298
|
-
const missing = requiredScripts.filter(script => !scripts[script]);
|
|
299
|
-
|
|
24
|
+
const result = validateSecurePath(inputPath, basePath);
|
|
25
|
+
const error = result.error === 'Path traversal attempt detected'
|
|
26
|
+
? 'Path is outside the project directory.'
|
|
27
|
+
: (result.error || null);
|
|
300
28
|
return {
|
|
301
|
-
|
|
302
|
-
|
|
29
|
+
isValid: result.isValid,
|
|
30
|
+
error,
|
|
31
|
+
safePath: result.safePath || null
|
|
303
32
|
};
|
|
304
33
|
}
|
|
305
34
|
|
|
306
|
-
/**
|
|
307
|
-
|
|
308
|
-
* @param {string} prefix - Prefix for the ID
|
|
309
|
-
* @returns {string} Unique ID
|
|
310
|
-
*/
|
|
311
|
-
export function generateId(prefix = 'atomix') {
|
|
312
|
-
const timestamp = Date.now().toString(36);
|
|
313
|
-
const random = Math.random().toString(36).substring(2, 7);
|
|
314
|
-
return `${prefix}-${timestamp}-${random}`;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Checks Node.js version compatibility
|
|
319
|
-
* @param {string} requiredVersion - Minimum required version (e.g., '18.0.0')
|
|
320
|
-
* @returns {Object} { compatible: boolean, current: string, required: string }
|
|
321
|
-
*/
|
|
322
|
-
export function checkNodeVersion(requiredVersion = '18.0.0') {
|
|
323
|
-
const currentVersion = process.version.substring(1); // Remove 'v' prefix
|
|
324
|
-
const current = currentVersion.split('.').map(Number);
|
|
325
|
-
const required = requiredVersion.split('.').map(Number);
|
|
326
|
-
|
|
327
|
-
let compatible = true;
|
|
328
|
-
for (let i = 0; i < required.length; i++) {
|
|
329
|
-
if (current[i] < required[i]) {
|
|
330
|
-
compatible = false;
|
|
331
|
-
break;
|
|
332
|
-
} else if (current[i] > required[i]) {
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
compatible,
|
|
339
|
-
current: currentVersion,
|
|
340
|
-
required: requiredVersion
|
|
341
|
-
};
|
|
342
|
-
}
|
|
35
|
+
/** Sync component name validation (PascalCase, reserved words). Use validation.validateComponentName for async. */
|
|
36
|
+
const validateComponentName = validateComponentNameSecure;
|
|
343
37
|
|
|
344
|
-
export
|
|
38
|
+
export {
|
|
345
39
|
validatePath,
|
|
346
40
|
validateComponentName,
|
|
347
41
|
validateThemeName,
|
|
348
42
|
sanitizeInput,
|
|
349
43
|
fileExists,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
formatFileSize,
|
|
353
|
-
debounce,
|
|
354
|
-
safePath,
|
|
355
|
-
isValidColor,
|
|
356
|
-
validateNpmScripts,
|
|
357
|
-
generateId,
|
|
358
|
-
checkNodeVersion
|
|
44
|
+
AtomixCLIError,
|
|
45
|
+
ErrorCategory
|
|
359
46
|
};
|
|
@@ -22,8 +22,6 @@ const IS_DISABLED_CLASS = ACCORDION.CLASSES.IS_DISABLED;
|
|
|
22
22
|
|
|
23
23
|
const mockHandlers = {
|
|
24
24
|
onOpenChange: fn(() => {}),
|
|
25
|
-
onOpen: fn(() => {}),
|
|
26
|
-
onClose: fn(() => {}),
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
// Sample content for stories
|
|
@@ -222,20 +220,6 @@ const meta = {
|
|
|
222
220
|
type: { summary: '(open: boolean) => void' },
|
|
223
221
|
},
|
|
224
222
|
},
|
|
225
|
-
onOpen: {
|
|
226
|
-
action: 'onOpen',
|
|
227
|
-
description: 'Callback when accordion opens',
|
|
228
|
-
table: {
|
|
229
|
-
type: { summary: '() => void' },
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
onClose: {
|
|
233
|
-
action: 'onClose',
|
|
234
|
-
description: 'Callback when accordion closes',
|
|
235
|
-
table: {
|
|
236
|
-
type: { summary: '() => void' },
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
223
|
},
|
|
240
224
|
} satisfies Meta<typeof Accordion>;
|
|
241
225
|
|
|
@@ -268,8 +252,6 @@ export const WithAllProps: Story = {
|
|
|
268
252
|
iconPosition: 'left',
|
|
269
253
|
disabled: false,
|
|
270
254
|
onOpenChange: mockHandlers.onOpenChange,
|
|
271
|
-
onOpen: mockHandlers.onOpen,
|
|
272
|
-
onClose: mockHandlers.onClose,
|
|
273
255
|
},
|
|
274
256
|
parameters: {
|
|
275
257
|
docs: {
|
|
@@ -28,23 +28,6 @@ describe('Accordion Component', () => {
|
|
|
28
28
|
expect(button).toHaveAttribute('aria-expanded', 'false');
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('calls legacy onOpen/onClose handlers', () => {
|
|
32
|
-
const onOpen = vi.fn();
|
|
33
|
-
const onClose = vi.fn();
|
|
34
|
-
render(
|
|
35
|
-
<Accordion title="Test" onOpen={onOpen} onClose={onClose}>
|
|
36
|
-
Content
|
|
37
|
-
</Accordion>
|
|
38
|
-
);
|
|
39
|
-
const button = screen.getByRole('button');
|
|
40
|
-
|
|
41
|
-
fireEvent.click(button);
|
|
42
|
-
expect(onOpen).toHaveBeenCalled();
|
|
43
|
-
|
|
44
|
-
fireEvent.click(button);
|
|
45
|
-
expect(onClose).toHaveBeenCalled();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
31
|
it('handles controlled state', () => {
|
|
49
32
|
const onOpenChange = vi.fn();
|
|
50
33
|
const { rerender } = render(
|
|
@@ -114,8 +114,6 @@ const AccordionImpl = memo(
|
|
|
114
114
|
defaultOpen = false,
|
|
115
115
|
isOpen: controlledOpen,
|
|
116
116
|
onOpenChange,
|
|
117
|
-
onOpen,
|
|
118
|
-
onClose,
|
|
119
117
|
disabled = false,
|
|
120
118
|
iconPosition = 'right',
|
|
121
119
|
icon,
|
|
@@ -143,8 +141,6 @@ const AccordionImpl = memo(
|
|
|
143
141
|
iconPosition,
|
|
144
142
|
isOpen: controlledOpen,
|
|
145
143
|
onOpenChange,
|
|
146
|
-
onOpen,
|
|
147
|
-
onClose,
|
|
148
144
|
});
|
|
149
145
|
|
|
150
146
|
const headerClassNames = generateHeaderClassNames();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { vi } from 'vitest';
|
|
5
5
|
import AtomixGlass from './AtomixGlass';
|
|
@@ -158,18 +158,52 @@ describe('AtomixGlass Component', () => {
|
|
|
158
158
|
expect(container.querySelector('.c-atomix-glass__container')).toBeInTheDocument();
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
test('applies custom style', () => {
|
|
162
|
-
const customStyle = { backgroundColor: 'red' };
|
|
161
|
+
test('applies custom style to root/container layout', () => {
|
|
162
|
+
const customStyle: React.CSSProperties = { backgroundColor: 'red', position: 'fixed', top: 0, left: 0 };
|
|
163
163
|
const { container } = render(
|
|
164
164
|
<AtomixGlass style={customStyle}>
|
|
165
165
|
<div>Content</div>
|
|
166
166
|
</AtomixGlass>
|
|
167
167
|
);
|
|
168
168
|
|
|
169
|
+
const root = container.querySelector('.c-atomix-glass');
|
|
169
170
|
const glassContainer = container.querySelector('.c-atomix-glass__container');
|
|
171
|
+
expect(root).toHaveStyle('position: fixed');
|
|
170
172
|
expect(glassContainer).toHaveStyle('background-color: rgb(255, 0, 0)');
|
|
171
173
|
});
|
|
172
174
|
|
|
175
|
+
test('sets 100% width/height for fixed/sticky positioning', async () => {
|
|
176
|
+
const { container } = render(
|
|
177
|
+
<AtomixGlass style={{ position: 'fixed' }}>
|
|
178
|
+
<div>Content</div>
|
|
179
|
+
</AtomixGlass>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const glassContainer = container.querySelector('.c-atomix-glass__container');
|
|
183
|
+
|
|
184
|
+
// Use waitFor because updateAtomixGlassStyles is called imperatively inside a requestAnimationFrame loop
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
// With the new logic, fixed/sticky elements use measured sizes,
|
|
187
|
+
// not 100% (which is for standard flow)
|
|
188
|
+
expect(glassContainer).not.toHaveStyle('--atomix-glass-container-width: 100%');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('sets 100% width/height for standard flow (not fixed/sticky)', async () => {
|
|
193
|
+
const { container } = render(
|
|
194
|
+
<AtomixGlass>
|
|
195
|
+
<div>Content</div>
|
|
196
|
+
</AtomixGlass>
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const glassContainer = container.querySelector('.c-atomix-glass__container');
|
|
200
|
+
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
expect(glassContainer).toHaveStyle('--atomix-glass-container-width: 100%');
|
|
203
|
+
expect(glassContainer).toHaveStyle('--atomix-glass-container-height: 100%');
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
173
207
|
test('uses standard mode by default', () => {
|
|
174
208
|
const { container } = render(
|
|
175
209
|
<AtomixGlass>
|