@shohojdhara/atomix 0.3.5 → 0.3.7
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 +101 -199
- package/atomix.config.ts +241 -0
- package/dist/atomix.css +260 -179
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +250 -179
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js +69 -166
- package/dist/charts.js.map +1 -1
- package/dist/core.js +184 -263
- package/dist/core.js.map +1 -1
- package/dist/forms.js +55 -131
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +184 -263
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +1831 -1657
- package/dist/index.esm.js +4497 -4318
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +4510 -4328
- 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 +1431 -1472
- package/dist/theme.js +4175 -4138
- package/dist/theme.js.map +1 -1
- package/package.json +6 -20
- package/src/components/Accordion/Accordion.stories.tsx +50 -17
- package/src/components/AtomixGlass/AtomixGlass.tsx +128 -322
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +12 -5
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
- package/src/components/Avatar/Avatar.stories.tsx +7 -0
- package/src/components/Badge/Badge.stories.tsx +91 -13
- package/src/components/Block/Block.stories.tsx +7 -23
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
- package/src/components/Button/Button.stories.tsx +141 -22
- package/src/components/Button/Button.tsx +85 -167
- package/src/components/Button/ButtonGroup.stories.tsx +315 -0
- package/src/components/Button/ButtonGroup.tsx +67 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Callout/Callout.stories.tsx +8 -6
- package/src/components/Card/Card.stories.tsx +82 -28
- package/src/components/Chart/AnimatedChart.tsx +0 -1
- package/src/components/Chart/AreaChart.tsx +0 -1
- package/src/components/Chart/BarChart.tsx +0 -1
- package/src/components/Chart/BubbleChart.tsx +0 -1
- package/src/components/Chart/CandlestickChart.tsx +0 -1
- package/src/components/Chart/Chart.stories.tsx +5 -7
- package/src/components/Chart/Chart.tsx +0 -16
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +0 -1
- package/src/components/Chart/FunnelChart.tsx +0 -1
- package/src/components/Chart/GaugeChart.tsx +0 -1
- package/src/components/Chart/HeatmapChart.tsx +0 -1
- package/src/components/Chart/LineChart.tsx +0 -1
- package/src/components/Chart/MultiAxisChart.tsx +0 -1
- package/src/components/Chart/PieChart.tsx +0 -1
- package/src/components/Chart/RadarChart.tsx +0 -1
- package/src/components/Chart/ScatterChart.tsx +0 -1
- package/src/components/Chart/WaterfallChart.tsx +0 -1
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
- package/src/components/DataTable/DataTable.stories.tsx +23 -16
- package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
- package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
- package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
- package/src/components/Footer/Footer.stories.tsx +8 -6
- package/src/components/Footer/FooterLink.tsx +9 -2
- package/src/components/Form/Checkbox.stories.tsx +7 -0
- package/src/components/Form/Form.stories.tsx +7 -0
- package/src/components/Form/FormGroup.stories.tsx +9 -1
- package/src/components/Form/Input.stories.tsx +69 -16
- package/src/components/Form/Radio.stories.tsx +9 -1
- package/src/components/Form/Select.stories.tsx +9 -1
- package/src/components/Form/Textarea.stories.tsx +10 -2
- package/src/components/Hero/Hero.stories.tsx +7 -0
- package/src/components/List/List.stories.tsx +7 -0
- package/src/components/Messages/Messages.stories.tsx +8 -7
- package/src/components/Modal/Modal.stories.tsx +17 -6
- package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +188 -111
- package/src/components/Pagination/Pagination.tsx +83 -3
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
- package/src/components/Popover/Popover.stories.tsx +191 -115
- package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
- package/src/components/Progress/Progress.stories.tsx +79 -49
- package/src/components/Rating/Rating.stories.tsx +109 -84
- package/src/components/River/River.stories.tsx +194 -114
- package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
- package/src/components/Slider/Slider.stories.tsx +7 -0
- package/src/components/Spinner/Spinner.stories.tsx +15 -11
- package/src/components/Steps/Steps.stories.tsx +132 -98
- package/src/components/Tabs/Tabs.stories.tsx +163 -112
- package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
- package/src/components/Todo/Todo.stories.tsx +38 -12
- package/src/components/Toggle/Toggle.stories.tsx +61 -28
- package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
- package/src/components/Upload/Upload.stories.tsx +122 -84
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
- package/src/components/index.ts +1 -0
- package/src/lib/composables/useAtomixGlass.ts +9 -10
- package/src/lib/composables/useNavbar.ts +0 -10
- package/src/lib/config/loader.ts +4 -4
- package/src/lib/constants/components.ts +17 -0
- package/src/lib/hooks/useComponentCustomization.ts +1 -1
- package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
- package/src/lib/hooks/useThemeTokens.ts +105 -0
- package/src/lib/theme/README.md +174 -0
- package/src/lib/theme/adapters/index.ts +31 -0
- package/src/lib/theme/adapters/themeAdapter.ts +287 -0
- package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
- package/src/lib/theme/config/configLoader.ts +95 -0
- package/src/lib/theme/config/loader.ts +37 -54
- package/src/lib/theme/config/types.ts +2 -2
- package/src/lib/theme/config/validator.ts +15 -91
- package/src/lib/theme/{constants.ts → constants/constants.ts} +1 -19
- package/src/lib/theme/constants/index.ts +8 -0
- package/src/lib/theme/core/ThemeRegistry.ts +75 -266
- package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
- package/src/lib/theme/core/composeTheme.ts +105 -0
- package/src/lib/theme/core/createTheme.ts +108 -0
- package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +12 -8
- package/src/lib/theme/core/index.ts +19 -19
- package/src/lib/theme/devtools/Comparator.tsx +346 -22
- package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
- package/src/lib/theme/devtools/Inspector.tsx +335 -51
- package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
- package/src/lib/theme/devtools/Preview.tsx +471 -221
- package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
- package/src/lib/theme/devtools/index.ts +14 -4
- package/src/lib/theme/devtools/useHistory.ts +130 -0
- package/src/lib/theme/{errors.ts → errors/errors.ts} +1 -1
- package/src/lib/theme/errors/index.ts +12 -0
- package/src/lib/theme/generators/cssFile.ts +79 -0
- package/src/lib/theme/generators/generateCSS.ts +89 -0
- package/src/lib/theme/generators/generateCSSNested.ts +130 -0
- package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
- package/src/lib/theme/generators/index.ts +25 -0
- package/src/lib/theme/i18n/rtl.ts +5 -6
- package/src/lib/theme/index.ts +149 -19
- package/src/lib/theme/runtime/ThemeApplicator.ts +53 -112
- package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +5 -5
- package/src/lib/theme/runtime/ThemeProvider.tsx +266 -282
- package/src/lib/theme/runtime/index.ts +2 -2
- package/src/lib/theme/runtime/useTheme.ts +1 -2
- package/src/lib/theme/runtime/useThemeTokens.ts +131 -0
- package/src/lib/theme/test/testTheme.ts +385 -0
- package/src/lib/theme/tokens/index.ts +12 -0
- package/src/lib/theme/tokens/tokens.ts +721 -0
- package/src/lib/theme/types.ts +6 -42
- package/src/lib/theme/utils/componentTheming.ts +132 -0
- package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
- package/src/lib/theme/utils/index.ts +11 -0
- package/src/lib/theme/utils/injectCSS.ts +90 -0
- package/src/lib/theme/utils/naming.ts +100 -0
- package/src/lib/theme/utils/themeHelpers.ts +78 -0
- package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +7 -7
- package/src/lib/theme-tools.ts +7 -8
- package/src/lib/types/components.ts +40 -130
- package/src/lib/utils/componentUtils.ts +2 -2
- package/src/lib/utils/memoryMonitor.ts +3 -3
- package/src/lib/utils/themeNaming.ts +135 -0
- package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
- package/src/styles/02-tools/_tools.button.scss +66 -79
- package/src/styles/06-components/_components.atomix-glass.scss +13 -3
- package/src/styles/06-components/_components.pagination.scss +88 -0
- package/scripts/sync-theme-config.js +0 -309
- package/src/lib/theme/composeTheme.ts +0 -370
- package/src/lib/theme/core/ThemeCache.ts +0 -283
- package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
- package/src/lib/theme/core/ThemeEngine.ts +0 -665
- package/src/lib/theme/createThemeFromConfig.ts +0 -132
- package/src/lib/theme/devtools/CLI.ts +0 -364
- package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
- package/src/lib/theme/runtime/ThemeManager.ts +0 -446
- package/src/styles/03-generic/_generated-root.css +0 -26
- package/src/themes/README.md +0 -442
- package/src/themes/themes.config.js +0 -68
- /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
|
@@ -1,284 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Theme
|
|
3
|
-
*
|
|
4
|
-
* Central registry for all themes with discovery and dependency management
|
|
2
|
+
* Theme Metadata interface
|
|
5
3
|
*/
|
|
4
|
+
export interface ThemeMetadata {
|
|
5
|
+
name: string;
|
|
6
|
+
class: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Theme Registry type - a record of theme IDs to metadata
|
|
14
|
+
*/
|
|
15
|
+
export type ThemeRegistry = Record<string, ThemeMetadata>;
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
|
-
*
|
|
18
|
+
* Create a new theme registry
|
|
13
19
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
id: string;
|
|
17
|
-
/** Theme definition from config */
|
|
18
|
-
definition: ThemeDefinition;
|
|
19
|
-
/** Resolved theme object (for JS themes) */
|
|
20
|
-
theme?: Theme;
|
|
21
|
-
/** Whether theme is loaded */
|
|
22
|
-
loaded: boolean;
|
|
23
|
-
/** Loading promise (if currently loading) */
|
|
24
|
-
loading?: Promise<Theme | void>;
|
|
25
|
-
/** Dependencies */
|
|
26
|
-
dependencies: string[];
|
|
27
|
-
/** Dependent themes (themes that depend on this one) */
|
|
28
|
-
dependents: string[];
|
|
20
|
+
export function createThemeRegistry(): ThemeRegistry {
|
|
21
|
+
return {};
|
|
29
22
|
}
|
|
30
23
|
|
|
31
24
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
25
|
+
* Register a theme
|
|
26
|
+
* @param registry - Theme registry object
|
|
27
|
+
* @param id - Theme identifier
|
|
28
|
+
* @param metadata - Theme metadata
|
|
35
29
|
*/
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
private initialized: boolean = false;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Initialize registry from config
|
|
43
|
-
*/
|
|
44
|
-
async initialize(config?: LoadedThemeConfig): Promise<void> {
|
|
45
|
-
if (this.initialized) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Load config if not provided
|
|
50
|
-
if (!config) {
|
|
51
|
-
try {
|
|
52
|
-
this.config = loadThemeConfig();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
// In browser environments, config loading will fail
|
|
55
|
-
// Use empty config as fallback
|
|
56
|
-
this.config = {
|
|
57
|
-
themes: {},
|
|
58
|
-
build: {
|
|
59
|
-
output: { directory: 'themes', formats: { expanded: '.css', compressed: '.min.css' } },
|
|
60
|
-
sass: { style: 'expanded', sourceMap: true, loadPaths: ['src'] },
|
|
61
|
-
},
|
|
62
|
-
runtime: {
|
|
63
|
-
basePath: '',
|
|
64
|
-
defaultTheme: undefined, // No default - use built-in styles
|
|
65
|
-
},
|
|
66
|
-
integration: {
|
|
67
|
-
cssVariables: { colorMode: '--color-mode' },
|
|
68
|
-
classNames: { theme: 'data-theme', colorMode: 'data-color-mode' },
|
|
69
|
-
},
|
|
70
|
-
dependencies: {},
|
|
71
|
-
validated: false,
|
|
72
|
-
errors: [],
|
|
73
|
-
warnings: [],
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
this.config = config;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Register all themes from config
|
|
81
|
-
for (const [themeId, definition] of Object.entries(this.config.themes)) {
|
|
82
|
-
this.register(themeId, definition);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Resolve dependencies
|
|
86
|
-
this.resolveDependencies();
|
|
87
|
-
|
|
88
|
-
this.initialized = true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Register a theme
|
|
93
|
-
*/
|
|
94
|
-
register(themeId: string, definition: ThemeDefinition): void {
|
|
95
|
-
// Get dependencies from config or definition
|
|
96
|
-
const dependencies =
|
|
97
|
-
this.config?.dependencies?.[themeId] ||
|
|
98
|
-
definition.dependencies ||
|
|
99
|
-
[];
|
|
100
|
-
|
|
101
|
-
const entry: RegistryEntry = {
|
|
102
|
-
id: themeId,
|
|
103
|
-
definition,
|
|
104
|
-
loaded: false,
|
|
105
|
-
dependencies: [...dependencies],
|
|
106
|
-
dependents: [],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
this.entries.set(themeId, entry);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Get theme entry
|
|
114
|
-
*/
|
|
115
|
-
get(themeId: string): RegistryEntry | undefined {
|
|
116
|
-
return this.entries.get(themeId);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Check if theme exists
|
|
121
|
-
*/
|
|
122
|
-
has(themeId: string): boolean {
|
|
123
|
-
return this.entries.has(themeId);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get all theme IDs
|
|
128
|
-
*/
|
|
129
|
-
getAllIds(): string[] {
|
|
130
|
-
return Array.from(this.entries.keys());
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Get all theme metadata
|
|
135
|
-
*/
|
|
136
|
-
getAllMetadata(): ThemeMetadata[] {
|
|
137
|
-
return Array.from(this.entries.values()).map(entry => ({
|
|
138
|
-
id: entry.id,
|
|
139
|
-
name: entry.definition.name,
|
|
140
|
-
type: entry.definition.type,
|
|
141
|
-
class: entry.definition.class,
|
|
142
|
-
description: entry.definition.description,
|
|
143
|
-
author: entry.definition.author,
|
|
144
|
-
version: entry.definition.version,
|
|
145
|
-
tags: entry.definition.tags,
|
|
146
|
-
supportsDarkMode: entry.definition.supportsDarkMode,
|
|
147
|
-
status: entry.definition.status,
|
|
148
|
-
a11y: entry.definition.a11y,
|
|
149
|
-
color: entry.definition.color,
|
|
150
|
-
features: entry.definition.features,
|
|
151
|
-
dependencies: entry.dependencies,
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get theme definition
|
|
157
|
-
*/
|
|
158
|
-
getDefinition(themeId: string): ThemeDefinition | undefined {
|
|
159
|
-
return this.entries.get(themeId)?.definition;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get theme object (for JS themes)
|
|
164
|
-
*/
|
|
165
|
-
getTheme(themeId: string): Theme | undefined {
|
|
166
|
-
return this.entries.get(themeId)?.theme;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Set theme object (for JS themes)
|
|
171
|
-
*/
|
|
172
|
-
setTheme(themeId: string, theme: Theme): void {
|
|
173
|
-
const entry = this.entries.get(themeId);
|
|
174
|
-
if (entry) {
|
|
175
|
-
entry.theme = theme;
|
|
176
|
-
entry.loaded = true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get dependencies for a theme
|
|
182
|
-
*/
|
|
183
|
-
getDependencies(themeId: string): string[] {
|
|
184
|
-
return this.entries.get(themeId)?.dependencies || [];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get dependents for a theme (themes that depend on this one)
|
|
189
|
-
*/
|
|
190
|
-
getDependents(themeId: string): string[] {
|
|
191
|
-
return this.entries.get(themeId)?.dependents || [];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Resolve all dependencies in correct order
|
|
196
|
-
*/
|
|
197
|
-
resolveDependencyOrder(themeId: string): string[] {
|
|
198
|
-
const resolved: string[] = [];
|
|
199
|
-
const visited = new Set<string>();
|
|
200
|
-
const visiting = new Set<string>();
|
|
201
|
-
|
|
202
|
-
const visit = (id: string): void => {
|
|
203
|
-
if (visiting.has(id)) {
|
|
204
|
-
throw new Error(`Circular dependency detected involving theme: ${id}`);
|
|
205
|
-
}
|
|
206
|
-
if (visited.has(id)) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
visiting.add(id);
|
|
211
|
-
const entry = this.entries.get(id);
|
|
212
|
-
if (entry) {
|
|
213
|
-
for (const dep of entry.dependencies) {
|
|
214
|
-
if (!this.has(dep)) {
|
|
215
|
-
throw new Error(`Theme "${id}" depends on non-existent theme: ${dep}`);
|
|
216
|
-
}
|
|
217
|
-
visit(dep);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
visiting.delete(id);
|
|
221
|
-
visited.add(id);
|
|
222
|
-
resolved.push(id);
|
|
223
|
-
};
|
|
30
|
+
export function registerTheme(registry: ThemeRegistry, id: string, metadata: ThemeMetadata): void {
|
|
31
|
+
registry[id] = metadata;
|
|
32
|
+
}
|
|
224
33
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Unregister a theme
|
|
36
|
+
* @param registry - Theme registry object
|
|
37
|
+
* @param id - Theme identifier
|
|
38
|
+
*/
|
|
39
|
+
export function unregisterTheme(registry: ThemeRegistry, id: string): boolean {
|
|
40
|
+
const exists = id in registry;
|
|
41
|
+
delete registry[id];
|
|
42
|
+
return exists;
|
|
43
|
+
}
|
|
228
44
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (depEntry) {
|
|
238
|
-
if (!depEntry.dependents.includes(entry.id)) {
|
|
239
|
-
depEntry.dependents.push(entry.id);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a theme is registered
|
|
47
|
+
* @param registry - Theme registry object
|
|
48
|
+
* @param id - Theme identifier
|
|
49
|
+
*/
|
|
50
|
+
export function hasTheme(registry: ThemeRegistry, id: string): boolean {
|
|
51
|
+
return id in registry;
|
|
52
|
+
}
|
|
245
53
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Get theme metadata
|
|
56
|
+
* @param registry - Theme registry object
|
|
57
|
+
* @param id - Theme identifier
|
|
58
|
+
*/
|
|
59
|
+
export function getTheme(registry: ThemeRegistry, id: string): ThemeMetadata | undefined {
|
|
60
|
+
return registry[id];
|
|
61
|
+
}
|
|
251
62
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
63
|
+
/**
|
|
64
|
+
* Get all registered theme metadata
|
|
65
|
+
* @param registry - Theme registry object
|
|
66
|
+
*/
|
|
67
|
+
export function getAllThemes(registry: ThemeRegistry): ThemeMetadata[] {
|
|
68
|
+
return Object.values(registry);
|
|
69
|
+
}
|
|
260
70
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all registered theme IDs
|
|
73
|
+
* @param registry - Theme registry object
|
|
74
|
+
*/
|
|
75
|
+
export function getThemeIds(registry: ThemeRegistry): string[] {
|
|
76
|
+
return Object.keys(registry);
|
|
77
|
+
}
|
|
269
78
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Clear all registered themes
|
|
81
|
+
* @param registry - Theme registry object
|
|
82
|
+
*/
|
|
83
|
+
export function clearThemes(registry: ThemeRegistry): void {
|
|
84
|
+
Object.keys(registry).forEach(key => delete registry[key]);
|
|
85
|
+
}
|
|
275
86
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.initialized = false;
|
|
283
|
-
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the number of registered themes
|
|
89
|
+
* @param registry - Theme registry object
|
|
90
|
+
*/
|
|
91
|
+
export function getThemeCount(registry: ThemeRegistry): number {
|
|
92
|
+
return Object.keys(registry).length;
|
|
284
93
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createTheme Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for createTheme function including automatic config loading
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import { createTheme } from '../createTheme';
|
|
9
|
+
import type { DesignTokens } from '../../tokens/tokens';
|
|
10
|
+
import type { Theme } from '../../types';
|
|
11
|
+
|
|
12
|
+
describe('createTheme', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Automatic Config Loading', () => {
|
|
18
|
+
it('should work without input (uses defaults or config)', () => {
|
|
19
|
+
// createTheme() should work even if config is not available
|
|
20
|
+
// It will fall back to default tokens
|
|
21
|
+
const css = createTheme();
|
|
22
|
+
|
|
23
|
+
expect(typeof css).toBe('string');
|
|
24
|
+
expect(css.length).toBeGreaterThan(0);
|
|
25
|
+
expect(css).toContain(':root');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should generate valid CSS with default tokens', () => {
|
|
29
|
+
const css = createTheme();
|
|
30
|
+
|
|
31
|
+
// Should contain CSS variable declarations
|
|
32
|
+
expect(css).toMatch(/--atomix-[a-z-]+:\s*[^;]+;/);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('DesignTokens Input', () => {
|
|
37
|
+
it('should accept DesignTokens and generate CSS', () => {
|
|
38
|
+
const tokens: Partial<DesignTokens> = {
|
|
39
|
+
'primary': '#7AFFD7',
|
|
40
|
+
'secondary': '#FF5733',
|
|
41
|
+
'spacing-4': '1rem',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const css = createTheme(tokens);
|
|
45
|
+
|
|
46
|
+
expect(css).toContain('--atomix-primary');
|
|
47
|
+
expect(css).toContain('#7AFFD7');
|
|
48
|
+
expect(css).toContain('--atomix-secondary');
|
|
49
|
+
expect(css).toContain('#FF5733');
|
|
50
|
+
expect(css).toContain('--atomix-spacing-4');
|
|
51
|
+
expect(css).toContain('1rem');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should merge with default tokens', () => {
|
|
55
|
+
const tokens: Partial<DesignTokens> = {
|
|
56
|
+
'primary': '#CUSTOM',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const css = createTheme(tokens);
|
|
60
|
+
|
|
61
|
+
// Should contain custom primary
|
|
62
|
+
expect(css).toContain('--atomix-primary');
|
|
63
|
+
expect(css).toContain('#CUSTOM');
|
|
64
|
+
|
|
65
|
+
// Should also contain default tokens
|
|
66
|
+
expect(css).toContain('--atomix-secondary');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Options', () => {
|
|
71
|
+
it('should respect prefix option', () => {
|
|
72
|
+
const tokens: Partial<DesignTokens> = {
|
|
73
|
+
'primary': '#7AFFD7',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const css = createTheme(tokens, { prefix: 'myapp' });
|
|
77
|
+
|
|
78
|
+
expect(css).toContain('--myapp-primary');
|
|
79
|
+
expect(css).not.toContain('--atomix-primary');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should respect selector option', () => {
|
|
83
|
+
const tokens: Partial<DesignTokens> = {
|
|
84
|
+
'primary': '#7AFFD7',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const css = createTheme(tokens, { selector: '[data-theme="dark"]' });
|
|
88
|
+
|
|
89
|
+
expect(css).toContain('[data-theme="dark"]');
|
|
90
|
+
expect(css).not.toContain(':root');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should use default prefix when not provided', () => {
|
|
94
|
+
const tokens: Partial<DesignTokens> = {
|
|
95
|
+
'primary': '#7AFFD7',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const css = createTheme(tokens);
|
|
99
|
+
|
|
100
|
+
expect(css).toContain('--atomix-primary');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('CSS Output Format', () => {
|
|
105
|
+
it('should generate valid CSS syntax', () => {
|
|
106
|
+
const tokens: Partial<DesignTokens> = {
|
|
107
|
+
'primary': '#7AFFD7',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const css = createTheme(tokens);
|
|
111
|
+
|
|
112
|
+
// Should be valid CSS
|
|
113
|
+
expect(css).toMatch(/^[^{]*\{[^}]*\}/s);
|
|
114
|
+
expect(css).toContain(':');
|
|
115
|
+
expect(css).toContain(';');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should format CSS with proper indentation', () => {
|
|
119
|
+
const tokens: Partial<DesignTokens> = {
|
|
120
|
+
'primary': '#7AFFD7',
|
|
121
|
+
'secondary': '#FF5733',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const css = createTheme(tokens);
|
|
125
|
+
|
|
126
|
+
// Should have proper formatting
|
|
127
|
+
expect(css).toContain('\n');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Composition Utilities
|
|
3
|
+
*
|
|
4
|
+
* Simplified utilities for composing, merging, and extending themes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Theme, ThemeOptions } from '../types';
|
|
8
|
+
import { createThemeObject } from './createThemeObject';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Deep Merge Utility
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if value is an object
|
|
16
|
+
*/
|
|
17
|
+
function isObject(item: any): item is Record<string, any> {
|
|
18
|
+
return item && typeof item === 'object' && !Array.isArray(item) && typeof item !== 'function';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Deep merge multiple objects
|
|
23
|
+
* Later objects override earlier ones
|
|
24
|
+
*/
|
|
25
|
+
export function deepMerge<T extends Record<string, unknown>>(...objects: Partial<T>[]): T {
|
|
26
|
+
if (objects.length === 0) return {} as T;
|
|
27
|
+
if (objects.length === 1) return objects[0] as T;
|
|
28
|
+
|
|
29
|
+
const [target, ...sources] = objects;
|
|
30
|
+
const result = { ...target } as T;
|
|
31
|
+
|
|
32
|
+
for (const source of sources) {
|
|
33
|
+
if (!source) continue;
|
|
34
|
+
|
|
35
|
+
for (const key in source) {
|
|
36
|
+
if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
|
|
37
|
+
|
|
38
|
+
const targetValue = result[key];
|
|
39
|
+
const sourceValue = source[key];
|
|
40
|
+
|
|
41
|
+
if (isObject(targetValue) && isObject(sourceValue)) {
|
|
42
|
+
// Recursively merge objects
|
|
43
|
+
result[key] = deepMerge(targetValue as Record<string, unknown>, sourceValue as Record<string, unknown>) as T[Extract<keyof T, string>];
|
|
44
|
+
} else {
|
|
45
|
+
// Override with source value
|
|
46
|
+
result[key] = sourceValue as T[Extract<keyof T, string>];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Theme Merging
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Merge multiple theme options into a single theme options object
|
|
60
|
+
*
|
|
61
|
+
* @param themes - Theme options to merge
|
|
62
|
+
* @returns Merged theme options
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const baseTheme = { palette: { primary: { main: '#000' } } };
|
|
67
|
+
* const customTheme = { palette: { secondary: { main: '#fff' } } };
|
|
68
|
+
* const merged = mergeTheme(baseTheme, customTheme);
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function mergeTheme(...themes: ThemeOptions[]): ThemeOptions {
|
|
72
|
+
return deepMerge({}, ...themes);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extend an existing theme with new options
|
|
77
|
+
*
|
|
78
|
+
* @param baseTheme - Base theme to extend (can be Theme or ThemeOptions)
|
|
79
|
+
* @param extension - Theme options to extend with
|
|
80
|
+
* @returns New theme with extended options
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const base = createTheme({ palette: { primary: { main: '#000' } } });
|
|
85
|
+
* const extended = extendTheme(base, {
|
|
86
|
+
* palette: { secondary: { main: '#fff' } }
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function extendTheme(baseTheme: Theme | ThemeOptions, extension: ThemeOptions): Theme {
|
|
91
|
+
// Convert baseTheme to ThemeOptions if it's a complete Theme
|
|
92
|
+
const baseOptions: ThemeOptions = (baseTheme as Theme & { __isJSTheme?: boolean }).__isJSTheme
|
|
93
|
+
? { ...baseTheme } as ThemeOptions
|
|
94
|
+
: baseTheme;
|
|
95
|
+
|
|
96
|
+
// Merge and create new theme
|
|
97
|
+
const merged = mergeTheme(baseOptions, extension);
|
|
98
|
+
return createThemeObject(merged);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default {
|
|
102
|
+
deepMerge,
|
|
103
|
+
mergeTheme,
|
|
104
|
+
extendTheme,
|
|
105
|
+
};
|