@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.
- package/atomix.config.ts +33 -33
- package/dist/config.d.ts +187 -112
- package/dist/config.js +7 -49
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1958 -900
- package/dist/index.esm.js +2275 -383
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2327 -417
- 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 +1390 -276
- package/dist/theme.js +2129 -621
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli/internal/config-loader.js +30 -20
- package/src/lib/config/index.ts +38 -362
- package/src/lib/config/loader.ts +419 -0
- package/src/lib/config/public-api.ts +43 -0
- package/src/lib/config/types.ts +389 -0
- package/src/lib/config/validator.ts +305 -0
- package/src/lib/theme/adapters/index.ts +1 -1
- package/src/lib/theme/adapters/themeAdapter.ts +358 -229
- package/src/lib/theme/components/ThemeToggle.tsx +276 -0
- package/src/lib/theme/config/configLoader.ts +351 -0
- package/src/lib/theme/config/loader.ts +221 -0
- package/src/lib/theme/core/createTheme.ts +126 -50
- package/src/lib/theme/core/createThemeObject.ts +7 -4
- package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
- package/src/lib/theme/index.ts +322 -38
- package/src/lib/theme/runtime/ThemeProvider.tsx +44 -10
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
- package/src/lib/theme/runtime/useTheme.ts +1 -0
- package/src/lib/theme/tokens/tokens.ts +101 -1
- package/src/lib/theme/types.ts +91 -0
- package/src/lib/theme/utils/performanceMonitor.ts +315 -0
- package/src/lib/theme/utils/responsive.ts +280 -0
- package/src/lib/theme/utils/themeUtils.ts +531 -117
- package/src/styles/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
|
+
}
|