@shohojdhara/atomix 0.3.2 → 0.3.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/README.md +58 -21
- package/dist/atomix.css +96 -121
- package/dist/atomix.min.css +3 -3
- package/dist/index.d.ts +7937 -7765
- package/dist/index.esm.js +3677 -4031
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3648 -3952
- 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 +44 -16
- package/scripts/atomix-cli.js +1764 -0
- package/scripts/build-themes.js +208 -0
- package/scripts/cli/interactive-init.js +520 -0
- package/scripts/cli/migration-tools.js +603 -0
- package/scripts/cli/theme-bridge.js +129 -0
- package/scripts/cli/token-manager.js +519 -0
- package/scripts/sync-theme-config.js +309 -0
- package/src/components/Button/Button.tsx +36 -1
- package/src/components/List/ListGroup.tsx +1 -2
- package/src/components/Popover/Popover.tsx +2 -2
- package/src/components/Tooltip/Tooltip.stories.tsx +49 -12
- package/src/components/Tooltip/Tooltip.tsx +32 -58
- package/src/lib/composables/useTooltip.ts +285 -0
- package/src/lib/config/index.ts +275 -0
- package/src/lib/config/loader.ts +105 -0
- package/src/lib/constants/cssVariables.ts +390 -0
- package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +151 -0
- package/src/lib/hooks/index.ts +19 -0
- package/src/lib/hooks/useComponentCustomization.ts +175 -0
- package/src/lib/index.ts +14 -1
- package/src/lib/patterns/__tests__/slots.test.ts +108 -0
- package/src/lib/patterns/index.ts +35 -0
- package/src/lib/patterns/slots.tsx +421 -0
- package/src/lib/theme/composeTheme.ts +0 -5
- package/src/lib/theme/config/index.ts +1 -1
- package/src/lib/theme/config/loader.ts +75 -41
- package/src/lib/theme/config/types.ts +21 -7
- package/src/lib/theme/config/validator.ts +1 -1
- package/src/lib/theme/constants.ts +12 -2
- package/src/lib/theme/createTheme.ts +2 -135
- package/src/lib/theme/createThemeFromConfig.ts +132 -0
- package/src/lib/theme/cssVariableMapper.ts +261 -0
- package/src/lib/theme/devtools/CLI.ts +161 -76
- package/src/lib/theme/devtools/Comparator.tsx +343 -0
- package/src/lib/theme/devtools/IMPROVEMENTS.md +429 -0
- package/src/lib/theme/devtools/Inspector.tsx +21 -6
- package/src/lib/theme/devtools/LiveEditor.tsx +393 -0
- package/src/lib/theme/devtools/README.md +433 -0
- package/src/lib/theme/devtools/index.ts +12 -11
- package/src/lib/theme/generateCSSVariables.ts +79 -38
- package/src/lib/theme/index.ts +45 -246
- package/src/lib/theme/runtime/ThemeApplicator.ts +252 -0
- package/src/lib/theme/runtime/ThemeManager.test.ts +17 -1
- package/src/lib/theme/runtime/ThemeManager.ts +7 -7
- package/src/lib/theme/themeUtils.ts +27 -5
- package/src/lib/theme/types.ts +59 -1
- package/src/lib/theme-tools.ts +125 -0
- package/src/lib/types/components.ts +260 -72
- package/src/lib/types/partProps.ts +426 -0
- package/src/lib/utils/__tests__/componentUtils.test.ts +144 -0
- package/src/lib/utils/componentUtils.ts +163 -0
- package/src/lib/utils/index.ts +17 -57
- package/src/styles/01-settings/_settings.colors.scss +10 -10
- package/src/styles/01-settings/_settings.navbar.scss +1 -1
- package/src/styles/01-settings/_settings.tooltip.scss +1 -1
- package/src/styles/03-generic/_generated-root.css +5 -0
- package/src/styles/06-components/_components.navbar.scss +12 -5
- package/src/styles/06-components/_components.tooltip.scss +31 -81
- package/src/themes/README.md +442 -0
- package/src/themes/themes.config.js +35 -0
- package/src/lib/theme/errors.test.ts +0 -207
- package/src/lib/theme/generators/CSSGenerator.ts +0 -311
- package/src/lib/theme/generators/ConfigGenerator.ts +0 -287
- package/src/lib/theme/generators/TypeGenerator.ts +0 -228
- package/src/lib/theme/generators/index.ts +0 -21
- package/src/lib/theme/monitoring/ThemeAnalytics.ts +0 -409
- package/src/lib/theme/monitoring/index.ts +0 -17
- package/src/lib/theme/overrides/ComponentOverrides.ts +0 -243
- package/src/lib/theme/overrides/index.ts +0 -15
- package/src/lib/theme/studio/ThemeStudio.tsx +0 -312
- package/src/lib/theme/studio/index.ts +0 -8
- package/src/lib/theme/whitelabel/WhiteLabelManager.ts +0 -364
- package/src/lib/theme/whitelabel/index.ts +0 -13
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Applicator
|
|
3
|
+
*
|
|
4
|
+
* Applies theme configurations to the DOM, including CSS variables,
|
|
5
|
+
* component overrides, typography, spacing, and color palettes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { applyCSSVariables, removeCSSVariables } from '../cssVariableMapper';
|
|
9
|
+
import type { Theme, ThemeComponentOverrides, ComponentThemeOverride } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Theme applicator class for runtime theme application
|
|
13
|
+
*/
|
|
14
|
+
export class ThemeApplicator {
|
|
15
|
+
private appliedVars: Set<string> = new Set();
|
|
16
|
+
private root: HTMLElement;
|
|
17
|
+
|
|
18
|
+
constructor(root: HTMLElement = document.documentElement) {
|
|
19
|
+
this.root = root;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply a complete theme configuration
|
|
24
|
+
*/
|
|
25
|
+
applyTheme(theme: Theme): void {
|
|
26
|
+
// Clear previously applied variables
|
|
27
|
+
this.clearAppliedVars();
|
|
28
|
+
|
|
29
|
+
// Apply global CSS variables
|
|
30
|
+
if (theme.cssVars) {
|
|
31
|
+
this.applyGlobalCSSVars(theme.cssVars);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Apply typography system
|
|
35
|
+
if (theme.typography) {
|
|
36
|
+
this.applyTypography(theme.typography);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Apply spacing system
|
|
40
|
+
if (theme.spacing) {
|
|
41
|
+
this.applySpacing(theme.spacing);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Apply color palette
|
|
45
|
+
if (theme.palette) {
|
|
46
|
+
this.applyPalette(theme.palette);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Apply component overrides
|
|
50
|
+
if (theme.components) {
|
|
51
|
+
this.applyComponentOverrides(theme.components);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Apply global CSS variables
|
|
57
|
+
*/
|
|
58
|
+
private applyGlobalCSSVars(vars: Record<string, string | number>): void {
|
|
59
|
+
Object.entries(vars).forEach(([key, value]) => {
|
|
60
|
+
this.root.style.setProperty(key, String(value));
|
|
61
|
+
this.appliedVars.add(key);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Apply typography system
|
|
67
|
+
*/
|
|
68
|
+
private applyTypography(typography: Theme['typography']): void {
|
|
69
|
+
if (!typography) return;
|
|
70
|
+
|
|
71
|
+
const vars: Record<string, string | number> = {};
|
|
72
|
+
|
|
73
|
+
if (typography.fontFamily) {
|
|
74
|
+
vars['--atomix-font-family'] = typography.fontFamily;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typography.fontSize) {
|
|
78
|
+
Object.entries(typography.fontSize).forEach(([key, value]) => {
|
|
79
|
+
vars[`--atomix-font-size-${key}`] = value;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typography.fontWeight) {
|
|
84
|
+
Object.entries(typography.fontWeight).forEach(([key, value]) => {
|
|
85
|
+
vars[`--atomix-font-weight-${key}`] = value;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typography.lineHeight) {
|
|
90
|
+
Object.entries(typography.lineHeight).forEach(([key, value]) => {
|
|
91
|
+
vars[`--atomix-line-height-${key}`] = value;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.applyGlobalCSSVars(vars);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply spacing system
|
|
100
|
+
*/
|
|
101
|
+
private applySpacing(spacing: Record<string, string | number>): void {
|
|
102
|
+
const vars: Record<string, string | number> = {};
|
|
103
|
+
|
|
104
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
105
|
+
vars[`--atomix-space-${key}`] = value;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.applyGlobalCSSVars(vars);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Apply color palette
|
|
113
|
+
*/
|
|
114
|
+
private applyPalette(palette: Theme['palette']): void {
|
|
115
|
+
if (!palette) return;
|
|
116
|
+
|
|
117
|
+
const vars: Record<string, string | number> = {};
|
|
118
|
+
|
|
119
|
+
Object.entries(palette).forEach(([colorName, colorScale]) => {
|
|
120
|
+
if (colorScale) {
|
|
121
|
+
Object.entries(colorScale).forEach(([shade, value]) => {
|
|
122
|
+
if (value) {
|
|
123
|
+
vars[`--atomix-color-${colorName}-${shade}`] = value;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.applyGlobalCSSVars(vars);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Apply component-level overrides
|
|
134
|
+
*/
|
|
135
|
+
private applyComponentOverrides(overrides: ThemeComponentOverrides): void {
|
|
136
|
+
Object.entries(overrides).forEach(([componentName, override]) => {
|
|
137
|
+
if (override) {
|
|
138
|
+
this.applyComponentOverride(componentName, override);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply override for a specific component
|
|
145
|
+
*/
|
|
146
|
+
private applyComponentOverride(
|
|
147
|
+
componentName: string,
|
|
148
|
+
override: ComponentThemeOverride
|
|
149
|
+
): void {
|
|
150
|
+
const vars: Record<string, string | number> = {};
|
|
151
|
+
const componentKey = componentName.toLowerCase();
|
|
152
|
+
|
|
153
|
+
// Apply component-level CSS variables
|
|
154
|
+
if (override.cssVars) {
|
|
155
|
+
Object.entries(override.cssVars).forEach(([key, value]) => {
|
|
156
|
+
// If key doesn't start with --, add component prefix
|
|
157
|
+
const varKey = key.startsWith('--')
|
|
158
|
+
? key
|
|
159
|
+
: `--atomix-${componentKey}-${key}`;
|
|
160
|
+
vars[varKey] = value;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Apply part-specific CSS variables
|
|
165
|
+
if (override.parts) {
|
|
166
|
+
Object.entries(override.parts).forEach(([partName, partOverride]) => {
|
|
167
|
+
if (partOverride.cssVars) {
|
|
168
|
+
Object.entries(partOverride.cssVars).forEach(([key, value]) => {
|
|
169
|
+
const varKey = key.startsWith('--')
|
|
170
|
+
? key
|
|
171
|
+
: `--atomix-${componentKey}-${partName}-${key}`;
|
|
172
|
+
vars[varKey] = value;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply variant-specific CSS variables
|
|
179
|
+
if (override.variants) {
|
|
180
|
+
Object.entries(override.variants).forEach(([variantName, variantOverride]) => {
|
|
181
|
+
if (variantOverride.cssVars) {
|
|
182
|
+
Object.entries(variantOverride.cssVars).forEach(([key, value]) => {
|
|
183
|
+
const varKey = key.startsWith('--')
|
|
184
|
+
? key
|
|
185
|
+
: `--atomix-${componentKey}-${variantName}-${key}`;
|
|
186
|
+
vars[varKey] = value;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.applyGlobalCSSVars(vars);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Clear all applied CSS variables
|
|
197
|
+
*/
|
|
198
|
+
private clearAppliedVars(): void {
|
|
199
|
+
removeCSSVariables(Array.from(this.appliedVars), this.root);
|
|
200
|
+
this.appliedVars.clear();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get all currently applied variables
|
|
205
|
+
*/
|
|
206
|
+
getAppliedVars(): string[] {
|
|
207
|
+
return Array.from(this.appliedVars);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Remove theme application
|
|
212
|
+
*/
|
|
213
|
+
removeTheme(): void {
|
|
214
|
+
this.clearAppliedVars();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Update specific CSS variables without clearing all
|
|
219
|
+
*/
|
|
220
|
+
updateCSSVars(vars: Record<string, string | number>): void {
|
|
221
|
+
this.applyGlobalCSSVars(vars);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Global theme applicator instance
|
|
227
|
+
*/
|
|
228
|
+
let globalApplicator: ThemeApplicator | null = null;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get or create global theme applicator
|
|
232
|
+
*/
|
|
233
|
+
export function getThemeApplicator(): ThemeApplicator {
|
|
234
|
+
if (!globalApplicator) {
|
|
235
|
+
globalApplicator = new ThemeApplicator();
|
|
236
|
+
}
|
|
237
|
+
return globalApplicator;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Apply theme using global applicator
|
|
242
|
+
*/
|
|
243
|
+
export function applyTheme(theme: Theme): void {
|
|
244
|
+
getThemeApplicator().applyTheme(theme);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Remove theme using global applicator
|
|
249
|
+
*/
|
|
250
|
+
export function removeTheme(): void {
|
|
251
|
+
getThemeApplicator().removeTheme();
|
|
252
|
+
}
|
|
@@ -10,7 +10,23 @@ import { ThemeError, ThemeErrorCode } from '../errors';
|
|
|
10
10
|
import type { ThemeMetadata } from '../types';
|
|
11
11
|
|
|
12
12
|
// Mock dependencies
|
|
13
|
-
vi.mock('../core/ThemeEngine')
|
|
13
|
+
vi.mock('../core/ThemeEngine', () => {
|
|
14
|
+
return {
|
|
15
|
+
ThemeEngine: class {
|
|
16
|
+
initialize = vi.fn().mockResolvedValue(undefined);
|
|
17
|
+
on = vi.fn();
|
|
18
|
+
getRegistry = vi.fn().mockReturnValue({
|
|
19
|
+
has: vi.fn().mockReturnValue(false),
|
|
20
|
+
register: vi.fn(),
|
|
21
|
+
getAllMetadata: vi.fn().mockReturnValue([]),
|
|
22
|
+
});
|
|
23
|
+
setTheme = vi.fn().mockResolvedValue(undefined);
|
|
24
|
+
getActiveTheme = vi.fn().mockReturnValue(null);
|
|
25
|
+
isThemeLoaded = vi.fn().mockReturnValue(false);
|
|
26
|
+
preloadTheme = vi.fn().mockResolvedValue(undefined);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
});
|
|
14
30
|
vi.mock('../config/loader');
|
|
15
31
|
vi.mock('../utils', () => ({
|
|
16
32
|
isBrowser: () => true,
|
|
@@ -63,7 +63,7 @@ const DEFAULT_CONFIG: Partial<ThemeManagerConfig> = {
|
|
|
63
63
|
* // No defaultTheme - uses built-in styles
|
|
64
64
|
* });
|
|
65
65
|
*
|
|
66
|
-
* await themeManager.setTheme('
|
|
66
|
+
* await themeManager.setTheme('my-custom-theme');
|
|
67
67
|
* ```
|
|
68
68
|
*/
|
|
69
69
|
export class ThemeManager {
|
|
@@ -356,13 +356,13 @@ export class ThemeManager {
|
|
|
356
356
|
* Emit theme error event
|
|
357
357
|
*/
|
|
358
358
|
private emitThemeError(error: Error, themeName: string): void {
|
|
359
|
-
const themeError = error instanceof ThemeError
|
|
360
|
-
? error
|
|
359
|
+
const themeError = error instanceof ThemeError
|
|
360
|
+
? error
|
|
361
361
|
: new ThemeError(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
error.message,
|
|
363
|
+
ThemeErrorCode.THEME_LOAD_FAILED,
|
|
364
|
+
{ themeName, originalError: error.message }
|
|
365
|
+
);
|
|
366
366
|
|
|
367
367
|
if (this.config.onError) {
|
|
368
368
|
try {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* spacing helpers, and theme value accessors.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { Theme, SpacingFunction } from './types';
|
|
8
|
+
import type { Theme, SpacingFunction, SpacingOptions } from './types';
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Color Manipulation Utilities
|
|
@@ -150,15 +150,37 @@ export function emphasize(color: string, coefficient: number = 0.15): string {
|
|
|
150
150
|
// ============================================================================
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
* Create a spacing function
|
|
153
|
+
* Create a spacing function from various input types
|
|
154
154
|
*
|
|
155
|
-
* @param
|
|
155
|
+
* @param spacingInput - Spacing configuration (number, array, or function), default 4
|
|
156
156
|
* @returns Spacing function
|
|
157
157
|
*/
|
|
158
|
-
export function createSpacing(
|
|
158
|
+
export function createSpacing(spacingInput: SpacingOptions = 4): SpacingFunction {
|
|
159
|
+
// If it's already a function, return it
|
|
160
|
+
if (typeof spacingInput === 'function') {
|
|
161
|
+
return spacingInput;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If it's a number, create a function that multiplies by that number
|
|
165
|
+
if (typeof spacingInput === 'number') {
|
|
166
|
+
return (...values: number[]) => {
|
|
167
|
+
if (values.length === 0) return '0px';
|
|
168
|
+
return values.map((value) => `${value * spacingInput}px`).join(' ');
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// If it's an array, use it as a scale
|
|
173
|
+
if (Array.isArray(spacingInput)) {
|
|
174
|
+
return (...values: number[]) => {
|
|
175
|
+
if (values.length === 0) return '0px';
|
|
176
|
+
return values.map((value) => `${spacingInput[value] || value}px`).join(' ');
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Default to 4px base
|
|
159
181
|
return (...values: number[]) => {
|
|
160
182
|
if (values.length === 0) return '0px';
|
|
161
|
-
return values.map((value) => `${value *
|
|
183
|
+
return values.map((value) => `${value * 4}px`).join(' ');
|
|
162
184
|
};
|
|
163
185
|
}
|
|
164
186
|
|
package/src/lib/theme/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ThemeManager as ThemeManagerType } from './runtime/ThemeManager';
|
|
8
|
+
import type { PartStyleProps } from '../types/partProps';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Theme metadata interface matching themes.config.js structure
|
|
@@ -184,6 +185,62 @@ export interface UseThemeReturn {
|
|
|
184
185
|
preloadTheme: (themeName: string) => Promise<void>;
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Component-level theme override configuration
|
|
190
|
+
*/
|
|
191
|
+
export interface ComponentThemeOverride {
|
|
192
|
+
/** CSS variable overrides for the component */
|
|
193
|
+
cssVars?: Record<string, string | number>;
|
|
194
|
+
|
|
195
|
+
/** Default prop overrides */
|
|
196
|
+
defaultProps?: Record<string, any>;
|
|
197
|
+
|
|
198
|
+
/** Part-specific overrides */
|
|
199
|
+
parts?: Record<string, {
|
|
200
|
+
cssVars?: Record<string, string | number>;
|
|
201
|
+
className?: string;
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
/** Variant overrides */
|
|
205
|
+
variants?: Record<string, {
|
|
206
|
+
cssVars?: Record<string, string | number>;
|
|
207
|
+
className?: string;
|
|
208
|
+
}>;
|
|
209
|
+
|
|
210
|
+
/** Additional className for the component */
|
|
211
|
+
className?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Theme component overrides for all components
|
|
216
|
+
*/
|
|
217
|
+
export interface ThemeComponentOverrides {
|
|
218
|
+
Button?: ComponentThemeOverride;
|
|
219
|
+
Card?: ComponentThemeOverride;
|
|
220
|
+
Input?: ComponentThemeOverride;
|
|
221
|
+
Modal?: ComponentThemeOverride;
|
|
222
|
+
Dropdown?: ComponentThemeOverride;
|
|
223
|
+
Badge?: ComponentThemeOverride;
|
|
224
|
+
Tabs?: ComponentThemeOverride;
|
|
225
|
+
Progress?: ComponentThemeOverride;
|
|
226
|
+
Tooltip?: ComponentThemeOverride;
|
|
227
|
+
Select?: ComponentThemeOverride;
|
|
228
|
+
Checkbox?: ComponentThemeOverride;
|
|
229
|
+
Radio?: ComponentThemeOverride;
|
|
230
|
+
Textarea?: ComponentThemeOverride;
|
|
231
|
+
FormGroup?: ComponentThemeOverride;
|
|
232
|
+
Navbar?: ComponentThemeOverride;
|
|
233
|
+
Accordion?: ComponentThemeOverride;
|
|
234
|
+
DataTable?: ComponentThemeOverride;
|
|
235
|
+
Avatar?: ComponentThemeOverride;
|
|
236
|
+
List?: ComponentThemeOverride;
|
|
237
|
+
Popover?: ComponentThemeOverride;
|
|
238
|
+
Messages?: ComponentThemeOverride;
|
|
239
|
+
Callout?: ComponentThemeOverride;
|
|
240
|
+
Spinner?: ComponentThemeOverride;
|
|
241
|
+
[key: string]: ComponentThemeOverride | undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
187
244
|
/**
|
|
188
245
|
* Theme provider props
|
|
189
246
|
*/
|
|
@@ -277,7 +334,6 @@ export interface PaletteOptions {
|
|
|
277
334
|
/** Background colors */
|
|
278
335
|
background?: {
|
|
279
336
|
default?: string;
|
|
280
|
-
paper?: string;
|
|
281
337
|
subtle?: string;
|
|
282
338
|
};
|
|
283
339
|
/** Text colors */
|
|
@@ -559,6 +615,8 @@ export interface Theme extends ThemeMetadata {
|
|
|
559
615
|
borderRadius: Required<BorderRadiusOptions>;
|
|
560
616
|
/** Custom properties */
|
|
561
617
|
custom: ThemeCustomProperties;
|
|
618
|
+
/** Global CSS variables to apply */
|
|
619
|
+
cssVars?: Record<string, string | number>;
|
|
562
620
|
/** Indicates this is a JS theme (not CSS-only) */
|
|
563
621
|
__isJSTheme: true;
|
|
564
622
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Tools for Library Users
|
|
3
|
+
*
|
|
4
|
+
* Developer-friendly utilities for working with Atomix themes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Theme, ThemeMetadata } from './theme/types';
|
|
8
|
+
import { createTheme } from './theme/createTheme';
|
|
9
|
+
import { extendTheme, mergeTheme } from './theme/composeTheme';
|
|
10
|
+
import { generateCSSVariables } from './theme/generateCSSVariables';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Quick theme creator with sensible defaults
|
|
14
|
+
*/
|
|
15
|
+
export function quickTheme(name: string, primaryColor: string, secondaryColor?: string): Theme {
|
|
16
|
+
return createTheme({
|
|
17
|
+
name,
|
|
18
|
+
palette: {
|
|
19
|
+
primary: { main: primaryColor },
|
|
20
|
+
secondary: secondaryColor ? { main: secondaryColor } : undefined,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a dark theme variant from a light theme
|
|
27
|
+
*/
|
|
28
|
+
export function createDarkVariant(lightTheme: Theme): Theme {
|
|
29
|
+
return extendTheme(lightTheme, {
|
|
30
|
+
name: `${lightTheme.name} Dark`,
|
|
31
|
+
palette: {
|
|
32
|
+
mode: 'dark',
|
|
33
|
+
background: {
|
|
34
|
+
default: '#121212',
|
|
35
|
+
paper: '#1e1e1e',
|
|
36
|
+
},
|
|
37
|
+
text: {
|
|
38
|
+
primary: '#ffffff',
|
|
39
|
+
secondary: 'rgba(255, 255, 255, 0.7)',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate theme structure
|
|
47
|
+
*/
|
|
48
|
+
export function validateTheme(theme: Theme): { valid: boolean; errors: string[] } {
|
|
49
|
+
const errors: string[] = [];
|
|
50
|
+
|
|
51
|
+
if (!theme.name) {
|
|
52
|
+
errors.push('Theme must have a name');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!theme.palette) {
|
|
56
|
+
errors.push('Theme must have a palette');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (theme.palette && !theme.palette.primary) {
|
|
60
|
+
errors.push('Theme palette must have a primary color');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
valid: errors.length === 0,
|
|
65
|
+
errors,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate CSS string from theme
|
|
71
|
+
*/
|
|
72
|
+
export function themeToCSS(theme: Theme, selector = ':root'): string {
|
|
73
|
+
return generateCSSVariables(theme, {
|
|
74
|
+
selector,
|
|
75
|
+
prefix: 'atomix',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get theme metadata
|
|
81
|
+
*/
|
|
82
|
+
export function getThemeMetadata(theme: Theme): ThemeMetadata {
|
|
83
|
+
return {
|
|
84
|
+
name: theme.name || 'Custom Theme',
|
|
85
|
+
description: theme.description,
|
|
86
|
+
author: theme.author,
|
|
87
|
+
version: theme.version || '1.0.0',
|
|
88
|
+
tags: theme.tags || [],
|
|
89
|
+
supportsDarkMode: theme.palette?.mode === 'dark',
|
|
90
|
+
status: 'stable',
|
|
91
|
+
color: theme.palette?.primary?.main || '#7AFFD7',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if theme supports dark mode
|
|
97
|
+
*/
|
|
98
|
+
export function supportsDarkMode(theme: Theme): boolean {
|
|
99
|
+
return theme.palette?.mode === 'dark' ||
|
|
100
|
+
theme.supportsDarkMode === true ||
|
|
101
|
+
Boolean(theme.a11y?.modes?.includes('dark'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Export theme as JSON
|
|
106
|
+
*/
|
|
107
|
+
export function exportTheme(theme: Theme): string {
|
|
108
|
+
return JSON.stringify(theme, null, 2);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Import theme from JSON
|
|
113
|
+
*/
|
|
114
|
+
export function importTheme(json: string): Theme {
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(json) as Theme;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error('Invalid theme JSON');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Re-export commonly used functions
|
|
123
|
+
export { createTheme, extendTheme, mergeTheme };
|
|
124
|
+
export { generateCSSVariables };
|
|
125
|
+
export { RTLManager } from './theme/i18n/rtl';
|