@shohojdhara/atomix 0.2.9 → 0.3.1
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/CHANGELOG.md +4 -0
- package/dist/atomix.css +309 -105
- package/dist/atomix.min.css +3 -5
- package/dist/index.d.ts +807 -51
- package/dist/index.esm.js +16367 -16405
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +16277 -16330
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/themes/applemix.css +309 -105
- package/dist/themes/applemix.min.css +5 -7
- package/dist/themes/boomdevs.css +202 -10
- package/dist/themes/boomdevs.min.css +3 -5
- package/dist/themes/esrar.css +309 -105
- package/dist/themes/esrar.min.css +4 -6
- package/dist/themes/flashtrade.css +310 -105
- package/dist/themes/flashtrade.min.css +5 -7
- package/dist/themes/mashroom.css +300 -96
- package/dist/themes/mashroom.min.css +4 -6
- package/dist/themes/shaj-default.css +300 -96
- package/dist/themes/shaj-default.min.css +4 -6
- package/package.json +1 -1
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +21 -32
- package/src/components/AtomixGlass/AtomixGlass.tsx +55 -42
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +205 -57
- package/src/components/AtomixGlass/GlassFilter.tsx +22 -8
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +221 -0
- package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -3
- package/src/components/AtomixGlass/shader-utils.ts +8 -0
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +319 -100
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +601 -105
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +30 -12
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +173 -38
- package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +18 -18
- package/src/components/AtomixGlass/stories/shared-components.tsx +27 -5
- package/src/components/Breadcrumb/Breadcrumb.tsx +8 -3
- package/src/components/Button/Button.tsx +62 -17
- package/src/components/Callout/Callout.test.tsx +8 -14
- package/src/components/Card/Card.tsx +103 -1
- package/src/components/Card/index.ts +3 -2
- package/src/components/Footer/Footer.stories.tsx +1 -2
- package/src/components/Footer/Footer.tsx +0 -5
- package/src/components/Footer/FooterLink.tsx +3 -2
- package/src/components/Footer/FooterSection.tsx +0 -7
- package/src/components/Icon/index.ts +1 -1
- package/src/components/Modal/Modal.stories.tsx +29 -38
- package/src/components/Modal/Modal.tsx +4 -4
- package/src/components/Navigation/Nav/NavItem.tsx +8 -3
- package/src/components/Navigation/SideMenu/SideMenu.tsx +49 -41
- package/src/components/Navigation/SideMenu/SideMenuItem.tsx +63 -19
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +977 -400
- package/src/components/VideoPlayer/VideoPlayer.tsx +1 -6
- package/src/lib/composables/shared-mouse-tracker.ts +133 -0
- package/src/lib/composables/useAtomixGlass.ts +303 -115
- package/src/lib/theme/ThemeManager.integration.test.ts +124 -0
- package/src/lib/theme/ThemeManager.stories.tsx +13 -13
- package/src/lib/theme/ThemeManager.test.ts +4 -0
- package/src/lib/theme/ThemeManager.ts +203 -59
- package/src/lib/theme/ThemeProvider.tsx +183 -33
- package/src/lib/theme/composeTheme.ts +375 -0
- package/src/lib/theme/createTheme.test.ts +475 -0
- package/src/lib/theme/createTheme.ts +510 -0
- package/src/lib/theme/generateCSSVariables.ts +713 -0
- package/src/lib/theme/index.ts +67 -0
- package/src/lib/theme/themeUtils.ts +333 -0
- package/src/lib/theme/types.ts +337 -8
- package/src/lib/theme/useTheme.test.tsx +2 -1
- package/src/lib/theme/useTheme.ts +6 -22
- package/src/lib/types/components.ts +152 -57
- package/src/styles/01-settings/_index.scss +2 -2
- package/src/styles/01-settings/_settings.badge.scss +2 -2
- package/src/styles/01-settings/_settings.border-radius.scss +1 -1
- package/src/styles/01-settings/{_settings.maps.scss → _settings.design-tokens.scss} +163 -49
- package/src/styles/01-settings/_settings.modal.scss +1 -1
- package/src/styles/01-settings/_settings.spacing.scss +14 -13
- package/src/styles/03-generic/_generic.root.scss +131 -50
- package/src/styles/05-objects/_objects.block.scss +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +20 -22
- package/src/styles/06-components/_components.badge.scss +2 -2
- package/src/styles/06-components/_components.button.scss +1 -1
- package/src/styles/06-components/_components.callout.scss +1 -1
- package/src/styles/06-components/_components.card.scss +74 -2
- package/src/styles/06-components/_components.chart.scss +1 -1
- package/src/styles/06-components/_components.dropdown.scss +6 -0
- package/src/styles/06-components/_components.footer.scss +1 -1
- package/src/styles/06-components/_components.list-group.scss +1 -1
- package/src/styles/06-components/_components.list.scss +1 -1
- package/src/styles/06-components/_components.menu.scss +1 -1
- package/src/styles/06-components/_components.messages.scss +1 -1
- package/src/styles/06-components/_components.modal.scss +7 -2
- package/src/styles/06-components/_components.navbar.scss +1 -1
- package/src/styles/06-components/_components.popover.scss +10 -0
- package/src/styles/06-components/_components.product-review.scss +1 -1
- package/src/styles/06-components/_components.progress.scss +1 -1
- package/src/styles/06-components/_components.rating.scss +1 -1
- package/src/styles/06-components/_components.spinner.scss +1 -1
- package/src/styles/99-utilities/_utilities.background.scss +1 -1
- package/src/styles/99-utilities/_utilities.border.scss +1 -1
- package/src/styles/99-utilities/_utilities.link.scss +1 -1
- package/src/styles/99-utilities/_utilities.text.scss +1 -1
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Variable Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates CSS custom properties from theme objects and injects them into the DOM.
|
|
5
|
+
* Follows the existing --atomix- prefix convention.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Theme } from './types';
|
|
9
|
+
import { isBrowser } from './utils';
|
|
10
|
+
import { hexToRgb, alpha, lighten, darken, emphasize } from './themeUtils';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// CSS Variable Generation
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for CSS variable generation
|
|
18
|
+
*/
|
|
19
|
+
export interface GenerateCSSVariablesOptions {
|
|
20
|
+
/** CSS selector for the variables (default: ':root') */
|
|
21
|
+
selector?: string;
|
|
22
|
+
/** Whether to inject the CSS into the DOM */
|
|
23
|
+
inject?: boolean;
|
|
24
|
+
/** ID for the injected style element */
|
|
25
|
+
styleId?: string;
|
|
26
|
+
/** Prefix for CSS variables (default: 'atomix') */
|
|
27
|
+
prefix?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert a nested object to flat CSS variable declarations
|
|
32
|
+
*/
|
|
33
|
+
function flattenObject(
|
|
34
|
+
obj: Record<string, any>,
|
|
35
|
+
prefix: string = '',
|
|
36
|
+
result: Record<string, string> = {}
|
|
37
|
+
): Record<string, string> {
|
|
38
|
+
for (const key in obj) {
|
|
39
|
+
if (!obj.hasOwnProperty(key)) continue;
|
|
40
|
+
|
|
41
|
+
const value = obj[key];
|
|
42
|
+
const newKey = prefix ? `${prefix}-${key}` : key;
|
|
43
|
+
|
|
44
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
45
|
+
// Skip special objects like functions
|
|
46
|
+
if (typeof value === 'function') continue;
|
|
47
|
+
|
|
48
|
+
// Recursively flatten nested objects
|
|
49
|
+
flattenObject(value, newKey, result);
|
|
50
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
51
|
+
// Convert camelCase to kebab-case
|
|
52
|
+
const kebabKey = newKey.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
53
|
+
result[kebabKey] = String(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate a color scale from a base color (1-10 steps)
|
|
62
|
+
* Creates lighter to darker variations
|
|
63
|
+
*/
|
|
64
|
+
function generateColorScale(baseColor: string, prefix: string, colorName: string): Record<string, string> {
|
|
65
|
+
const vars: Record<string, string> = {};
|
|
66
|
+
const rgb = hexToRgb(baseColor);
|
|
67
|
+
if (!rgb) return vars;
|
|
68
|
+
|
|
69
|
+
// Generate 10-step scale
|
|
70
|
+
// Steps 1-5: lighter variations
|
|
71
|
+
// Step 6: base color
|
|
72
|
+
// Steps 7-10: darker variations
|
|
73
|
+
for (let i = 1; i <= 10; i++) {
|
|
74
|
+
let color: string;
|
|
75
|
+
if (i < 6) {
|
|
76
|
+
// Lighter: mix with white
|
|
77
|
+
const mixRatio = (6 - i) / 5;
|
|
78
|
+
color = lighten(baseColor, mixRatio * 0.8);
|
|
79
|
+
} else if (i === 6) {
|
|
80
|
+
// Base color
|
|
81
|
+
color = baseColor;
|
|
82
|
+
} else {
|
|
83
|
+
// Darker: mix with black
|
|
84
|
+
const mixRatio = (i - 6) / 4;
|
|
85
|
+
color = darken(baseColor, mixRatio * 0.6);
|
|
86
|
+
}
|
|
87
|
+
vars[`${prefix}-${colorName}-${i}`] = color;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return vars;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate CSS variables from theme palette
|
|
95
|
+
*/
|
|
96
|
+
function generatePaletteVariables(palette: Theme['palette'], prefix: string): Record<string, string> {
|
|
97
|
+
const vars: Record<string, string> = {};
|
|
98
|
+
|
|
99
|
+
// Primary, secondary, error, warning, info, success
|
|
100
|
+
const colorKeys = ['primary', 'secondary', 'error', 'warning', 'info', 'success'] as const;
|
|
101
|
+
colorKeys.forEach((key) => {
|
|
102
|
+
const color = palette[key];
|
|
103
|
+
if (color && typeof color === 'object') {
|
|
104
|
+
// Main color
|
|
105
|
+
vars[`${prefix}-${key}`] = color.main;
|
|
106
|
+
|
|
107
|
+
// Generate RGB for transparency support
|
|
108
|
+
const rgb = hexToRgb(color.main);
|
|
109
|
+
if (rgb) {
|
|
110
|
+
vars[`${prefix}-${key}-rgb`] = `${rgb.r}, ${rgb.g}, ${rgb.b}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Map dark variant to hover (closest SCSS equivalent)
|
|
114
|
+
if (color.dark) {
|
|
115
|
+
vars[`${prefix}-${key}-hover`] = color.dark;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Generate semantic color variants
|
|
119
|
+
// Text emphasis: emphasized version of the color for text
|
|
120
|
+
vars[`${prefix}-${key}-text-emphasis`] = emphasize(color.main, 0.15);
|
|
121
|
+
|
|
122
|
+
// Background subtle: very light version for backgrounds
|
|
123
|
+
vars[`${prefix}-${key}-bg-subtle`] = alpha(color.main, 0.1);
|
|
124
|
+
|
|
125
|
+
// Border subtle: light version for borders
|
|
126
|
+
vars[`${prefix}-${key}-border-subtle`] = alpha(color.main, 0.2);
|
|
127
|
+
|
|
128
|
+
// Generate full color scale (1-10)
|
|
129
|
+
const colorScale = generateColorScale(color.main, prefix, key);
|
|
130
|
+
Object.assign(vars, colorScale);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Generate gray scale from text colors
|
|
135
|
+
// Use text.primary as base for gray scale
|
|
136
|
+
if (palette.text?.primary) {
|
|
137
|
+
const grayScale = generateColorScale(palette.text.primary, prefix, 'gray');
|
|
138
|
+
Object.assign(vars, grayScale);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Generate red, green, blue, yellow scales if available
|
|
142
|
+
// These are typically used for semantic colors but can be extended
|
|
143
|
+
if (palette.error && typeof palette.error === 'object' && palette.error.main) {
|
|
144
|
+
const redScale = generateColorScale(palette.error.main, prefix, 'red');
|
|
145
|
+
Object.assign(vars, redScale);
|
|
146
|
+
}
|
|
147
|
+
if (palette.success && typeof palette.success === 'object' && palette.success.main) {
|
|
148
|
+
const greenScale = generateColorScale(palette.success.main, prefix, 'green');
|
|
149
|
+
Object.assign(vars, greenScale);
|
|
150
|
+
}
|
|
151
|
+
if (palette.info && typeof palette.info === 'object' && palette.info.main) {
|
|
152
|
+
const blueScale = generateColorScale(palette.info.main, prefix, 'blue');
|
|
153
|
+
Object.assign(vars, blueScale);
|
|
154
|
+
}
|
|
155
|
+
if (palette.warning && typeof palette.warning === 'object' && palette.warning.main) {
|
|
156
|
+
const yellowScale = generateColorScale(palette.warning.main, prefix, 'yellow');
|
|
157
|
+
Object.assign(vars, yellowScale);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Background mappings to SCSS body variables
|
|
161
|
+
if (palette.background) {
|
|
162
|
+
vars[`${prefix}-body-bg`] = palette.background.default;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Text mappings to SCSS body variables
|
|
166
|
+
if (palette.text) {
|
|
167
|
+
vars[`${prefix}-body-color`] = palette.text.primary;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Heading color (defaults to text primary)
|
|
171
|
+
if (palette.text) {
|
|
172
|
+
vars[`${prefix}-heading-color`] = palette.text.primary;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Link colors (defaults to primary color)
|
|
176
|
+
if (palette.primary) {
|
|
177
|
+
vars[`${prefix}-link-color`] = palette.primary.main;
|
|
178
|
+
const linkRgb = hexToRgb(palette.primary.main);
|
|
179
|
+
if (linkRgb) {
|
|
180
|
+
vars[`${prefix}-link-color-rgb`] = `${linkRgb.r}, ${linkRgb.g}, ${linkRgb.b}`;
|
|
181
|
+
}
|
|
182
|
+
// Link hover color (slightly darker)
|
|
183
|
+
vars[`${prefix}-link-hover-color`] = palette.primary.dark || darken(palette.primary.main, 0.1);
|
|
184
|
+
const linkHoverRgb = hexToRgb(palette.primary.dark || darken(palette.primary.main, 0.1));
|
|
185
|
+
if (linkHoverRgb) {
|
|
186
|
+
vars[`${prefix}-link-hover-color-rgb`] = `${linkHoverRgb.r}, ${linkHoverRgb.g}, ${linkHoverRgb.b}`;
|
|
187
|
+
}
|
|
188
|
+
// Link decoration (default: underline)
|
|
189
|
+
vars[`${prefix}-link-decoration`] = 'underline';
|
|
190
|
+
vars[`${prefix}-link-hover-decoration`] = 'none';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Border color (defaults to subtle gray)
|
|
194
|
+
if (palette.text) {
|
|
195
|
+
vars[`${prefix}-border-color`] = alpha(palette.text.primary, 0.1);
|
|
196
|
+
vars[`${prefix}-border-color-translucent`] = alpha(palette.text.primary, 0.15);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Focus border color (defaults to primary)
|
|
200
|
+
if (palette.primary) {
|
|
201
|
+
vars[`${prefix}-focus-border-color`] = palette.primary.main;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Form validation colors
|
|
205
|
+
if (palette.success) {
|
|
206
|
+
vars[`${prefix}-form-valid-color`] = palette.success.main;
|
|
207
|
+
vars[`${prefix}-form-valid-border-color`] = alpha(palette.success.main, 0.3);
|
|
208
|
+
}
|
|
209
|
+
if (palette.error) {
|
|
210
|
+
vars[`${prefix}-form-invalid-color`] = palette.error.main;
|
|
211
|
+
vars[`${prefix}-form-invalid-border-color`] = alpha(palette.error.main, 0.3);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Code/highlight colors
|
|
215
|
+
// Highlight background (defaults to subtle yellow)
|
|
216
|
+
if (palette.warning) {
|
|
217
|
+
vars[`${prefix}-highlight-bg`] = alpha(palette.warning.main, 0.2);
|
|
218
|
+
} else {
|
|
219
|
+
vars[`${prefix}-highlight-bg`] = 'rgba(255, 235, 59, 0.2)';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Code color (defaults to text secondary)
|
|
223
|
+
if (palette.text) {
|
|
224
|
+
vars[`${prefix}-code-color`] = palette.text.secondary;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return vars;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate CSS variables from theme typography
|
|
232
|
+
*/
|
|
233
|
+
function generateTypographyVariables(
|
|
234
|
+
typography: Theme['typography'],
|
|
235
|
+
prefix: string
|
|
236
|
+
): Record<string, string> {
|
|
237
|
+
const vars: Record<string, string> = {};
|
|
238
|
+
|
|
239
|
+
// Font family (SCSS: --atomix-body-font-family)
|
|
240
|
+
vars[`${prefix}-body-font-family`] = typography.fontFamily;
|
|
241
|
+
// Additional font family tokens
|
|
242
|
+
vars[`${prefix}-font-sans-serif`] = typography.fontFamily;
|
|
243
|
+
vars[`${prefix}-font-monospace`] = 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
244
|
+
|
|
245
|
+
// Root font size (SCSS: --atomix-root-font-size)
|
|
246
|
+
// Typically 16px, but can be customized
|
|
247
|
+
const rootFontSize = typography.fontSize || 16;
|
|
248
|
+
vars[`${prefix}-root-font-size`] = `${rootFontSize}px`;
|
|
249
|
+
|
|
250
|
+
// Base font size (SCSS: --atomix-body-font-size)
|
|
251
|
+
const baseFontSize = typography.fontSize;
|
|
252
|
+
vars[`${prefix}-body-font-size`] = `${baseFontSize}px`;
|
|
253
|
+
|
|
254
|
+
// Base font weight (SCSS: --atomix-body-font-weight)
|
|
255
|
+
vars[`${prefix}-body-font-weight`] = String(typography.fontWeightRegular);
|
|
256
|
+
|
|
257
|
+
// Font weight scale
|
|
258
|
+
vars[`${prefix}-font-weight-light`] = String(typography.fontWeightLight ?? 300);
|
|
259
|
+
vars[`${prefix}-font-weight-normal`] = String(typography.fontWeightRegular ?? 400);
|
|
260
|
+
vars[`${prefix}-font-weight-medium`] = String(typography.fontWeightMedium ?? 500);
|
|
261
|
+
vars[`${prefix}-font-weight-semibold`] = String(typography.fontWeightSemiBold ?? 600);
|
|
262
|
+
vars[`${prefix}-font-weight-bold`] = String(typography.fontWeightBold ?? 700);
|
|
263
|
+
// Optional font weights (may not be in theme, but exist in design tokens)
|
|
264
|
+
if ('fontWeightHeavy' in typography) {
|
|
265
|
+
vars[`${prefix}-font-weight-heavy`] = String((typography as any).fontWeightHeavy || 800);
|
|
266
|
+
}
|
|
267
|
+
if ('fontWeightBlack' in typography) {
|
|
268
|
+
vars[`${prefix}-font-weight-black`] = String((typography as any).fontWeightBlack || 900);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Base line height (SCSS: --atomix-body-line-height)
|
|
272
|
+
const baseLineHeight = typeof typography.body1?.lineHeight === 'number'
|
|
273
|
+
? typography.body1.lineHeight
|
|
274
|
+
: parseFloat(String(typography.body1?.lineHeight || 1.2));
|
|
275
|
+
vars[`${prefix}-body-line-height`] = String(baseLineHeight);
|
|
276
|
+
|
|
277
|
+
// Line height scale (using calculated defaults based on design tokens)
|
|
278
|
+
vars[`${prefix}-line-height-base`] = String(baseLineHeight);
|
|
279
|
+
vars[`${prefix}-line-height-sm`] = String(1.43);
|
|
280
|
+
vars[`${prefix}-line-height-lg`] = String(1.56);
|
|
281
|
+
|
|
282
|
+
// Extended font size scale (matching design system tokens)
|
|
283
|
+
const fontSizeXs = baseFontSize * 0.75; // 12px if base is 16px
|
|
284
|
+
const fontSizeSm = baseFontSize * 0.875; // 14px if base is 16px
|
|
285
|
+
const fontSizeMd = baseFontSize * 1; // 16px if base is 16px (same as base)
|
|
286
|
+
const fontSizeLg = baseFontSize * 1.125; // 18px if base is 16px
|
|
287
|
+
const fontSizeXl = baseFontSize * 1.5; // 24px if base is 16px
|
|
288
|
+
const fontSize2xl = baseFontSize * 2; // 32px if base is 16px
|
|
289
|
+
|
|
290
|
+
vars[`${prefix}-font-size-xs`] = `${fontSizeXs}px`;
|
|
291
|
+
vars[`${prefix}-font-size-sm`] = `${fontSizeSm}px`;
|
|
292
|
+
vars[`${prefix}-font-size-md`] = `${fontSizeMd}px`;
|
|
293
|
+
vars[`${prefix}-font-size-lg`] = `${fontSizeLg}px`;
|
|
294
|
+
vars[`${prefix}-font-size-xl`] = `${fontSizeXl}px`;
|
|
295
|
+
vars[`${prefix}-font-size-2xl`] = `${fontSize2xl}px`;
|
|
296
|
+
|
|
297
|
+
// Display font size (optional, may not be in theme)
|
|
298
|
+
if ('display1' in typography) {
|
|
299
|
+
const display1 = (typography as any).display1;
|
|
300
|
+
vars[`${prefix}-display-1`] = typeof display1 === 'string' ? display1 : `${display1}px`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Letter spacing for headings (from typography config)
|
|
304
|
+
const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
|
|
305
|
+
headings.forEach((heading) => {
|
|
306
|
+
const headingConfig = typography[heading];
|
|
307
|
+
if (headingConfig?.letterSpacing) {
|
|
308
|
+
vars[`${prefix}-letter-spacing-${heading}`] = String(headingConfig.letterSpacing);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return vars;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Generate CSS variables from theme shadows
|
|
317
|
+
*/
|
|
318
|
+
function generateShadowVariables(shadows: Theme['shadows'], prefix: string): Record<string, string> {
|
|
319
|
+
const vars: Record<string, string> = {};
|
|
320
|
+
|
|
321
|
+
// Map JS shadow keys to SCSS variables
|
|
322
|
+
// SCSS uses --atomix-box-shadow (base) and --atomix-box-shadow-{size}
|
|
323
|
+
if (shadows.md) vars[`${prefix}-box-shadow`] = shadows.md; // Map md to base
|
|
324
|
+
if (shadows.xs) vars[`${prefix}-box-shadow-xs`] = shadows.xs;
|
|
325
|
+
if (shadows.sm) vars[`${prefix}-box-shadow-sm`] = shadows.sm;
|
|
326
|
+
if (shadows.lg) vars[`${prefix}-box-shadow-lg`] = shadows.lg;
|
|
327
|
+
if (shadows.xl) vars[`${prefix}-box-shadow-xl`] = shadows.xl;
|
|
328
|
+
|
|
329
|
+
// Inset shadow (generate from base shadow if not provided)
|
|
330
|
+
if (shadows.inset) {
|
|
331
|
+
vars[`${prefix}-box-shadow-inset`] = shadows.inset;
|
|
332
|
+
} else if (shadows.sm) {
|
|
333
|
+
// Generate inset shadow from sm shadow
|
|
334
|
+
vars[`${prefix}-box-shadow-inset`] = shadows.sm.replace(/^0\s/, 'inset ');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return vars;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Generate CSS variables from theme transitions
|
|
342
|
+
*/
|
|
343
|
+
function generateTransitionVariables(
|
|
344
|
+
transitions: Theme['transitions'],
|
|
345
|
+
prefix: string
|
|
346
|
+
): Record<string, string> {
|
|
347
|
+
const vars: Record<string, string> = {};
|
|
348
|
+
|
|
349
|
+
// Map JS transition durations to SCSS equivalents (in seconds to match design tokens)
|
|
350
|
+
const durationFast = transitions.duration.shortest || 150;
|
|
351
|
+
const durationBase = transitions.duration.standard || 300;
|
|
352
|
+
const durationSlow = transitions.duration.complex || 500;
|
|
353
|
+
const durationSlower = 700; // Default value for slower duration
|
|
354
|
+
const easingBase = transitions.easing.easeInOut || 'cubic-bezier(0.23, 1, 0.32, 1)';
|
|
355
|
+
|
|
356
|
+
vars[`${prefix}-transition-duration-fast`] = `${durationFast / 1000}s`;
|
|
357
|
+
vars[`${prefix}-transition-duration-base`] = `${durationBase / 1000}s`;
|
|
358
|
+
vars[`${prefix}-transition-duration-slow`] = `${durationSlow / 1000}s`;
|
|
359
|
+
vars[`${prefix}-transition-duration-slower`] = `${durationSlower / 1000}s`;
|
|
360
|
+
|
|
361
|
+
// Map easing functions
|
|
362
|
+
vars[`${prefix}-easing-base`] = easingBase;
|
|
363
|
+
vars[`${prefix}-easing-ease-in-out`] = transitions.easing.easeInOut || 'cubic-bezier(0.4, 0, 0.2, 1)';
|
|
364
|
+
vars[`${prefix}-easing-ease-out`] = transitions.easing.easeOut || 'cubic-bezier(0, 0, 0.2, 1)';
|
|
365
|
+
vars[`${prefix}-easing-ease-in`] = transitions.easing.easeIn || 'cubic-bezier(0.4, 0, 1, 1)';
|
|
366
|
+
vars[`${prefix}-easing-ease-linear`] = 'linear';
|
|
367
|
+
|
|
368
|
+
// Generate full transition strings
|
|
369
|
+
vars[`${prefix}-transition-fast`] = `all ${durationFast / 1000}s ${easingBase}`;
|
|
370
|
+
vars[`${prefix}-transition-base`] = `all ${durationBase / 1000}s ${easingBase}`;
|
|
371
|
+
vars[`${prefix}-transition-slow`] = `all ${durationSlow / 1000}s ${easingBase}`;
|
|
372
|
+
|
|
373
|
+
return vars;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Generate CSS variables from theme z-index
|
|
378
|
+
*/
|
|
379
|
+
function generateZIndexVariables(zIndex: Theme['zIndex'], prefix: string): Record<string, string> {
|
|
380
|
+
const vars: Record<string, string> = {};
|
|
381
|
+
|
|
382
|
+
// Map to SCSS z-layers
|
|
383
|
+
if (zIndex.mobileStepper) vars[`${prefix}-z-dropdown`] = String(zIndex.mobileStepper);
|
|
384
|
+
if (zIndex.appBar) vars[`${prefix}-z-sticky`] = String(zIndex.appBar);
|
|
385
|
+
vars[`${prefix}-z-fixed`] = '1030'; // Default fixed
|
|
386
|
+
if (zIndex.modal) vars[`${prefix}-z-modal`] = String(zIndex.modal);
|
|
387
|
+
if (zIndex.speedDial) vars[`${prefix}-z-popover`] = String(zIndex.speedDial);
|
|
388
|
+
if (zIndex.tooltip) vars[`${prefix}-z-tooltip`] = String(zIndex.tooltip);
|
|
389
|
+
if (zIndex.drawer) vars[`${prefix}-z-drawer`] = String(zIndex.drawer);
|
|
390
|
+
|
|
391
|
+
// Keep original mappings if needed or remove if strictly aligning
|
|
392
|
+
if (zIndex.snackbar) vars[`${prefix}-z-snackbar`] = String(zIndex.snackbar);
|
|
393
|
+
|
|
394
|
+
return vars;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Generate CSS variables from theme breakpoints
|
|
399
|
+
*/
|
|
400
|
+
function generateBreakpointVariables(
|
|
401
|
+
breakpoints: Theme['breakpoints'],
|
|
402
|
+
prefix: string
|
|
403
|
+
): Record<string, string> {
|
|
404
|
+
const vars: Record<string, string> = {};
|
|
405
|
+
|
|
406
|
+
Object.entries(breakpoints.values).forEach(([key, value]) => {
|
|
407
|
+
vars[`${prefix}-breakpoint-${key}`] = `${value}${breakpoints.unit}`;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return vars;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate CSS variables from theme spacing
|
|
415
|
+
*/
|
|
416
|
+
function generateSpacingVariables(
|
|
417
|
+
spacing: Theme['spacing'],
|
|
418
|
+
prefix: string
|
|
419
|
+
): Record<string, string> {
|
|
420
|
+
const vars: Record<string, string> = {};
|
|
421
|
+
|
|
422
|
+
// Generate spacing scale based on design system tokens
|
|
423
|
+
// Note: Some spacing values like px-6, px-10, etc. are generated from the spacing function
|
|
424
|
+
// and should match the actual design system spacing scale
|
|
425
|
+
// Values are multipliers for the spacing base unit (default 4px)
|
|
426
|
+
const spacingScale: Record<string, number> = {
|
|
427
|
+
'0': 0,
|
|
428
|
+
'1': 1, // 4px (1 × 4 = 4px)
|
|
429
|
+
'px-6': 1.5, // 6px (1.5 × 4 = 6px)
|
|
430
|
+
'2': 2, // 8px (2 × 4 = 8px)
|
|
431
|
+
'px-10': 2.5, // 10px (2.5 × 4 = 10px)
|
|
432
|
+
'3': 3, // 12px (3 × 4 = 12px)
|
|
433
|
+
'px-14': 3.5, // 14px (3.5 × 4 = 14px)
|
|
434
|
+
'4': 4, // 16px (4 × 4 = 16px)
|
|
435
|
+
'5': 5, // 20px (5 × 4 = 20px)
|
|
436
|
+
'px-22': 5.5, // 22px (5.5 × 4 = 22px)
|
|
437
|
+
'6': 6, // 24px (6 × 4 = 24px)
|
|
438
|
+
'7': 7, // 28px (7 × 4 = 28px)
|
|
439
|
+
'px-30': 7.5, // 30px (7.5 × 4 = 30px)
|
|
440
|
+
'8': 8, // 32px (8 × 4 = 32px)
|
|
441
|
+
'9': 9, // 36px (9 × 4 = 36px)
|
|
442
|
+
'10': 10, // 40px (10 × 4 = 40px)
|
|
443
|
+
'11': 11, // 44px (11 × 4 = 44px)
|
|
444
|
+
'12': 12, // 48px (12 × 4 = 48px)
|
|
445
|
+
'14': 14, // 56px (14 × 4 = 56px)
|
|
446
|
+
'16': 16, // 64px (16 × 4 = 64px)
|
|
447
|
+
'20': 20, // 80px (20 × 4 = 80px)
|
|
448
|
+
'24': 24, // 96px (24 × 4 = 96px)
|
|
449
|
+
'28': 28, // 112px (28 × 4 = 112px)
|
|
450
|
+
'32': 32, // 128px (32 × 4 = 128px)
|
|
451
|
+
'36': 36, // 144px (36 × 4 = 144px)
|
|
452
|
+
'40': 40, // 160px (40 × 4 = 160px)
|
|
453
|
+
'44': 44, // 176px (44 × 4 = 176px)
|
|
454
|
+
'48': 48, // 192px (48 × 4 = 192px)
|
|
455
|
+
'52': 52, // 208px (52 × 4 = 208px)
|
|
456
|
+
'56': 56, // 224px (56 × 4 = 224px)
|
|
457
|
+
'60': 60, // 240px (60 × 4 = 240px)
|
|
458
|
+
'64': 64, // 256px (64 × 4 = 256px)
|
|
459
|
+
'72': 72, // 288px (72 × 4 = 288px)
|
|
460
|
+
'80': 80, // 320px (80 × 4 = 320px)
|
|
461
|
+
'90': 90, // 360px (90 × 4 = 360px)
|
|
462
|
+
'200': 200, // 800px (200 × 4 = 800px)
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Generate spacing variables
|
|
466
|
+
// Use the spacing function to calculate values
|
|
467
|
+
Object.entries(spacingScale).forEach(([key, multiplier]) => {
|
|
468
|
+
const spacingValue = spacing(multiplier);
|
|
469
|
+
// Extract numeric value and convert to rem if needed
|
|
470
|
+
const match = spacingValue.match(/([\d.]+)px/);
|
|
471
|
+
if (match && match[1]) {
|
|
472
|
+
const pxValue = parseFloat(match[1]);
|
|
473
|
+
const remValue = pxValue / 16; // Convert px to rem (assuming 16px base)
|
|
474
|
+
vars[`${prefix}-spacing-${key}`] = `${remValue}rem`;
|
|
475
|
+
} else {
|
|
476
|
+
vars[`${prefix}-spacing-${key}`] = spacingValue;
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
return vars;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Generate border-related CSS variables
|
|
485
|
+
*/
|
|
486
|
+
function generateBorderVariables(
|
|
487
|
+
palette: Theme['palette'],
|
|
488
|
+
prefix: string
|
|
489
|
+
): Record<string, string> {
|
|
490
|
+
const vars: Record<string, string> = {};
|
|
491
|
+
|
|
492
|
+
// Border width
|
|
493
|
+
vars[`${prefix}-border-width`] = '1px';
|
|
494
|
+
|
|
495
|
+
// Border style
|
|
496
|
+
vars[`${prefix}-border-style`] = 'solid';
|
|
497
|
+
|
|
498
|
+
// Border color (already generated in palette, but ensure it exists)
|
|
499
|
+
if (!vars[`${prefix}-border-color`] && palette.text) {
|
|
500
|
+
vars[`${prefix}-border-color`] = alpha(palette.text.primary, 0.1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return vars;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Generate border radius CSS variables
|
|
508
|
+
*/
|
|
509
|
+
function generateBorderRadiusVariables(
|
|
510
|
+
borderRadius: Theme['borderRadius'],
|
|
511
|
+
prefix: string
|
|
512
|
+
): Record<string, string> {
|
|
513
|
+
const vars: Record<string, string> = {};
|
|
514
|
+
|
|
515
|
+
// Convert values to string with proper units
|
|
516
|
+
const formatValue = (value: string | number | undefined, defaultValue: string): string => {
|
|
517
|
+
if (value === undefined) return defaultValue;
|
|
518
|
+
if (typeof value === 'number') return `${value}px`;
|
|
519
|
+
return String(value);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// Base border radius (maps to spacing-2 = 8px)
|
|
523
|
+
vars[`${prefix}-border-radius`] = formatValue(borderRadius.base, '0.5rem');
|
|
524
|
+
|
|
525
|
+
// Small border radius (maps to spacing-1 = 4px)
|
|
526
|
+
vars[`${prefix}-border-radius-sm`] = formatValue(borderRadius.sm, '0.25rem');
|
|
527
|
+
|
|
528
|
+
// Large border radius (maps to spacing-2.5 = 10px)
|
|
529
|
+
vars[`${prefix}-border-radius-lg`] = formatValue(borderRadius.lg, '0.625rem');
|
|
530
|
+
|
|
531
|
+
// Extra large border radius (maps to spacing-3 = 12px)
|
|
532
|
+
vars[`${prefix}-border-radius-xl`] = formatValue(borderRadius.xl, '0.75rem');
|
|
533
|
+
|
|
534
|
+
// 2X large border radius (maps to spacing-4 = 16px)
|
|
535
|
+
vars[`${prefix}-border-radius-xxl`] = formatValue(borderRadius.xxl, '1rem');
|
|
536
|
+
// Also add deprecated 2xl alias for consistency
|
|
537
|
+
vars[`${prefix}-border-radius-2xl`] = formatValue(borderRadius.xxl, '1rem');
|
|
538
|
+
|
|
539
|
+
// 3X large border radius (maps to spacing-6 = 24px)
|
|
540
|
+
vars[`${prefix}-border-radius-3xl`] = formatValue(borderRadius['3xl'], '1.5rem');
|
|
541
|
+
|
|
542
|
+
// 4X large border radius (maps to spacing-8 = 32px)
|
|
543
|
+
vars[`${prefix}-border-radius-4xl`] = formatValue(borderRadius['4xl'], '2rem');
|
|
544
|
+
|
|
545
|
+
// Pill shape (fully rounded, maps to spacing-200 = 800px)
|
|
546
|
+
vars[`${prefix}-border-radius-pill`] = formatValue(borderRadius.pill, '50rem');
|
|
547
|
+
|
|
548
|
+
return vars;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Generate focus ring CSS variables
|
|
553
|
+
*/
|
|
554
|
+
function generateFocusRingVariables(
|
|
555
|
+
palette: Theme['palette'],
|
|
556
|
+
prefix: string
|
|
557
|
+
): Record<string, string> {
|
|
558
|
+
const vars: Record<string, string> = {};
|
|
559
|
+
|
|
560
|
+
// Focus ring properties
|
|
561
|
+
vars[`${prefix}-focus-ring-width`] = '3px';
|
|
562
|
+
vars[`${prefix}-focus-ring-offset`] = '2px';
|
|
563
|
+
vars[`${prefix}-focus-ring-opacity`] = '0.25';
|
|
564
|
+
|
|
565
|
+
return vars;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Generate CSS custom properties from a theme object
|
|
570
|
+
*
|
|
571
|
+
* @param theme - Theme object created with createTheme
|
|
572
|
+
* @param options - Generation options
|
|
573
|
+
* @returns CSS string with custom properties
|
|
574
|
+
*/
|
|
575
|
+
export function generateCSSVariables(
|
|
576
|
+
theme: Theme,
|
|
577
|
+
options: GenerateCSSVariablesOptions = {}
|
|
578
|
+
): string {
|
|
579
|
+
const {
|
|
580
|
+
selector = ':root',
|
|
581
|
+
inject = false,
|
|
582
|
+
styleId = 'atomix-theme-variables',
|
|
583
|
+
prefix = 'atomix',
|
|
584
|
+
} = options;
|
|
585
|
+
|
|
586
|
+
const variables: Record<string, string> = {};
|
|
587
|
+
|
|
588
|
+
// Generate variables from each theme section
|
|
589
|
+
Object.assign(variables, generatePaletteVariables(theme.palette, prefix));
|
|
590
|
+
Object.assign(variables, generateTypographyVariables(theme.typography, prefix));
|
|
591
|
+
Object.assign(variables, generateShadowVariables(theme.shadows, prefix));
|
|
592
|
+
Object.assign(variables, generateTransitionVariables(theme.transitions, prefix));
|
|
593
|
+
Object.assign(variables, generateZIndexVariables(theme.zIndex, prefix));
|
|
594
|
+
Object.assign(variables, generateBreakpointVariables(theme.breakpoints, prefix));
|
|
595
|
+
Object.assign(variables, generateSpacingVariables(theme.spacing, prefix));
|
|
596
|
+
Object.assign(variables, generateBorderVariables(theme.palette, prefix));
|
|
597
|
+
Object.assign(variables, generateBorderRadiusVariables(theme.borderRadius, prefix));
|
|
598
|
+
Object.assign(variables, generateFocusRingVariables(theme.palette, prefix));
|
|
599
|
+
|
|
600
|
+
// Add custom properties if present
|
|
601
|
+
if (theme.custom && Object.keys(theme.custom).length > 0) {
|
|
602
|
+
const customVars = flattenObject(theme.custom, `${prefix}-custom`);
|
|
603
|
+
Object.assign(variables, customVars);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Convert to CSS string
|
|
607
|
+
const cssVariables = Object.entries(variables)
|
|
608
|
+
.map(([key, value]) => ` --${key}: ${value};`)
|
|
609
|
+
.join('\n');
|
|
610
|
+
|
|
611
|
+
const css = `${selector} {\n${cssVariables}\n}`;
|
|
612
|
+
|
|
613
|
+
// Inject into DOM if requested
|
|
614
|
+
if (inject && isBrowser()) {
|
|
615
|
+
injectCSS(css, styleId);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return css;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Inject CSS into the DOM
|
|
623
|
+
*
|
|
624
|
+
* @param css - CSS string to inject
|
|
625
|
+
* @param styleId - ID for the style element
|
|
626
|
+
*/
|
|
627
|
+
export function injectCSS(css: string, styleId: string = 'atomix-theme-variables'): void {
|
|
628
|
+
if (!isBrowser()) return;
|
|
629
|
+
|
|
630
|
+
let styleElement = document.getElementById(styleId) as HTMLStyleElement | null;
|
|
631
|
+
|
|
632
|
+
if (!styleElement) {
|
|
633
|
+
styleElement = document.createElement('style');
|
|
634
|
+
styleElement.id = styleId;
|
|
635
|
+
styleElement.setAttribute('data-atomix-theme-vars', 'true');
|
|
636
|
+
document.head.appendChild(styleElement);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
styleElement.textContent = css;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Remove injected CSS from the DOM
|
|
644
|
+
*
|
|
645
|
+
* @param styleId - ID of the style element to remove
|
|
646
|
+
*/
|
|
647
|
+
export function removeInjectedCSS(styleId: string = 'atomix-theme-variables'): void {
|
|
648
|
+
if (!isBrowser()) return;
|
|
649
|
+
|
|
650
|
+
const styleElement = document.getElementById(styleId);
|
|
651
|
+
if (styleElement) {
|
|
652
|
+
styleElement.remove();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Generate CSS variables for a specific theme section
|
|
658
|
+
*
|
|
659
|
+
* @param theme - Theme object
|
|
660
|
+
* @param section - Theme section to generate variables for
|
|
661
|
+
* @param options - Generation options
|
|
662
|
+
* @returns CSS string with custom properties for the section
|
|
663
|
+
*/
|
|
664
|
+
export function generateSectionVariables(
|
|
665
|
+
theme: Theme,
|
|
666
|
+
section: 'palette' | 'typography' | 'shadows' | 'transitions' | 'zIndex' | 'breakpoints' | 'spacing' | 'borders' | 'borderRadius' | 'focusRing',
|
|
667
|
+
options: GenerateCSSVariablesOptions = {}
|
|
668
|
+
): string {
|
|
669
|
+
const { selector = ':root', prefix = 'atomix' } = options;
|
|
670
|
+
|
|
671
|
+
let variables: Record<string, string> = {};
|
|
672
|
+
|
|
673
|
+
switch (section) {
|
|
674
|
+
case 'palette':
|
|
675
|
+
variables = generatePaletteVariables(theme.palette, prefix);
|
|
676
|
+
break;
|
|
677
|
+
case 'typography':
|
|
678
|
+
variables = generateTypographyVariables(theme.typography, prefix);
|
|
679
|
+
break;
|
|
680
|
+
case 'shadows':
|
|
681
|
+
variables = generateShadowVariables(theme.shadows, prefix);
|
|
682
|
+
break;
|
|
683
|
+
case 'transitions':
|
|
684
|
+
variables = generateTransitionVariables(theme.transitions, prefix);
|
|
685
|
+
break;
|
|
686
|
+
case 'zIndex':
|
|
687
|
+
variables = generateZIndexVariables(theme.zIndex, prefix);
|
|
688
|
+
break;
|
|
689
|
+
case 'breakpoints':
|
|
690
|
+
variables = generateBreakpointVariables(theme.breakpoints, prefix);
|
|
691
|
+
break;
|
|
692
|
+
case 'spacing':
|
|
693
|
+
variables = generateSpacingVariables(theme.spacing, prefix);
|
|
694
|
+
break;
|
|
695
|
+
case 'borders':
|
|
696
|
+
variables = generateBorderVariables(theme.palette, prefix);
|
|
697
|
+
break;
|
|
698
|
+
case 'borderRadius':
|
|
699
|
+
variables = generateBorderRadiusVariables(theme.borderRadius, prefix);
|
|
700
|
+
break;
|
|
701
|
+
case 'focusRing':
|
|
702
|
+
variables = generateFocusRingVariables(theme.palette, prefix);
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const cssVariables = Object.entries(variables)
|
|
707
|
+
.map(([key, value]) => ` --${key}: ${value};`)
|
|
708
|
+
.join('\n');
|
|
709
|
+
|
|
710
|
+
return `${selector} {\n${cssVariables}\n}`;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export default generateCSSVariables;
|