@shohojdhara/atomix 0.5.2 → 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 (39) hide show
  1. package/atomix.config.ts +33 -33
  2. package/dist/config.d.ts +187 -112
  3. package/dist/config.js +7 -49
  4. package/dist/config.js.map +1 -1
  5. package/dist/index.d.ts +1958 -900
  6. package/dist/index.esm.js +2275 -383
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +2327 -417
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/theme.d.ts +1390 -276
  13. package/dist/theme.js +2129 -621
  14. package/dist/theme.js.map +1 -1
  15. package/package.json +1 -1
  16. package/scripts/cli/internal/config-loader.js +30 -20
  17. package/src/lib/config/index.ts +38 -362
  18. package/src/lib/config/loader.ts +419 -0
  19. package/src/lib/config/public-api.ts +43 -0
  20. package/src/lib/config/types.ts +389 -0
  21. package/src/lib/config/validator.ts +305 -0
  22. package/src/lib/theme/adapters/index.ts +1 -1
  23. package/src/lib/theme/adapters/themeAdapter.ts +358 -229
  24. package/src/lib/theme/components/ThemeToggle.tsx +276 -0
  25. package/src/lib/theme/config/configLoader.ts +351 -0
  26. package/src/lib/theme/config/loader.ts +221 -0
  27. package/src/lib/theme/core/createTheme.ts +126 -50
  28. package/src/lib/theme/core/createThemeObject.ts +7 -4
  29. package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
  30. package/src/lib/theme/index.ts +322 -38
  31. package/src/lib/theme/runtime/ThemeProvider.tsx +44 -10
  32. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
  33. package/src/lib/theme/runtime/useTheme.ts +1 -0
  34. package/src/lib/theme/tokens/tokens.ts +101 -1
  35. package/src/lib/theme/types.ts +91 -0
  36. package/src/lib/theme/utils/performanceMonitor.ts +315 -0
  37. package/src/lib/theme/utils/responsive.ts +280 -0
  38. package/src/lib/theme/utils/themeUtils.ts +531 -117
  39. package/src/styles/05-objects/_objects.masonry-grid.scss +3 -3
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Atomix Config Loader
3
+ *
4
+ * Helper functions to load atomix.config.ts from external projects.
5
+ * Now also supports atomix.config.js and atomix.config.json
6
+ */
7
+
8
+ import type { AtomixConfig } from './types';
9
+ import { existsSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ /**
13
+ * Validate Atomix configuration structure
14
+ *
15
+ * Performs basic validation to catch common configuration errors early.
16
+ * Returns warnings for potential issues.
17
+ *
18
+ * @param config - Configuration object to validate
19
+ * @returns Array of validation warnings (empty if valid)
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { loadAtomixConfig, validateConfig } from '@shohojdhara/atomix/config';
24
+ *
25
+ * const config = loadAtomixConfig();
26
+ * const warnings = validateConfig(config);
27
+ * warnings.forEach(w => console.warn(w));
28
+ * ```
29
+ */
30
+ export function validateConfig(config: AtomixConfig): string[] {
31
+ const warnings: string[] = [];
32
+
33
+ // Check prefix format
34
+ if (config.prefix) {
35
+ if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(config.prefix)) {
36
+ warnings.push(
37
+ `Invalid prefix "${config.prefix}". Prefix should start with a letter and contain only letters, numbers, and hyphens.\n` +
38
+ 'Example: "myapp", "brand-ui", "enterprise"'
39
+ );
40
+ }
41
+
42
+ if (config.prefix.length < 2) {
43
+ warnings.push(
44
+ `Prefix "${config.prefix}" is too short. Use at least 2 characters for clarity.\n` +
45
+ 'Example: "app" instead of "a"'
46
+ );
47
+ }
48
+ }
49
+
50
+ // Check theme structure
51
+ if (config.theme) {
52
+ // Warn if both extend and tokens are provided
53
+ if (config.theme.extend && config.theme.tokens) {
54
+ warnings.push(
55
+ 'Both theme.extend and theme.tokens are defined. theme.tokens will take precedence and completely replace the default token system.\n' +
56
+ 'If you want to extend defaults, remove theme.tokens and use only theme.extend.'
57
+ );
58
+ }
59
+
60
+ // Check extend structure
61
+ if (config.theme.extend) {
62
+ const extend = config.theme.extend;
63
+
64
+ // Check for common typos in theme properties
65
+ const validThemeKeys = [
66
+ 'colors', 'typography', 'spacing', 'borderRadius',
67
+ 'shadows', 'zIndex', 'transitions', 'breakpoints'
68
+ ];
69
+
70
+ Object.keys(extend).forEach(key => {
71
+ if (!validThemeKeys.includes(key)) {
72
+ warnings.push(
73
+ `Unknown theme property: "${key}"\n` +
74
+ `Valid properties: ${validThemeKeys.join(', ')}\n` +
75
+ 'Did you mean one of these? Check for typos.'
76
+ );
77
+ }
78
+ });
79
+ }
80
+ }
81
+
82
+ // Validate advanced features
83
+ if (config.interactiveEffects) {
84
+ const ie = config.interactiveEffects;
85
+
86
+ // Validate vortex settings
87
+ if (ie.vortex) {
88
+ if (ie.vortex.strength && (ie.vortex.strength < 0 || ie.vortex.strength > 10)) {
89
+ warnings.push('Vortex strength should be between 0 and 10 for optimal performance');
90
+ }
91
+ if (ie.vortex.radius && ie.vortex.radius < 0) {
92
+ warnings.push('Vortex radius should be a positive number');
93
+ }
94
+ if (ie.vortex.decay && (ie.vortex.decay <= 0 || ie.vortex.decay > 1)) {
95
+ warnings.push('Vortex decay should be between 0 and 1');
96
+ }
97
+ }
98
+
99
+ // Validate chromatic aberration settings
100
+ if (ie.chromaticAberration) {
101
+ if (ie.chromaticAberration.redShift && Math.abs(ie.chromaticAberration.redShift) > 0.1) {
102
+ warnings.push('Chromatic red shift value seems unusually high (>0.1), verify this is intended');
103
+ }
104
+ if (ie.chromaticAberration.blueShift && Math.abs(ie.chromaticAberration.blueShift) > 0.1) {
105
+ warnings.push('Chromatic blue shift value seems unusually high (>0.1), verify this is intended');
106
+ }
107
+ if (ie.chromaticAberration.edgeThreshold && (ie.chromaticAberration.edgeThreshold < 0 || ie.chromaticAberration.edgeThreshold > 1)) {
108
+ warnings.push('Chromatic edge threshold should be between 0 and 1');
109
+ }
110
+ }
111
+
112
+ // Validate mouse interaction settings
113
+ if (ie.mouseInteraction) {
114
+ if (ie.mouseInteraction.sensitivity && ie.mouseInteraction.sensitivity < 0) {
115
+ warnings.push('Mouse sensitivity should be a positive number');
116
+ }
117
+ }
118
+
119
+ // Validate animation speed settings
120
+ if (ie.animationSpeed) {
121
+ if (ie.animationSpeed.base && ie.animationSpeed.base <= 0) {
122
+ warnings.push('Animation base speed should be greater than 0');
123
+ }
124
+ if (ie.animationSpeed.timeMultiplier && ie.animationSpeed.timeMultiplier <= 0) {
125
+ warnings.push('Animation time multiplier should be greater than 0');
126
+ }
127
+ }
128
+ }
129
+
130
+ // Validate optimization settings
131
+ if (config.optimization) {
132
+ const opt = config.optimization;
133
+
134
+ // Validate responsive breakpoints
135
+ if (opt.responsive && opt.responsive.breakpoints) {
136
+ const breakpoints = opt.responsive.breakpoints;
137
+ if (breakpoints.mobile && !isValidCSSLength(breakpoints.mobile)) {
138
+ warnings.push('Mobile breakpoint value is not a valid CSS length');
139
+ }
140
+ if (breakpoints.tablet && !isValidCSSLength(breakpoints.tablet)) {
141
+ warnings.push('Tablet breakpoint value is not a valid CSS length');
142
+ }
143
+ if (breakpoints.desktop && !isValidCSSLength(breakpoints.desktop)) {
144
+ warnings.push('Desktop breakpoint value is not a valid CSS length');
145
+ }
146
+ if (breakpoints.wide && !isValidCSSLength(breakpoints.wide)) {
147
+ warnings.push('Wide breakpoint value is not a valid CSS length');
148
+ }
149
+ }
150
+
151
+ // Validate device scaling
152
+ if (opt.responsive && opt.responsive.deviceScaling) {
153
+ const scaling = opt.responsive.deviceScaling;
154
+ if (scaling.mobile && (scaling.mobile <= 0 || scaling.mobile > 1)) {
155
+ warnings.push('Mobile device scaling should be between 0 and 1');
156
+ }
157
+ if (scaling.tablet && (scaling.tablet <= 0 || scaling.tablet > 1)) {
158
+ warnings.push('Tablet device scaling should be between 0 and 1');
159
+ }
160
+ if (scaling.desktop && (scaling.desktop <= 0 || scaling.desktop > 1)) {
161
+ warnings.push('Desktop device scaling should be between 0 and 1');
162
+ }
163
+ }
164
+
165
+ // Validate performance settings
166
+ if (opt.performance) {
167
+ if (opt.performance.fpsTarget && (opt.performance.fpsTarget <= 0 || opt.performance.fpsTarget > 240)) {
168
+ warnings.push('FPS target should be a reasonable value (typically 30-120)');
169
+ }
170
+ }
171
+
172
+ // Validate auto-scaling thresholds
173
+ if (opt.autoScaling && opt.autoScaling.qualityThresholds) {
174
+ const thresholds = opt.autoScaling.qualityThresholds;
175
+ if (thresholds.lowEnd && (thresholds.lowEnd < 0 || thresholds.lowEnd > 1)) {
176
+ warnings.push('Auto-scaling low-end threshold should be between 0 and 1');
177
+ }
178
+ if (thresholds.midRange && (thresholds.midRange < 0 || thresholds.midRange > 1)) {
179
+ warnings.push('Auto-scaling mid-range threshold should be between 0 and 1');
180
+ }
181
+ if (thresholds.highEnd && (thresholds.highEnd < 0 || thresholds.highEnd > 1)) {
182
+ warnings.push('Auto-scaling high-end threshold should be between 0 and 1');
183
+ }
184
+ }
185
+ }
186
+
187
+ // Validate visual polish settings
188
+ if (config.visualPolish) {
189
+ const vp = config.visualPolish;
190
+
191
+ // Validate content aware blur settings
192
+ if (vp.contentAwareBlur) {
193
+ if (vp.contentAwareBlur.edgePreservation !== undefined && typeof vp.contentAwareBlur.edgePreservation !== 'boolean') {
194
+ warnings.push('Content-aware blur edge preservation should be a boolean value');
195
+ }
196
+ if (vp.contentAwareBlur.depthDetection !== undefined && typeof vp.contentAwareBlur.depthDetection !== 'boolean') {
197
+ warnings.push('Content-aware blur depth detection should be a boolean value');
198
+ }
199
+ }
200
+
201
+ // Validate holographic effects settings
202
+ if (vp.holographicEffects) {
203
+ if (vp.holographicEffects.enabled !== undefined && typeof vp.holographicEffects.enabled !== 'boolean') {
204
+ warnings.push('Holographic effects enabled should be a boolean value');
205
+ }
206
+ if (vp.holographicEffects.rainbowDiffraction !== undefined && typeof vp.holographicEffects.rainbowDiffraction !== 'boolean') {
207
+ warnings.push('Holographic rainbow diffraction should be a boolean value');
208
+ }
209
+ }
210
+ }
211
+
212
+ // Validate AI settings
213
+ if (config.ai) {
214
+ if (config.ai.provider && !['openai', 'anthropic'].includes(config.ai.provider)) {
215
+ warnings.push(`Unknown AI provider: "${config.ai.provider}". Supported: openai, anthropic`);
216
+ }
217
+ if (config.ai.temperature && (config.ai.temperature < 0 || config.ai.temperature > 1)) {
218
+ warnings.push('AI temperature should be between 0 and 1');
219
+ }
220
+ if (config.ai.maxTokens && config.ai.maxTokens < 100) {
221
+ warnings.push('AI maxTokens should typically be 100 or more');
222
+ }
223
+ if (config.ai.rateLimit) {
224
+ if (config.ai.rateLimit.requests <= 0) {
225
+ warnings.push('AI rate limit requests should be greater than 0');
226
+ }
227
+ if (config.ai.rateLimit.windowMs <= 0) {
228
+ warnings.push('AI rate limit window should be greater than 0');
229
+ }
230
+ }
231
+ }
232
+
233
+ // Validate telemetry settings
234
+ if (config.telemetry) {
235
+ if (config.telemetry.path && !config.telemetry.path.endsWith('.json')) {
236
+ warnings.push('Telemetry path should typically end with .json');
237
+ }
238
+ }
239
+
240
+ return warnings;
241
+ }
242
+
243
+ /**
244
+ * Helper function to validate CSS length values
245
+ */
246
+ function isValidCSSLength(value: string): boolean {
247
+ // Basic validation for CSS length values
248
+ const cssLengthRegex = /^(\d+(\.\d+)?)(px|em|rem|%|vw|vh|vmin|vmax|cm|mm|in|pt|pc|ex|ch)?$/;
249
+ return cssLengthRegex.test(value);
250
+ }
251
+
252
+ /**
253
+ * Load Atomix configuration from project root
254
+ *
255
+ * Attempts to load atomix.config.ts, atomix.config.js, or atomix.config.json from the current working directory.
256
+ * Falls back to default config if file doesn't exist.
257
+ *
258
+ * @param options - Loader options
259
+ * @returns Loaded configuration or default
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * import { loadAtomixConfig } from '@shohojdhara/atomix/config';
264
+ * import { createTheme } from '@shohojdhara/atomix/theme';
265
+ *
266
+ * const config = loadAtomixConfig();
267
+ * const theme = createTheme(config.theme?.tokens || {});
268
+ * ```
269
+ */
270
+ export function loadAtomixConfig(
271
+ options: {
272
+ /** Custom config path (default: 'atomix.config.ts') */
273
+ configPath?: string;
274
+ /** Whether to throw error if config not found (default: false) */
275
+ required?: boolean;
276
+ } = {}
277
+ ): AtomixConfig {
278
+ const { configPath, required = false } = options;
279
+
280
+ // Default config
281
+ const defaultConfig: AtomixConfig = {
282
+ prefix: 'atomix',
283
+ theme: {
284
+ extend: {},
285
+ },
286
+ };
287
+
288
+ // In browser environments, config loading is not supported
289
+ if (typeof window !== 'undefined') {
290
+ if (required) {
291
+ throw new Error(
292
+ 'Config loading requires Node.js file system access.\n' +
293
+ '\n' +
294
+ 'Solutions:\n' +
295
+ '1. Provide tokens explicitly to createTheme():\n' +
296
+ ' const css = createTheme({ "--brand-primary": "#6366f1" });\n' +
297
+ '\n' +
298
+ '2. Use SSR framework (Next.js, Remix, Astro)\n' +
299
+ '\n' +
300
+ '3. Load config on server and pass to client\n' +
301
+ '\n' +
302
+ 'See examples/config-examples/browser-only.config.ts'
303
+ );
304
+ }
305
+ return defaultConfig;
306
+ }
307
+
308
+ // If a specific config path is provided, try to load it directly
309
+ if (configPath) {
310
+ return loadConfigAtPath(configPath, required, defaultConfig);
311
+ }
312
+
313
+ // Otherwise, try standard locations in order of preference
314
+ const possiblePaths = [
315
+ 'atomix.config.ts',
316
+ 'atomix.config.js',
317
+ 'atomix.config.json'
318
+ ];
319
+
320
+ for (const path of possiblePaths) {
321
+ const config = loadConfigAtPath(path, false, defaultConfig);
322
+ // If we found a valid config, return it
323
+ if (JSON.stringify(config) !== JSON.stringify(defaultConfig)) {
324
+ return config;
325
+ }
326
+ }
327
+
328
+ // If no config file was found or all contained only defaults, return default config
329
+ if (required) {
330
+ throw new Error(
331
+ `No Atomix configuration file found in project root.\n` +
332
+ '\n' +
333
+ 'Expected one of:\n' +
334
+ ' - atomix.config.ts (recommended)\n' +
335
+ ' - atomix.config.js\n' +
336
+ ' - atomix.config.json\n' +
337
+ '\n' +
338
+ 'Quick Fix:\n' +
339
+ '1. Create a config file in your project root:\n' +
340
+ ' touch atomix.config.ts\n' +
341
+ '\n' +
342
+ '2. Add basic configuration:\n' +
343
+ ' import { defineConfig } from "@shohojdhara/atomix/config";\n' +
344
+ ' export default defineConfig({ prefix: "myapp" });\n' +
345
+ '\n' +
346
+ '3. Or copy an example:\n' +
347
+ ' cp node_modules/@shohojdhara/atomix/examples/config-examples/standard.config.ts ./atomix.config.ts'
348
+ );
349
+ }
350
+
351
+ return defaultConfig;
352
+ }
353
+
354
+ /**
355
+ * Helper function to load config from a specific path
356
+ */
357
+ function loadConfigAtPath(path: string, required: boolean, defaultConfig: AtomixConfig): AtomixConfig {
358
+ try {
359
+ // Use dynamic import for ESM compatibility
360
+ const configModule = require(path);
361
+ const config = configModule.default || configModule;
362
+
363
+ // Validate it's an AtomixConfig
364
+ if (config && typeof config === 'object') {
365
+ return config as AtomixConfig;
366
+ }
367
+
368
+ throw new Error('Invalid config format');
369
+ } catch (error: any) {
370
+ if (required) {
371
+ throw new Error(`Failed to load config from ${path}: ${error.message}`);
372
+ }
373
+
374
+ // Return default config if not required
375
+ return defaultConfig;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Resolve config path
381
+ *
382
+ * Finds atomix.config.ts in the project, checking common locations.
383
+ * Returns null in browser environments where file system access is not available.
384
+ *
385
+ * This function is designed to help tools identify if a config exists without loading it.
386
+ *
387
+ * @param configPath - Optional custom path to check
388
+ * @returns Absolute path to config file or null if not found
389
+ */
390
+ export function resolveConfigPath(configPath?: string): string | null {
391
+ // In browser environments, config resolution is not possible
392
+ if (typeof window !== 'undefined') {
393
+ return null;
394
+ }
395
+
396
+ // If a specific config path is provided, check if it exists
397
+ if (configPath) {
398
+ const absPath = join(process.cwd(), configPath);
399
+ if (existsSync(absPath)) {
400
+ return absPath;
401
+ }
402
+ return null;
403
+ }
404
+
405
+ // Otherwise, check standard locations
406
+ const possiblePaths = [
407
+ join(process.cwd(), 'atomix.config.ts'),
408
+ join(process.cwd(), 'atomix.config.js'),
409
+ join(process.cwd(), 'atomix.config.json')
410
+ ];
411
+
412
+ for (const path of possiblePaths) {
413
+ if (existsSync(path)) {
414
+ return path;
415
+ }
416
+ }
417
+
418
+ return null;
419
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Public API for loading and managing Atomix configuration
3
+ *
4
+ * This module provides the public-facing API for configuration loading
5
+ * in external projects.
6
+ */
7
+
8
+ import type { AtomixConfig } from './types';
9
+ import { loadAtomixConfig as internalLoadConfig, validateConfig as internalValidateConfig } from './loader';
10
+
11
+ /**
12
+ * Load Atomix configuration from an external project.
13
+ *
14
+ * @param options - Loading options
15
+ * @returns The loaded configuration
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { loadConfig } from '@shohojdhara/atomix/config';
20
+ *
21
+ * const config = loadConfig();
22
+ * console.log(config.prefix); // 'atomix' or user's custom prefix
23
+ * ```
24
+ */
25
+ export function loadConfig(options?: {
26
+ configPath?: string;
27
+ required?: boolean;
28
+ }): AtomixConfig {
29
+ return internalLoadConfig({
30
+ configPath: options?.configPath,
31
+ required: options?.required ?? false,
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Validate Atomix configuration structure.
37
+ *
38
+ * @param config - Configuration object to validate
39
+ * @returns Array of validation warnings (empty if valid)
40
+ */
41
+ export function validateConfig(config: AtomixConfig): string[] {
42
+ return internalValidateConfig(config);
43
+ }