@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,276 @@
1
+ /**
2
+ * ThemeToggle Component
3
+ *
4
+ * A pre-built toggle button for switching between light and dark themes.
5
+ * Includes smooth animations and system preference detection.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { ThemeToggle } from '@shohojdhara/atomix/theme';
10
+ *
11
+ * function Header() {
12
+ * return (
13
+ * <header>
14
+ * <h1>My App</h1>
15
+ * <ThemeToggle />
16
+ * </header>
17
+ * );
18
+ * }
19
+ * ```
20
+ */
21
+
22
+ import React from 'react';
23
+ import { useThemeSwitcher } from '../hooks/useThemeSwitcher';
24
+ import type { UseThemeSwitcherOptions } from '../hooks/useThemeSwitcher';
25
+
26
+ export interface ThemeToggleProps extends Omit<UseThemeSwitcherOptions, 'initialMode'> {
27
+ /** Custom class name */
28
+ className?: string;
29
+ /** Show text label (default: false) */
30
+ showLabel?: boolean;
31
+ /** Label for light mode (default: 'Light') */
32
+ lightLabel?: string;
33
+ /** Label for dark mode (default: 'Dark') */
34
+ darkLabel?: string;
35
+ /** Icon size in pixels (default: 20) */
36
+ iconSize?: number;
37
+ /** Button variant (default: 'icon') */
38
+ variant?: 'icon' | 'button' | 'switch';
39
+ /** Custom render function */
40
+ render?: (props: {
41
+ isDark: boolean;
42
+ toggle: () => void;
43
+ mode: import('../utils/themeUtils').ThemeMode;
44
+ }) => React.ReactNode;
45
+ /** Aria label (default: 'Toggle theme') */
46
+ ariaLabel?: string;
47
+ }
48
+
49
+ /**
50
+ * ThemeToggle component with multiple variants
51
+ */
52
+ export const ThemeToggle: React.FC<ThemeToggleProps> = ({
53
+ className = '',
54
+ showLabel = false,
55
+ lightLabel = 'Light',
56
+ darkLabel = 'Dark',
57
+ iconSize = 20,
58
+ variant = 'icon',
59
+ render,
60
+ ariaLabel = 'Toggle theme',
61
+ ...hookOptions
62
+ }) => {
63
+ const { mode, isDark, toggle } = useThemeSwitcher(hookOptions);
64
+
65
+ // Custom render
66
+ if (render) {
67
+ return <>{render({ isDark, toggle, mode })}</>;
68
+ }
69
+
70
+ // Icon-only variant (default)
71
+ if (variant === 'icon') {
72
+ return (
73
+ <button
74
+ onClick={toggle}
75
+ className={`theme-toggle theme-toggle-icon ${className}`}
76
+ aria-label={ariaLabel}
77
+ title={isDark ? darkLabel : lightLabel}
78
+ style={{
79
+ background: 'none',
80
+ border: 'none',
81
+ cursor: 'pointer',
82
+ padding: '8px',
83
+ borderRadius: '50%',
84
+ display: 'flex',
85
+ alignItems: 'center',
86
+ justifyContent: 'center',
87
+ transition: 'all 0.3s ease-in-out',
88
+ }}
89
+ >
90
+ {isDark ? (
91
+ // Sun icon for dark mode (click to go light)
92
+ <svg
93
+ width={iconSize}
94
+ height={iconSize}
95
+ viewBox="0 0 24 24"
96
+ fill="none"
97
+ stroke="currentColor"
98
+ strokeWidth="2"
99
+ strokeLinecap="round"
100
+ strokeLinejoin="round"
101
+ >
102
+ <circle cx="12" cy="12" r="5" />
103
+ <line x1="12" y1="1" x2="12" y2="3" />
104
+ <line x1="12" y1="21" x2="12" y2="23" />
105
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
106
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
107
+ <line x1="1" y1="12" x2="3" y2="12" />
108
+ <line x1="21" y1="12" x2="23" y2="12" />
109
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
110
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
111
+ </svg>
112
+ ) : (
113
+ // Moon icon for light mode (click to go dark)
114
+ <svg
115
+ width={iconSize}
116
+ height={iconSize}
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ strokeWidth="2"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ >
124
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
125
+ </svg>
126
+ )}
127
+ </button>
128
+ );
129
+ }
130
+
131
+ // Button variant with text
132
+ if (variant === 'button') {
133
+ return (
134
+ <button
135
+ onClick={toggle}
136
+ className={`theme-toggle theme-toggle-button ${className}`}
137
+ aria-label={ariaLabel}
138
+ style={{
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ gap: '8px',
142
+ padding: '8px 16px',
143
+ borderRadius: '8px',
144
+ border: '1px solid currentColor',
145
+ background: 'transparent',
146
+ color: 'inherit',
147
+ cursor: 'pointer',
148
+ fontSize: '14px',
149
+ fontWeight: '500',
150
+ transition: 'all 0.3s ease-in-out',
151
+ }}
152
+ >
153
+ {isDark ? (
154
+ <svg
155
+ width={iconSize}
156
+ height={iconSize}
157
+ viewBox="0 0 24 24"
158
+ fill="none"
159
+ stroke="currentColor"
160
+ strokeWidth="2"
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ >
164
+ <circle cx="12" cy="12" r="5" />
165
+ <line x1="12" y1="1" x2="12" y2="3" />
166
+ <line x1="12" y1="21" x2="12" y2="23" />
167
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
168
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
169
+ <line x1="1" y1="12" x2="3" y2="12" />
170
+ <line x1="21" y1="12" x2="23" y2="12" />
171
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
172
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
173
+ </svg>
174
+ ) : (
175
+ <svg
176
+ width={iconSize}
177
+ height={iconSize}
178
+ viewBox="0 0 24 24"
179
+ fill="none"
180
+ stroke="currentColor"
181
+ strokeWidth="2"
182
+ strokeLinecap="round"
183
+ strokeLinejoin="round"
184
+ >
185
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
186
+ </svg>
187
+ )}
188
+ {showLabel && <span>{isDark ? darkLabel : lightLabel}</span>}
189
+ </button>
190
+ );
191
+ }
192
+
193
+ // Switch/toggle variant
194
+ if (variant === 'switch') {
195
+ return (
196
+ <div
197
+ className={`theme-toggle theme-toggle-switch ${className}`}
198
+ role="button"
199
+ tabIndex={0}
200
+ onClick={toggle}
201
+ onKeyDown={(e) => e.key === 'Enter' && toggle()}
202
+ aria-label={ariaLabel}
203
+ style={{
204
+ position: 'relative',
205
+ width: '56px',
206
+ height: '28px',
207
+ borderRadius: '14px',
208
+ background: isDark ? '#4b5563' : '#d1d5db',
209
+ cursor: 'pointer',
210
+ transition: 'background 0.3s ease-in-out',
211
+ display: 'flex',
212
+ alignItems: 'center',
213
+ padding: '2px',
214
+ }}
215
+ >
216
+ {/* Track */}
217
+ <div
218
+ style={{
219
+ position: 'absolute',
220
+ left: isDark ? 'auto' : '2px',
221
+ right: isDark ? '2px' : 'auto',
222
+ width: '24px',
223
+ height: '24px',
224
+ borderRadius: '50%',
225
+ background: 'white',
226
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
227
+ transition: 'all 0.3s ease-in-out',
228
+ display: 'flex',
229
+ alignItems: 'center',
230
+ justifyContent: 'center',
231
+ }}
232
+ >
233
+ {isDark ? (
234
+ <svg
235
+ width="14"
236
+ height="14"
237
+ viewBox="0 0 24 24"
238
+ fill="none"
239
+ stroke="#4b5563"
240
+ strokeWidth="2"
241
+ strokeLinecap="round"
242
+ strokeLinejoin="round"
243
+ >
244
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
245
+ </svg>
246
+ ) : (
247
+ <svg
248
+ width="14"
249
+ height="14"
250
+ viewBox="0 0 24 24"
251
+ fill="none"
252
+ stroke="#f59e0b"
253
+ strokeWidth="2"
254
+ strokeLinecap="round"
255
+ strokeLinejoin="round"
256
+ >
257
+ <circle cx="12" cy="12" r="5" />
258
+ <line x1="12" y1="1" x2="12" y2="3" />
259
+ <line x1="12" y1="21" x2="12" y2="23" />
260
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
261
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
262
+ <line x1="1" y1="12" x2="3" y2="12" />
263
+ <line x1="21" y1="12" x2="23" y2="12" />
264
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
265
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
266
+ </svg>
267
+ )}
268
+ </div>
269
+ </div>
270
+ );
271
+ }
272
+
273
+ return null;
274
+ };
275
+
276
+ ThemeToggle.displayName = 'ThemeToggle';
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Theme Configuration Loader
3
+ *
4
+ * Provides functions to load theme configurations from atomix.config.ts
5
+ * Now also supports atomix.config.js and atomix.config.json
6
+ * Includes both sync and async versions, with automatic fallbacks
7
+ */
8
+
9
+ import type { Theme } from '../types';
10
+ import type { DesignTokens } from '../tokens/tokens';
11
+ import { createTokens } from '../tokens/tokens';
12
+ import { themeToDesignTokens } from '../adapters/themeAdapter';
13
+
14
+ /**
15
+ * Load theme from config file (synchronous, Node.js only)
16
+ * @param configPath - Path to config file (default: atomix.config.ts)
17
+ * @returns DesignTokens from theme configuration
18
+ * @throws Error if config loading is not available in browser environment
19
+ */
20
+ export function loadThemeFromConfigSync(options?: { configPath?: string; required?: boolean }): DesignTokens {
21
+ // Check if we're in a browser environment
22
+ if (typeof window !== 'undefined') {
23
+ throw new Error('loadThemeFromConfigSync: Not available in browser environment. Config loading requires Node.js/SSR environment.');
24
+ }
25
+
26
+ // Use dynamic import to load the config loader
27
+ // This allows bundlers to handle external dependencies properly
28
+ let loadAtomixConfig: any;
29
+
30
+ try {
31
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
32
+ const { loadAtomixConfig: loader } = require('../../config/loader');
33
+ loadAtomixConfig = loader;
34
+ } catch (error) {
35
+ if (options?.required !== false) {
36
+ throw new Error('Config loader module not available');
37
+ }
38
+ // Return empty tokens if config is not required
39
+ return createTokens({});
40
+ }
41
+
42
+ const config = loadAtomixConfig({
43
+ configPath: options?.configPath,
44
+ required: options?.required !== false,
45
+ });
46
+
47
+ if (!config?.theme) {
48
+ return createTokens({});
49
+ }
50
+
51
+ if (isThemeObject(config.theme)) {
52
+ return themeToDesignTokens(config.theme);
53
+ }
54
+
55
+ // Handle the config.theme object which has extend/tokens/themes properties
56
+ // Extract the actual tokens from the theme configuration
57
+ const themeConfig = config.theme;
58
+ let tokensToApply = {};
59
+
60
+ if (themeConfig.tokens) {
61
+ // If tokens is provided, use it as the base
62
+ tokensToApply = themeConfig.tokens;
63
+ } else if (themeConfig.extend) {
64
+ // If only extend is provided, use it as overrides
65
+ tokensToApply = themeConfig.extend;
66
+ }
67
+
68
+ // Apply advanced feature configurations as tokens
69
+ if (config.interactiveEffects) {
70
+ // Vortex effects
71
+ if (config.interactiveEffects.vortex) {
72
+ tokensToApply = {
73
+ ...tokensToApply,
74
+ 'interactive-vortex-enabled': String(config.interactiveEffects.vortex.enabled ?? false),
75
+ 'interactive-vortex-strength': String(config.interactiveEffects.vortex.strength ?? 0.5),
76
+ 'interactive-vortex-radius': String(config.interactiveEffects.vortex.radius ?? 100),
77
+ 'interactive-vortex-decay': String(config.interactiveEffects.vortex.decay ?? 0.8),
78
+ };
79
+ }
80
+
81
+ // Chromatic aberration
82
+ if (config.interactiveEffects.chromaticAberration) {
83
+ tokensToApply = {
84
+ ...tokensToApply,
85
+ 'interactive-chromatic-enabled': String(config.interactiveEffects.chromaticAberration.enabled ?? false),
86
+ 'interactive-chromatic-mode': config.interactiveEffects.chromaticAberration.mode ?? 'lateral',
87
+ 'interactive-chromatic-red-shift': String(config.interactiveEffects.chromaticAberration.redShift ?? 0.02),
88
+ 'interactive-chromatic-green-shift': String(config.interactiveEffects.chromaticAberration.greenShift ?? 0),
89
+ 'interactive-chromatic-blue-shift': String(config.interactiveEffects.chromaticAberration.blueShift ?? -0.02),
90
+ 'interactive-chromatic-edge-only': String(config.interactiveEffects.chromaticAberration.edgeOnly ?? false),
91
+ 'interactive-chromatic-edge-threshold': String(config.interactiveEffects.chromaticAberration.edgeThreshold ?? 0.5),
92
+ };
93
+ }
94
+
95
+ // Mouse interaction
96
+ if (config.interactiveEffects.mouseInteraction) {
97
+ tokensToApply = {
98
+ ...tokensToApply,
99
+ 'interactive-mouse-sensitivity': String(config.interactiveEffects.mouseInteraction.sensitivity ?? 1.0),
100
+ 'interactive-mouse-trail-effect': String(config.interactiveEffects.mouseInteraction.trailEffect ?? false),
101
+ };
102
+ }
103
+
104
+ // Animation speed
105
+ if (config.interactiveEffects.animationSpeed) {
106
+ tokensToApply = {
107
+ ...tokensToApply,
108
+ 'interactive-animation-speed-base': String(config.interactiveEffects.animationSpeed.base ?? 1.0),
109
+ 'interactive-animation-speed-multiplier': String(config.interactiveEffects.animationSpeed.timeMultiplier ?? 1.0),
110
+ };
111
+ }
112
+ }
113
+
114
+ // Apply optimization configurations as tokens
115
+ if (config.optimization) {
116
+ // Responsive breakpoints
117
+ if (config.optimization.responsive) {
118
+ if (config.optimization.responsive.breakpoints) {
119
+ tokensToApply = {
120
+ ...tokensToApply,
121
+ 'optimization-breakpoint-mobile': config.optimization.responsive.breakpoints.mobile ?? '0px',
122
+ 'optimization-breakpoint-tablet': config.optimization.responsive.breakpoints.tablet ?? '768px',
123
+ 'optimization-breakpoint-desktop': config.optimization.responsive.breakpoints.desktop ?? '1024px',
124
+ 'optimization-breakpoint-wide': config.optimization.responsive.breakpoints.wide ?? '1440px',
125
+ };
126
+ }
127
+
128
+ if (config.optimization.responsive.deviceScaling) {
129
+ tokensToApply = {
130
+ ...tokensToApply,
131
+ 'optimization-device-scaling-mobile': String(config.optimization.responsive.deviceScaling.mobile ?? 0.5),
132
+ 'optimization-device-scaling-tablet': String(config.optimization.responsive.deviceScaling.tablet ?? 0.75),
133
+ 'optimization-device-scaling-desktop': String(config.optimization.responsive.deviceScaling.desktop ?? 1.0),
134
+ };
135
+ }
136
+ }
137
+
138
+ // Performance settings
139
+ if (config.optimization.performance) {
140
+ tokensToApply = {
141
+ ...tokensToApply,
142
+ 'optimization-performance-fps-target': String(config.optimization.performance.fpsTarget ?? 60),
143
+ 'optimization-auto-scaling-enabled': String(config.optimization.performance.autoScaling ?? false),
144
+ };
145
+ }
146
+
147
+ // Auto-scaling settings
148
+ if (config.optimization.autoScaling) {
149
+ tokensToApply = {
150
+ ...tokensToApply,
151
+ 'optimization-auto-scaling-enabled': String(config.optimization.autoScaling.enabled ?? false),
152
+ 'optimization-auto-scaling-low-end': String(config.optimization.autoScaling.qualityThresholds?.lowEnd ?? 0.5),
153
+ 'optimization-auto-scaling-mid-range': String(config.optimization.autoScaling.qualityThresholds?.midRange ?? 0.75),
154
+ 'optimization-auto-scaling-high-end': String(config.optimization.autoScaling.qualityThresholds?.highEnd ?? 1.0),
155
+ };
156
+ }
157
+ }
158
+
159
+ // Apply visual polish configurations as tokens
160
+ if (config.visualPolish) {
161
+ if (config.visualPolish.borders) {
162
+ tokensToApply = {
163
+ ...tokensToApply,
164
+ 'visual-polish-border-iridescent-glow': String(config.visualPolish.borders.iridescentGlow ?? false),
165
+ 'visual-polish-border-shimmer-effect': String(config.visualPolish.borders.shimmerEffect ?? false),
166
+ 'visual-polish-border-beveled-edges': String(config.visualPolish.borders.beveledEdges ?? false),
167
+ 'visual-polish-border-pulsing-glow': String(config.visualPolish.borders.pulsingGlow ?? false),
168
+ };
169
+ }
170
+
171
+ if (config.visualPolish.contentAwareBlur) {
172
+ tokensToApply = {
173
+ ...tokensToApply,
174
+ 'visual-polish-content-aware-blur-enabled': String(config.visualPolish.contentAwareBlur.enabled ?? false),
175
+ 'visual-polish-content-aware-depth-detection': String(config.visualPolish.contentAwareBlur.depthDetection ?? false),
176
+ 'visual-polish-content-aware-edge-preservation': String(config.visualPolish.contentAwareBlur.edgePreservation ?? false),
177
+ 'visual-polish-content-aware-variable-radius': String(config.visualPolish.contentAwareBlur.variableRadius ?? false),
178
+ };
179
+ }
180
+
181
+ if (config.visualPolish.holographicEffects) {
182
+ tokensToApply = {
183
+ ...tokensToApply,
184
+ 'visual-polish-holographic-enabled': String(config.visualPolish.holographicEffects.enabled ?? false),
185
+ 'visual-polish-holographic-rainbow-diffraction': String(config.visualPolish.holographicEffects.rainbowDiffraction ?? false),
186
+ 'visual-polish-holographic-scanline-animation': String(config.visualPolish.holographicEffects.scanlineAnimation ?? false),
187
+ 'visual-polish-holographic-grid-overlay': String(config.visualPolish.holographicEffects.gridOverlay ?? false),
188
+ 'visual-polish-holographic-data-stream': String(config.visualPolish.holographicEffects.dataStream ?? false),
189
+ 'visual-polish-holographic-pulse-rings': String(config.visualPolish.holographicEffects.pulseRings ?? false),
190
+ };
191
+ }
192
+ }
193
+
194
+ return createTokens(tokensToApply);
195
+ }
196
+
197
+ /**
198
+ * Load theme from config file (asynchronous)
199
+ * @param configPath - Path to config file (default: atomix.config.ts)
200
+ * @returns Promise resolving to DesignTokens from theme configuration
201
+ */
202
+ export async function loadThemeFromConfig(options?: { configPath?: string; required?: boolean }): Promise<DesignTokens> {
203
+ // In browser environments, config loading is not supported
204
+ if (typeof window !== 'undefined') {
205
+ throw new Error('loadThemeFromConfig: Not available in browser environment. Config loading requires Node.js/SSR environment.');
206
+ }
207
+
208
+ // In browser environments, config loading is not supported
209
+ if (typeof window !== 'undefined') {
210
+ throw new Error('loadThemeFromConfig: Not available in browser environment. Config loading requires Node.js/SSR environment.');
211
+ }
212
+
213
+ // Dynamically import the loader to avoid bundling issues in browser
214
+ let config: any;
215
+ try {
216
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
217
+ const { loadAtomixConfig } = require('../../config/loader');
218
+ config = loadAtomixConfig({ configPath: options?.configPath, required: options?.required !== false });
219
+ } catch (error) {
220
+ // If loadAtomixConfig is not available (e.g., in browser bundle), provide helpful error
221
+ if (error instanceof Error && error.message.includes('Cannot find module')) {
222
+ throw new Error('loadThemeFromConfig: Config loader not available. This function requires Node.js/SSR environment.');
223
+ }
224
+ throw error;
225
+ }
226
+
227
+ if (!config || !config.theme) {
228
+ throw new Error(`Config file ${options?.configPath || 'atomix.config.ts'} does not contain theme configuration.`);
229
+ }
230
+
231
+ // Extract tokens from config
232
+ const tokens = config.theme.tokens || config.theme.extend || {};
233
+
234
+ if (Object.keys(tokens).length === 0) {
235
+ throw new Error(`Config file ${options?.configPath || 'atomix.config.ts'} has empty theme configuration.`);
236
+ }
237
+
238
+ // Convert nested structure to flat tokens
239
+ return flattenConfigTokens(tokens, config.prefix || 'atomix');
240
+ }
241
+
242
+ // Helper function to convert nested config structure to flat tokens
243
+ function flattenConfigTokens(tokens: any, prefix: string): DesignTokens {
244
+ const flat: Partial<DesignTokens> = {};
245
+
246
+ // Flatten colors
247
+ if (tokens.colors) {
248
+ if (typeof tokens.colors === 'object') {
249
+ Object.entries(tokens.colors).forEach(([key, value]) => {
250
+ if (typeof value === 'string') {
251
+ flat[key as keyof DesignTokens] = String(value);
252
+ } else if (value && typeof value === 'object' && 'main' in value) {
253
+ // Handle palette color objects
254
+ const colorObj = value as any;
255
+ flat[key as keyof DesignTokens] = String(colorObj.main);
256
+
257
+ // Handle color scales
258
+ if (colorObj['1']) {
259
+ for (let i = 1; i <= 10; i++) {
260
+ if (colorObj[i]) {
261
+ flat[`${key}-${i}` as keyof DesignTokens] = String(colorObj[i]);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ });
267
+ }
268
+ }
269
+
270
+ // Flatten typography
271
+ if (tokens.typography) {
272
+ if (tokens.typography.fontFamilies) {
273
+ Object.entries(tokens.typography.fontFamilies).forEach(([key, value]) => {
274
+ if (value) flat[`font-${key}` as keyof DesignTokens] = String(value);
275
+ });
276
+ }
277
+
278
+ if (tokens.typography.fontSizes) {
279
+ Object.entries(tokens.typography.fontSizes).forEach(([key, value]) => {
280
+ if (value) flat[`font-size-${key}` as keyof DesignTokens] = String(value);
281
+ });
282
+ }
283
+
284
+ if (tokens.typography.fontWeights) {
285
+ Object.entries(tokens.typography.fontWeights).forEach(([key, value]) => {
286
+ if (value) flat[`font-weight-${key}` as keyof DesignTokens] = String(value);
287
+ });
288
+ }
289
+
290
+ if (tokens.typography.lineHeights) {
291
+ Object.entries(tokens.typography.lineHeights).forEach(([key, value]) => {
292
+ if (!value) return;
293
+ if (key === 'base' || key === 'default') {
294
+ flat['line-height-base' as keyof DesignTokens] = String(value);
295
+ } else {
296
+ flat[`line-height-${key}` as keyof DesignTokens] = String(value);
297
+ }
298
+ });
299
+ }
300
+ }
301
+
302
+ // Flatten spacing
303
+ if (tokens.spacing) {
304
+ Object.entries(tokens.spacing).forEach(([key, value]) => {
305
+ if (value) flat[`spacing-${key}` as keyof DesignTokens] = String(value);
306
+ });
307
+ }
308
+
309
+ // Flatten borderRadius
310
+ if (tokens.borderRadius) {
311
+ Object.entries(tokens.borderRadius).forEach(([key, value]) => {
312
+ if (value) flat[`border-radius-${key}` as keyof DesignTokens] = String(value);
313
+ });
314
+ }
315
+
316
+ // Flatten shadows
317
+ if (tokens.shadows) {
318
+ Object.entries(tokens.shadows).forEach(([key, value]) => {
319
+ if (value) flat[`shadow-${key}` as keyof DesignTokens] = String(value);
320
+ });
321
+ }
322
+
323
+ // Flatten zIndex
324
+ if (tokens.zIndex) {
325
+ Object.entries(tokens.zIndex).forEach(([key, value]) => {
326
+ if (value) flat[`z-index-${key}` as keyof DesignTokens] = String(value);
327
+ });
328
+ }
329
+
330
+ // Flatten transitions
331
+ if (tokens.transitions) {
332
+ if (tokens.transitions.durations) {
333
+ Object.entries(tokens.transitions.durations).forEach(([key, value]) => {
334
+ if (value) flat[`transition-${key}` as keyof DesignTokens] = String(value);
335
+ });
336
+ }
337
+ }
338
+
339
+ return createTokens(flat);
340
+ }
341
+
342
+ // Helper type guard function
343
+ function isThemeObject(obj: any): obj is Theme {
344
+ return obj && typeof obj === 'object' && (
345
+ obj.palette ||
346
+ obj.typography ||
347
+ obj.spacing ||
348
+ obj.breakpoints ||
349
+ obj.colors
350
+ );
351
+ }