@shohojdhara/atomix 0.3.12 → 0.3.14
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 +19 -0
- package/README.md +2 -0
- package/dist/atomix.css +101 -88
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +5 -15258
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +1 -1
- package/dist/charts.js +17 -19
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +41 -11
- package/dist/core.js +55 -41
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +28 -11
- package/dist/forms.js +25 -24
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +32 -25
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +122 -46
- package/dist/index.esm.js +865 -200
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +870 -204
- 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 +27 -2
- package/dist/theme.js +721 -108
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/scripts/atomix-cli.js +610 -1111
- package/scripts/cli/component-generator.js +610 -0
- package/scripts/cli/documentation-sync.js +542 -0
- package/scripts/cli/interactive-init.js +84 -288
- package/scripts/cli/mappings.js +211 -0
- package/scripts/cli/migration-tools.js +95 -288
- package/scripts/cli/template-manager.js +107 -0
- package/scripts/cli/templates/README.md +123 -0
- package/scripts/cli/templates/composable-templates.js +149 -0
- package/scripts/cli/templates/config-templates.js +126 -0
- package/scripts/cli/templates/index.js +95 -0
- package/scripts/cli/templates/project-templates.js +214 -0
- package/scripts/cli/templates/react-templates.js +261 -0
- package/scripts/cli/templates/scss-templates.js +156 -0
- package/scripts/cli/templates/storybook-templates.js +236 -0
- package/scripts/cli/templates/testing-templates.js +45 -0
- package/scripts/cli/templates/token-templates.js +447 -0
- package/scripts/cli/templates/types-templates.js +133 -0
- package/scripts/cli/templates-original-backup.js +1655 -0
- package/scripts/cli/templates.js +35 -0
- package/scripts/cli/templates_backup.js +684 -0
- package/scripts/cli/theme-bridge.js +20 -14
- package/scripts/cli/token-manager.js +150 -77
- package/scripts/cli/utils.js +37 -25
- package/src/components/Accordion/Accordion.stories.tsx +5 -5
- package/src/components/Accordion/Accordion.test.tsx +57 -0
- package/src/components/Accordion/Accordion.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +41 -44
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +37 -37
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -51
- package/src/components/Avatar/Avatar.stories.tsx +26 -26
- package/src/components/Badge/Badge.stories.tsx +31 -31
- package/src/components/Badge/Badge.test.tsx +51 -0
- package/src/components/Badge/Badge.tsx +20 -1
- package/src/components/Block/Block.stories.tsx +5 -5
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/Button/Button.stories.tsx +13 -13
- package/src/components/Button/Button.tsx +4 -4
- package/src/components/Button/ButtonGroup.stories.tsx +2 -2
- package/src/components/Button/README.md +5 -0
- package/src/components/Callout/Callout.stories.tsx +11 -11
- package/src/components/Callout/Callout.test.tsx +10 -10
- package/src/components/Callout/Callout.tsx +7 -7
- package/src/components/Callout/README.md +9 -8
- package/src/components/Card/Card.tsx +2 -2
- package/src/components/Chart/Chart.stories.tsx +6 -6
- package/src/components/Chart/Chart.tsx +1 -1
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +14 -12
- package/src/components/DatePicker/DatePicker.stories.tsx +6 -6
- package/src/components/Dropdown/Dropdown.stories.tsx +4 -4
- package/src/components/Form/Checkbox.stories.tsx +3 -3
- package/src/components/Form/Checkbox.tsx +4 -2
- package/src/components/Form/Form.stories.tsx +3 -3
- package/src/components/Form/FormGroup.stories.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +28 -16
- package/src/components/Form/Input.test.tsx +59 -0
- package/src/components/Form/Input.tsx +97 -95
- package/src/components/Form/Radio.stories.tsx +94 -94
- package/src/components/Form/Radio.tsx +2 -2
- package/src/components/Form/Select.stories.tsx +4 -4
- package/src/components/Form/Select.tsx +2 -2
- package/src/components/Form/Textarea.stories.tsx +22 -7
- package/src/components/Form/Textarea.test.tsx +45 -0
- package/src/components/Form/Textarea.tsx +88 -86
- package/src/components/List/List.stories.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +4 -4
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +5 -5
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/README.md +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +5 -2
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -10
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +1 -1
- package/src/components/Progress/Progress.tsx +46 -46
- package/src/components/Rating/Rating.stories.tsx +4 -4
- package/src/components/Rating/Rating.tsx +8 -8
- package/src/components/Slider/Slider.stories.tsx +63 -63
- package/src/components/Spinner/Spinner.stories.tsx +2 -2
- package/src/components/Spinner/Spinner.test.tsx +35 -0
- package/src/components/Spinner/Spinner.tsx +9 -2
- package/src/components/Testimonial/Testimonial.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.stories.tsx +32 -9
- package/src/components/Toggle/Toggle.test.tsx +91 -0
- package/src/components/Toggle/Toggle.tsx +44 -27
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/layouts/Grid/Grid.stories.tsx +49 -49
- package/src/layouts/MasonryGrid/MasonryGrid.stories.tsx +2 -2
- package/src/lib/composables/useAccordion.ts +12 -3
- package/src/lib/composables/useBreadcrumb.ts +2 -2
- package/src/lib/composables/useCallout.ts +7 -7
- package/src/lib/composables/useNavbar.ts +1 -1
- package/src/lib/constants/components.ts +1 -1
- package/src/lib/storybook/InteractiveDemo.tsx +113 -0
- package/src/lib/storybook/PreviewContainer.tsx +36 -0
- package/src/lib/storybook/VariantsGrid.tsx +21 -0
- package/src/lib/storybook/index.ts +3 -0
- package/src/lib/theme/core/createThemeObject.ts +9 -5
- package/src/lib/theme/devtools/CLI.ts +155 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +213 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +566 -0
- package/src/lib/theme/devtools/LiveEditor.tsx +2 -1
- package/src/lib/theme/devtools/index.ts +3 -0
- package/src/lib/theme/errors/errors.ts +8 -0
- package/src/lib/theme/runtime/ThemeProvider.tsx +117 -57
- package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +305 -0
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +588 -0
- package/src/lib/theme/utils/__tests__/themeValidation.test.ts +264 -0
- package/src/lib/theme/utils/index.ts +1 -0
- package/src/lib/theme/utils/themeValidation.ts +501 -0
- package/src/lib/theme-tools.ts +32 -3
- package/src/lib/types/components.ts +81 -26
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +14 -15
- package/src/styles/06-components/_components.callout.scss +29 -33
- package/src/styles/06-components/_index.scss +1 -1
- package/src/styles/99-utilities/_utilities.display.scss +14 -3
- package/src/styles/99-utilities/_utilities.flex.scss +10 -10
- package/src/styles/99-utilities/_utilities.text.scss +28 -8
- package/scripts/cli/__tests__/cli-commands.test.js +0 -204
- package/scripts/cli/__tests__/utils.test.js +0 -201
- package/scripts/cli/__tests__/vitest.config.js +0 -26
package/dist/theme.js
CHANGED
|
@@ -406,6 +406,14 @@ import { createContext, useRef, useEffect, useCallback, useMemo, useState, useCo
|
|
|
406
406
|
ThemeErrorCode.INVALID_THEME_NAME = "INVALID_THEME_NAME",
|
|
407
407
|
/** CSS injection failed */
|
|
408
408
|
ThemeErrorCode.CSS_INJECTION_FAILED = "CSS_INJECTION_FAILED",
|
|
409
|
+
/** Invalid color format */
|
|
410
|
+
ThemeErrorCode.INVALID_COLOR_FORMAT = "INVALID_COLOR_FORMAT",
|
|
411
|
+
/** Missing required token */
|
|
412
|
+
ThemeErrorCode.MISSING_REQUIRED_TOKEN = "MISSING_REQUIRED_TOKEN",
|
|
413
|
+
/** Accessibility contrast violation */
|
|
414
|
+
ThemeErrorCode.CONTRAST_VIOLATION = "CONTRAST_VIOLATION",
|
|
415
|
+
/** Invalid token type */
|
|
416
|
+
ThemeErrorCode.INVALID_TOKEN_TYPE = "INVALID_TOKEN_TYPE",
|
|
409
417
|
/** Unknown error */
|
|
410
418
|
ThemeErrorCode.UNKNOWN_ERROR = "UNKNOWN_ERROR";
|
|
411
419
|
}(ThemeErrorCode || (ThemeErrorCode = {}));
|
|
@@ -707,29 +715,6 @@ class ThemeLogger {
|
|
|
707
715
|
return Object.keys(registry).length;
|
|
708
716
|
}
|
|
709
717
|
|
|
710
|
-
/**
|
|
711
|
-
* Core Theme Engine
|
|
712
|
-
*
|
|
713
|
-
* Core theme creation, composition, and registry functionality
|
|
714
|
-
*/ const index = Object.freeze( Object.defineProperty({
|
|
715
|
-
__proto__: null,
|
|
716
|
-
clearThemes: clearThemes,
|
|
717
|
-
createTheme: createTheme,
|
|
718
|
-
createThemeRegistry: createThemeRegistry,
|
|
719
|
-
deepMerge: deepMerge,
|
|
720
|
-
extendTheme: extendTheme,
|
|
721
|
-
getAllThemes: getAllThemes,
|
|
722
|
-
getTheme: getTheme,
|
|
723
|
-
getThemeCount: getThemeCount,
|
|
724
|
-
getThemeIds: getThemeIds,
|
|
725
|
-
hasTheme: hasTheme,
|
|
726
|
-
mergeTheme: mergeTheme,
|
|
727
|
-
registerTheme: registerTheme,
|
|
728
|
-
unregisterTheme: unregisterTheme
|
|
729
|
-
}, Symbol.toStringTag, {
|
|
730
|
-
value: "Module"
|
|
731
|
-
}));
|
|
732
|
-
|
|
733
718
|
/**
|
|
734
719
|
* CSS Injection Utilities
|
|
735
720
|
*
|
|
@@ -1276,7 +1261,7 @@ const isBrowser = () => "undefined" != typeof window && "undefined" != typeof do
|
|
|
1276
1261
|
/**
|
|
1277
1262
|
* Convert hex color to RGB object
|
|
1278
1263
|
*/
|
|
1279
|
-
function hexToRgb(hex) {
|
|
1264
|
+
function hexToRgb$1(hex) {
|
|
1280
1265
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
1281
1266
|
return result ? {
|
|
1282
1267
|
r: parseInt(result[1], 16),
|
|
@@ -1296,7 +1281,7 @@ function hexToRgb(hex) {
|
|
|
1296
1281
|
* Calculate relative luminance of a color
|
|
1297
1282
|
* Used for determining contrast ratios
|
|
1298
1283
|
*/ function getLuminance(color) {
|
|
1299
|
-
const rgb = hexToRgb(color);
|
|
1284
|
+
const rgb = hexToRgb$1(color);
|
|
1300
1285
|
if (!rgb) return 0;
|
|
1301
1286
|
const {r: r, g: g, b: b} = rgb, [rs, gs, bs] = [ r ?? 0, g ?? 0, b ?? 0 ].map((c => {
|
|
1302
1287
|
const val = c / 255;
|
|
@@ -1326,7 +1311,7 @@ function hexToRgb(hex) {
|
|
|
1326
1311
|
* @param amount - Amount to lighten (0-1), default 0.2
|
|
1327
1312
|
* @returns Lightened hex color
|
|
1328
1313
|
*/ function lighten(color, amount = .2) {
|
|
1329
|
-
const rgb = hexToRgb(color);
|
|
1314
|
+
const rgb = hexToRgb$1(color);
|
|
1330
1315
|
if (!rgb) return color;
|
|
1331
1316
|
const {r: r, g: g, b: b} = rgb, lightenValue = val => Math.min(255, Math.round(val + (255 - val) * amount));
|
|
1332
1317
|
return rgbToHex(lightenValue(r), lightenValue(g), lightenValue(b));
|
|
@@ -1339,7 +1324,7 @@ function hexToRgb(hex) {
|
|
|
1339
1324
|
* @param amount - Amount to darken (0-1), default 0.2
|
|
1340
1325
|
* @returns Darkened hex color
|
|
1341
1326
|
*/ function darken(color, amount = .2) {
|
|
1342
|
-
const rgb = hexToRgb(color);
|
|
1327
|
+
const rgb = hexToRgb$1(color);
|
|
1343
1328
|
if (!rgb) return color;
|
|
1344
1329
|
const {r: r, g: g, b: b} = rgb, darkenValue = val => Math.max(0, Math.round(val * (1 - amount)));
|
|
1345
1330
|
return rgbToHex(darkenValue(r), darkenValue(g), darkenValue(b));
|
|
@@ -1352,7 +1337,7 @@ function hexToRgb(hex) {
|
|
|
1352
1337
|
* @param opacity - Opacity value (0-1)
|
|
1353
1338
|
* @returns RGBA color string
|
|
1354
1339
|
*/ function alpha(color, opacity) {
|
|
1355
|
-
const rgb = hexToRgb(color);
|
|
1340
|
+
const rgb = hexToRgb$1(color);
|
|
1356
1341
|
if (!rgb) return color;
|
|
1357
1342
|
const {r: r, g: g, b: b} = rgb;
|
|
1358
1343
|
return `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, opacity))})`;
|
|
@@ -1371,7 +1356,7 @@ function hexToRgb(hex) {
|
|
|
1371
1356
|
*/
|
|
1372
1357
|
function generateColorScale(baseColor, prefix, colorName) {
|
|
1373
1358
|
const vars = {};
|
|
1374
|
-
if (!hexToRgb(baseColor)) return vars;
|
|
1359
|
+
if (!hexToRgb$1(baseColor)) return vars;
|
|
1375
1360
|
// Generate 10-step scale
|
|
1376
1361
|
// Steps 1-5: lighter variations
|
|
1377
1362
|
// Step 6: base color
|
|
@@ -1417,7 +1402,7 @@ function generateCSSVariables(theme, options = {}) {
|
|
|
1417
1402
|
// Main color (flat structure, matches SCSS: --atomix-primary)
|
|
1418
1403
|
vars[`${prefix}-${key}`] = color.main;
|
|
1419
1404
|
// Generate RGB for transparency support (matches SCSS: --atomix-primary-rgb)
|
|
1420
|
-
const rgb = hexToRgb(color.main);
|
|
1405
|
+
const rgb = hexToRgb$1(color.main);
|
|
1421
1406
|
// Generate full color scale (1-10) - matches SCSS: --atomix-primary-1 through --atomix-primary-10
|
|
1422
1407
|
// Only for primary, secondary, error, warning, info, success (not for light/dark)
|
|
1423
1408
|
if (rgb && (vars[`${prefix}-${key}-rgb`] = `${rgb.r}, ${rgb.g}, ${rgb.b}`), "light" !== key && "dark" !== key) {
|
|
@@ -1500,11 +1485,11 @@ function generateCSSVariables(theme, options = {}) {
|
|
|
1500
1485
|
// Heading color (defaults to text primary) - matches SCSS: --atomix-heading-color
|
|
1501
1486
|
palette.text && (vars[`${prefix}-heading-color`] = palette.text.primary), palette.primary) {
|
|
1502
1487
|
vars[`${prefix}-link-color`] = palette.primary.main;
|
|
1503
|
-
const linkRgb = hexToRgb(palette.primary.main);
|
|
1488
|
+
const linkRgb = hexToRgb$1(palette.primary.main);
|
|
1504
1489
|
linkRgb && (vars[`${prefix}-link-color-rgb`] = `${linkRgb.r}, ${linkRgb.g}, ${linkRgb.b}`),
|
|
1505
1490
|
// Link hover color (slightly darker)
|
|
1506
1491
|
vars[`${prefix}-link-hover-color`] = palette.primary.dark || darken(palette.primary.main, .1);
|
|
1507
|
-
const linkHoverRgb = hexToRgb(palette.primary.dark || darken(palette.primary.main, .1));
|
|
1492
|
+
const linkHoverRgb = hexToRgb$1(palette.primary.dark || darken(palette.primary.main, .1));
|
|
1508
1493
|
linkHoverRgb && (vars[`${prefix}-link-hover-color-rgb`] = `${linkHoverRgb.r}, ${linkHoverRgb.g}, ${linkHoverRgb.b}`),
|
|
1509
1494
|
// Link decoration (default: none, matching tokens list)
|
|
1510
1495
|
vars[`${prefix}-link-decoration`] = "none";
|
|
@@ -1962,17 +1947,292 @@ function generateClassName(block, element, modifiers) {
|
|
|
1962
1947
|
ThemeContext.displayName = "ThemeContext";
|
|
1963
1948
|
|
|
1964
1949
|
/**
|
|
1965
|
-
* Theme
|
|
1950
|
+
* Theme Validation Utilities
|
|
1966
1951
|
*
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
* - String-based themes (CSS files)
|
|
1970
|
-
* - DesignTokens (dynamic themes)
|
|
1971
|
-
* - Persistence via localStorage
|
|
1972
|
-
*
|
|
1973
|
-
* Falls back to 'default' theme if no configuration is found.
|
|
1952
|
+
* Comprehensive validation utilities for DesignTokens objects.
|
|
1953
|
+
* Includes color format validation, accessibility checks, and required properties verification.
|
|
1974
1954
|
*/
|
|
1975
|
-
const
|
|
1955
|
+
const logger$1 = getLogger(), COLOR_PATTERNS = {
|
|
1956
|
+
/** Hex color: #RGB, #RRGGBB, #RRGGBBAA */
|
|
1957
|
+
hex: /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/,
|
|
1958
|
+
/** RGB color: rgb(r, g, b) or rgb(r, g, b, a) */
|
|
1959
|
+
rgb: /^rgb\(\s*(\d{1,3}(?:\.\d+)?)\s*,\s*(\d{1,3}(?:\.\d+)?)\s*,\s*(\d{1,3}(?:\.\d+)?)\s*(?:,\s*([01]?\.?\d+))?\s*\)$/,
|
|
1960
|
+
/** RGBA color: rgba(r, g, b, a) */
|
|
1961
|
+
rgba: /^rgba\(\s*(\d{1,3}(?:\.\d+)?)\s*,\s*(\d{1,3}(?:\.\d+)?)\s*,\s*(\d{1,3}(?:\.\d+)?)\s*,\s*([01]?\.?\d+)\s*\)$/,
|
|
1962
|
+
/** HSL color: hsl(h, s%, l%) */
|
|
1963
|
+
hsl: /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/,
|
|
1964
|
+
/** HSLA color: hsla(h, s%, l%, a) */
|
|
1965
|
+
hsla: /^hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*([01]?\.?\d+)\s*\)$/
|
|
1966
|
+
}, REQUIRED_COLOR_TOKENS = [ "primary", "secondary", "success", "info", "warning", "error", "light", "dark" ], REQUIRED_TEXT_EMPHASIS_TOKENS = [ "primary-text-emphasis", "secondary-text-emphasis", "tertiary-text-emphasis", "disabled-text-emphasis" ], ACCESSIBILITY_CHECKS = [ {
|
|
1967
|
+
text: "primary-text-emphasis",
|
|
1968
|
+
background: "primary-bg-subtle",
|
|
1969
|
+
name: "Primary text on subtle background"
|
|
1970
|
+
}, {
|
|
1971
|
+
text: "secondary-text-emphasis",
|
|
1972
|
+
background: "secondary-bg-subtle",
|
|
1973
|
+
name: "Secondary text on subtle background"
|
|
1974
|
+
}, {
|
|
1975
|
+
text: "error-text-emphasis",
|
|
1976
|
+
background: "error-bg-subtle",
|
|
1977
|
+
name: "Error text on subtle background"
|
|
1978
|
+
}, {
|
|
1979
|
+
text: "success-text-emphasis",
|
|
1980
|
+
background: "success-bg-subtle",
|
|
1981
|
+
name: "Success text on subtle background"
|
|
1982
|
+
} ];
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Color format validation patterns
|
|
1986
|
+
*/
|
|
1987
|
+
/**
|
|
1988
|
+
* Parse RGB/RGBA color string to RGB values
|
|
1989
|
+
*/
|
|
1990
|
+
function rgbToRgb(rgb) {
|
|
1991
|
+
const match = rgb.match(COLOR_PATTERNS.rgb) || rgb.match(COLOR_PATTERNS.rgba);
|
|
1992
|
+
if (!match) return null;
|
|
1993
|
+
const r = parseInt(match[1] ?? "0", 10), g = parseInt(match[2] ?? "0", 10), b = parseInt(match[3] ?? "0", 10);
|
|
1994
|
+
// Validate RGB ranges
|
|
1995
|
+
return r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 ? null : {
|
|
1996
|
+
r: r,
|
|
1997
|
+
g: g,
|
|
1998
|
+
b: b
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/**
|
|
2003
|
+
* Parse HSL/HSLA color string to RGB values
|
|
2004
|
+
*/ function hslToRgb(hsl) {
|
|
2005
|
+
const match = hsl.match(COLOR_PATTERNS.hsl) || hsl.match(COLOR_PATTERNS.hsla);
|
|
2006
|
+
if (!match) return null;
|
|
2007
|
+
let h = parseInt(match[1] ?? "0", 10) / 360, s = parseInt(match[2] ?? "0", 10) / 100, l = parseInt(match[3] ?? "0", 10) / 100;
|
|
2008
|
+
// Validate HSL ranges
|
|
2009
|
+
if (h < 0 || h > 1 || s < 0 || s > 1 || l < 0 || l > 1) return null;
|
|
2010
|
+
const hue2rgb = (p, q, t) => (t < 0 && (t += 1), t > 1 && (t -= 1), t < 1 / 6 ? p + 6 * (q - p) * t : t < .5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p);
|
|
2011
|
+
let r, g, b;
|
|
2012
|
+
if (0 === s) r = g = b = l; // achromatic
|
|
2013
|
+
else {
|
|
2014
|
+
const q = l < .5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
2015
|
+
r = hue2rgb(p, q, h + 1 / 3), g = hue2rgb(p, q, h), b = hue2rgb(p, q, h - 1 / 3);
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
r: Math.round(255 * r),
|
|
2019
|
+
g: Math.round(255 * g),
|
|
2020
|
+
b: Math.round(255 * b)
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
/**
|
|
2025
|
+
* Convert any valid color format to RGB values
|
|
2026
|
+
*/ function colorToRgb(color) {
|
|
2027
|
+
// Try hex first
|
|
2028
|
+
return COLOR_PATTERNS.hex.test(color) ?
|
|
2029
|
+
/**
|
|
2030
|
+
* Convert hex color to RGB values
|
|
2031
|
+
*/
|
|
2032
|
+
function(hex) {
|
|
2033
|
+
const result = COLOR_PATTERNS.hex.exec(hex);
|
|
2034
|
+
if (!result || !result[1]) return null;
|
|
2035
|
+
const digits = result[1];
|
|
2036
|
+
let r, g, b;
|
|
2037
|
+
switch (digits.length) {
|
|
2038
|
+
case 3:
|
|
2039
|
+
case 4:
|
|
2040
|
+
// #RGBA (ignore alpha)
|
|
2041
|
+
r = parseInt((digits[0] ?? "0") + (digits[0] ?? "0"), 16), g = parseInt((digits[1] ?? "0") + (digits[1] ?? "0"), 16),
|
|
2042
|
+
b = parseInt((digits[2] ?? "0") + (digits[2] ?? "0"), 16);
|
|
2043
|
+
break;
|
|
2044
|
+
|
|
2045
|
+
case 6:
|
|
2046
|
+
case 8:
|
|
2047
|
+
// #RRGGBBAA (ignore alpha)
|
|
2048
|
+
r = parseInt(digits.slice(0, 2), 16), g = parseInt(digits.slice(2, 4), 16), b = parseInt(digits.slice(4, 6), 16);
|
|
2049
|
+
break;
|
|
2050
|
+
|
|
2051
|
+
default:
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
return {
|
|
2055
|
+
r: r,
|
|
2056
|
+
g: g,
|
|
2057
|
+
b: b
|
|
2058
|
+
};
|
|
2059
|
+
}(color) :
|
|
2060
|
+
// Try RGB/RGBA
|
|
2061
|
+
color.startsWith("rgb") ? rgbToRgb(color) :
|
|
2062
|
+
// Try HSL/HSLA
|
|
2063
|
+
color.startsWith("hsl") ? hslToRgb(color) : null;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
/**
|
|
2067
|
+
* Validate that a color string is in a valid format (hex, rgb, hsl, etc.)
|
|
2068
|
+
* Includes validation of value ranges for RGB and HSL
|
|
2069
|
+
*/
|
|
2070
|
+
/**
|
|
2071
|
+
* Calculate relative luminance of a color
|
|
2072
|
+
* Based on WCAG guidelines: https://www.w3.org/TR/WCAG20-TECHS/G17.html
|
|
2073
|
+
*/
|
|
2074
|
+
function getRelativeLuminance(r, g, b) {
|
|
2075
|
+
const normalize = val => (val /= 255) <= .03928 ? val / 12.92 : Math.pow((val + .055) / 1.055, 2.4);
|
|
2076
|
+
return .2126 * normalize(r) + .7152 * normalize(g) + .0722 * normalize(b);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* Calculate contrast ratio between two colors (supports multiple formats)
|
|
2081
|
+
*/
|
|
2082
|
+
/**
|
|
2083
|
+
* Check if contrast ratio meets WCAG AA standards
|
|
2084
|
+
* AA requires 4.5:1 for normal text, 3:1 for large text
|
|
2085
|
+
*/
|
|
2086
|
+
function validateContrastRatio(foreground, background, isLargeText = !1) {
|
|
2087
|
+
const ratio = function(color1, color2) {
|
|
2088
|
+
const rgb1 = colorToRgb(color1), rgb2 = colorToRgb(color2);
|
|
2089
|
+
if (!rgb1 || !rgb2) return null;
|
|
2090
|
+
const lum1 = getRelativeLuminance(rgb1.r, rgb1.g, rgb1.b), lum2 = getRelativeLuminance(rgb2.r, rgb2.g, rgb2.b);
|
|
2091
|
+
return (Math.max(lum1, lum2) + .05) / (Math.min(lum1, lum2) + .05);
|
|
2092
|
+
}(foreground, background);
|
|
2093
|
+
if (null === ratio) return {
|
|
2094
|
+
valid: !1,
|
|
2095
|
+
ratio: 0,
|
|
2096
|
+
requiredRatio: isLargeText ? 3 : 4.5
|
|
2097
|
+
};
|
|
2098
|
+
const requiredRatio = isLargeText ? 3 : 4.5;
|
|
2099
|
+
return {
|
|
2100
|
+
valid: ratio >= requiredRatio,
|
|
2101
|
+
ratio: ratio,
|
|
2102
|
+
requiredRatio: requiredRatio
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
/**
|
|
2107
|
+
* Validate all color formats in DesignTokens
|
|
2108
|
+
*/
|
|
2109
|
+
/**
|
|
2110
|
+
* Comprehensive validation of DesignTokens
|
|
2111
|
+
*/
|
|
2112
|
+
function validateDesignTokens(tokens, options = {}) {
|
|
2113
|
+
const results = [];
|
|
2114
|
+
options.skipRequiredTokens || results.push(
|
|
2115
|
+
/**
|
|
2116
|
+
* Validate that all required tokens are present
|
|
2117
|
+
*/
|
|
2118
|
+
function(tokens) {
|
|
2119
|
+
const errors = [], warnings = [];
|
|
2120
|
+
// Check required color tokens
|
|
2121
|
+
for (const token of REQUIRED_COLOR_TOKENS) token in tokens || errors.push(`Required color token '${token}' is missing`);
|
|
2122
|
+
// Check required text emphasis tokens
|
|
2123
|
+
for (const token of REQUIRED_TEXT_EMPHASIS_TOKENS) token in tokens || errors.push(`Required text emphasis token '${token}' is missing`);
|
|
2124
|
+
// Check for RGB versions of base colors
|
|
2125
|
+
for (const token of REQUIRED_COLOR_TOKENS) {
|
|
2126
|
+
const rgbToken = `${token}-rgb`;
|
|
2127
|
+
rgbToken in tokens || warnings.push(`RGB version of '${token}' token '${rgbToken}' is missing`);
|
|
2128
|
+
}
|
|
2129
|
+
return {
|
|
2130
|
+
valid: 0 === errors.length,
|
|
2131
|
+
errors: errors,
|
|
2132
|
+
warnings: warnings
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Validate accessibility contrast ratios
|
|
2137
|
+
*/ (tokens)), options.skipColorValidation || results.push(function(tokens) {
|
|
2138
|
+
const errors = [], colorTokenKeys = new Set([
|
|
2139
|
+
// Base colors
|
|
2140
|
+
"primary", "secondary", "success", "info", "warning", "error", "light", "dark",
|
|
2141
|
+
// Text emphasis
|
|
2142
|
+
"primary-text-emphasis", "secondary-text-emphasis", "tertiary-text-emphasis", "disabled-text-emphasis", "invert-text-emphasis", "brand-text-emphasis", "error-text-emphasis", "success-text-emphasis", "warning-text-emphasis", "info-text-emphasis", "light-text-emphasis", "dark-text-emphasis",
|
|
2143
|
+
// Background subtle
|
|
2144
|
+
"primary-bg-subtle", "secondary-bg-subtle", "tertiary-bg-subtle", "invert-bg-subtle", "brand-bg-subtle", "error-bg-subtle", "success-bg-subtle", "warning-bg-subtle", "info-bg-subtle", "light-bg-subtle", "dark-bg-subtle",
|
|
2145
|
+
// Border subtle
|
|
2146
|
+
"primary-border-subtle", "secondary-border-subtle", "success-border-subtle", "error-border-subtle", "warning-border-subtle", "info-border-subtle", "brand-border-subtle", "light-border-subtle", "dark-border-subtle",
|
|
2147
|
+
// Hover states
|
|
2148
|
+
"primary-hover", "secondary-hover", "light-hover", "dark-hover", "error-hover", "success-hover", "warning-hover", "info-hover",
|
|
2149
|
+
// Colors from scales (primary, red, green, blue, yellow)
|
|
2150
|
+
...Array.from({
|
|
2151
|
+
length: 10
|
|
2152
|
+
}, ((_, i) => [ `primary-${i + 1}`, `red-${i + 1}`, `green-${i + 1}`, `blue-${i + 1}`, `yellow-${i + 1}` ])).flat(),
|
|
2153
|
+
// Gray scale
|
|
2154
|
+
...Array.from({
|
|
2155
|
+
length: 10
|
|
2156
|
+
}, ((_, i) => `gray-${i + 1}`)),
|
|
2157
|
+
// Body colors
|
|
2158
|
+
"body-color", "heading-color",
|
|
2159
|
+
// Link colors
|
|
2160
|
+
"link-color", "link-hover-color",
|
|
2161
|
+
// Highlight & code
|
|
2162
|
+
"highlight-bg", "code-color",
|
|
2163
|
+
// Border colors
|
|
2164
|
+
"border-color", "border-color-translucent",
|
|
2165
|
+
// Focus ring
|
|
2166
|
+
"focus-border-color",
|
|
2167
|
+
// Form validation
|
|
2168
|
+
"form-valid-color", "form-valid-border-color", "form-invalid-color", "form-invalid-border-color" ]);
|
|
2169
|
+
for (const key of colorTokenKeys) {
|
|
2170
|
+
if (!(key in tokens)) continue;
|
|
2171
|
+
// Skip if token not present
|
|
2172
|
+
const value = tokens[key];
|
|
2173
|
+
"string" == typeof value ? (color = value,
|
|
2174
|
+
// Check hex first (regex is sufficient)
|
|
2175
|
+
COLOR_PATTERNS.hex.test(color) || (
|
|
2176
|
+
// Check RGB/RGBA with value validation
|
|
2177
|
+
COLOR_PATTERNS.rgb.test(color) || COLOR_PATTERNS.rgba.test(color) ? null !== rgbToRgb(color) :
|
|
2178
|
+
// Check HSL/HSLA with value validation
|
|
2179
|
+
(COLOR_PATTERNS.hsl.test(color) || COLOR_PATTERNS.hsla.test(color)) && null !== hslToRgb(color)) || errors.push(`Token '${key}' has invalid color format: '${value}'`)) : errors.push(`Token '${key}' must be a string, got ${typeof value}`);
|
|
2180
|
+
}
|
|
2181
|
+
var color;
|
|
2182
|
+
return {
|
|
2183
|
+
valid: 0 === errors.length,
|
|
2184
|
+
errors: errors,
|
|
2185
|
+
warnings: []
|
|
2186
|
+
};
|
|
2187
|
+
}(tokens)), options.skipAccessibility || results.push(function(tokens) {
|
|
2188
|
+
const errors = [], warnings = [];
|
|
2189
|
+
for (const check of ACCESSIBILITY_CHECKS) {
|
|
2190
|
+
const textColor = tokens[check.text], bgColor = tokens[check.background];
|
|
2191
|
+
if (!textColor || !bgColor) {
|
|
2192
|
+
warnings.push(`Cannot validate contrast for ${check.name}: missing tokens`);
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const contrast = validateContrastRatio(textColor, bgColor);
|
|
2196
|
+
if (!contrast.valid) {
|
|
2197
|
+
const level = 3 === contrast.requiredRatio ? "large text (AA)" : "normal text (AA)";
|
|
2198
|
+
errors.push(`${check.name}: contrast ratio ${contrast.ratio.toFixed(2)}:1 is below required ${contrast.requiredRatio}:1 for ${level}`);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
return {
|
|
2202
|
+
valid: 0 === errors.length,
|
|
2203
|
+
errors: errors,
|
|
2204
|
+
warnings: warnings
|
|
2205
|
+
};
|
|
2206
|
+
}(tokens));
|
|
2207
|
+
const allErrors = results.flatMap((r => r.errors)), allWarnings = results.flatMap((r => r.warnings)), valid = 0 === allErrors.length;
|
|
2208
|
+
// Log validation results
|
|
2209
|
+
return valid ? allWarnings.length > 0 ? logger$1.warn(`DesignTokens validation passed with ${allWarnings.length} warnings`, {
|
|
2210
|
+
warnings: allWarnings
|
|
2211
|
+
}) : logger$1.debug("DesignTokens validation passed") : logger$1.error("DesignTokens validation failed", new Error(`Validation failed with ${allErrors.length} errors and ${allWarnings.length} warnings`), {
|
|
2212
|
+
errors: allErrors,
|
|
2213
|
+
warnings: allWarnings,
|
|
2214
|
+
tokenCount: Object.keys(tokens).length
|
|
2215
|
+
}), {
|
|
2216
|
+
valid: valid,
|
|
2217
|
+
errors: allErrors,
|
|
2218
|
+
warnings: allWarnings
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* Safely validate and merge partial tokens with defaults
|
|
2224
|
+
*/ function validateAndMergeTokens(partialTokens) {
|
|
2225
|
+
const merged = {
|
|
2226
|
+
...defaultTokens,
|
|
2227
|
+
...partialTokens
|
|
2228
|
+
};
|
|
2229
|
+
return {
|
|
2230
|
+
tokens: merged,
|
|
2231
|
+
validation: validateDesignTokens(merged)
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
const logger = getLogger(), ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes: themes = {}, basePath: basePath = "/themes", cdnPath: cdnPath = null, useMinified: useMinified = !1, storageKey: storageKey = "atomix-theme", dataAttribute: dataAttribute = "data-theme", enablePersistence: enablePersistence = !0, onThemeChange: onThemeChange, onError: onError}) => {
|
|
1976
2236
|
// Store callbacks in refs to avoid recreating when they change
|
|
1977
2237
|
const onThemeChangeRef = useRef(onThemeChange), onErrorRef = useRef(onError);
|
|
1978
2238
|
// Update ref when callback changes
|
|
@@ -1993,12 +2253,20 @@ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes:
|
|
|
1993
2253
|
// If defaultTheme is provided, use it
|
|
1994
2254
|
return null != defaultTheme ? defaultTheme : "default";
|
|
1995
2255
|
// Default fallback
|
|
1996
|
-
}), [ defaultTheme, enablePersistence, storageKey ]), [currentTheme, setCurrentTheme] = useState((() => "string" == typeof initialDefaultTheme ? initialDefaultTheme : "tokens-theme")), [activeTokens, setActiveTokens] = useState((() =>
|
|
1997
|
-
|
|
1998
|
-
|
|
2256
|
+
}), [ defaultTheme, enablePersistence, storageKey ]), [currentTheme, setCurrentTheme] = useState((() => "string" == typeof initialDefaultTheme ? initialDefaultTheme : "tokens-theme")), [activeTokens, setActiveTokens] = useState((() => {
|
|
2257
|
+
// If defaultTheme is DesignTokens, validate and store them
|
|
2258
|
+
if (defaultTheme && "string" != typeof defaultTheme) {
|
|
2259
|
+
const {tokens: tokens, validation: validation} = validateAndMergeTokens(defaultTheme);
|
|
2260
|
+
return validation.valid ? tokens : (logger.warn("Invalid default theme tokens, using defaults", {
|
|
2261
|
+
errors: validation.errors,
|
|
2262
|
+
warnings: validation.warnings
|
|
2263
|
+
}), createTokens({}));
|
|
2264
|
+
}
|
|
2265
|
+
return null;
|
|
2266
|
+
})), [isLoading, setIsLoading] = useState(!1), [error, setError] = useState(null), loadedThemesRef = useRef(new Set), themePromisesRef = useRef({}), abortControllerRef = useRef(null);
|
|
1999
2267
|
// Handle initial DesignTokens defaultTheme
|
|
2000
2268
|
useEffect((() => {
|
|
2001
|
-
defaultTheme && "string" != typeof defaultTheme && activeTokens && !isServer() && injectCSS$1(createTheme(
|
|
2269
|
+
defaultTheme && "string" != typeof defaultTheme && activeTokens && !isServer() && injectCSS$1(createTheme(activeTokens), "theme-tokens-theme");
|
|
2002
2270
|
}), [ defaultTheme, activeTokens ]), // Run when defaultTheme or activeTokens change
|
|
2003
2271
|
// Apply initial theme attributes to document element
|
|
2004
2272
|
useEffect((() => {
|
|
@@ -2028,19 +2296,42 @@ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes:
|
|
|
2028
2296
|
if ("string" != typeof theme) {
|
|
2029
2297
|
// Check if aborted before processing
|
|
2030
2298
|
if (abortController.signal.aborted) return;
|
|
2031
|
-
//
|
|
2032
|
-
const {
|
|
2299
|
+
// Validate and merge DesignTokens
|
|
2300
|
+
const {tokens: validatedTokens, validation: validation} = validateAndMergeTokens(theme);
|
|
2301
|
+
if (!validation.valid) {
|
|
2302
|
+
const errorMsg = `Invalid DesignTokens provided: ${validation.errors.join(", ")}`, validationError = new Error(errorMsg);
|
|
2303
|
+
// Default to true
|
|
2304
|
+
if (logger.error("Theme validation failed", validationError, {
|
|
2305
|
+
errors: validation.errors,
|
|
2306
|
+
warnings: validation.warnings
|
|
2307
|
+
}), !1 !== options?.fallbackOnError) {
|
|
2308
|
+
logger.warn("Falling back to default theme due to validation errors");
|
|
2309
|
+
// Use default tokens instead
|
|
2310
|
+
const {tokens: defaultTokens} = validateAndMergeTokens({}), css = createTheme(defaultTokens), themeId = "tokens-theme-fallback";
|
|
2311
|
+
// Check if aborted before state update
|
|
2312
|
+
if (abortController.signal.aborted) return;
|
|
2313
|
+
// Remove any previously loaded theme CSS
|
|
2314
|
+
return removeCSS(`theme-${currentTheme}`),
|
|
2315
|
+
// Inject new theme CSS
|
|
2316
|
+
injectCSS$1(css, `theme-${themeId}`),
|
|
2317
|
+
// Store default tokens
|
|
2318
|
+
setActiveTokens(defaultTokens), setCurrentTheme(themeId), handleThemeChange(defaultTokens),
|
|
2319
|
+
handleError(validationError, themeId), void setIsLoading(!1);
|
|
2320
|
+
}
|
|
2321
|
+
// No fallback, throw the error
|
|
2322
|
+
throw validationError;
|
|
2323
|
+
}
|
|
2324
|
+
// For valid DesignTokens, create CSS and inject it
|
|
2325
|
+
const css = createTheme(validatedTokens), themeId = "tokens-theme";
|
|
2033
2326
|
// Check if aborted after async operation
|
|
2034
2327
|
if (abortController.signal.aborted) return;
|
|
2035
2328
|
// Remove any previously loaded theme CSS
|
|
2036
|
-
|
|
2037
|
-
// Inject new theme CSS
|
|
2038
|
-
injectCSS$1(css, `theme-${themeId}`);
|
|
2039
|
-
// Store tokens for reference
|
|
2040
|
-
const fullTokens = createTokens(theme);
|
|
2329
|
+
// Store validated tokens for reference
|
|
2041
2330
|
// Check if aborted before state update
|
|
2042
|
-
|
|
2043
|
-
|
|
2331
|
+
if (removeCSS(`theme-${currentTheme}`),
|
|
2332
|
+
// Inject new theme CSS
|
|
2333
|
+
injectCSS$1(css, `theme-${themeId}`), abortController.signal.aborted) return;
|
|
2334
|
+
return setActiveTokens(validatedTokens), setCurrentTheme(themeId), handleThemeChange(validatedTokens),
|
|
2044
2335
|
void setIsLoading(!1);
|
|
2045
2336
|
}
|
|
2046
2337
|
// If it's a string theme name, load the associated CSS
|
|
@@ -2055,30 +2346,33 @@ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes:
|
|
|
2055
2346
|
// If previous load failed, continue with new load
|
|
2056
2347
|
}
|
|
2057
2348
|
// Load CSS theme
|
|
2058
|
-
const themeLoadPromise = new Promise((
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
if (!themes[theme]) throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
2063
|
-
{
|
|
2064
|
-
// Build CSS path using utility function
|
|
2065
|
-
const cssPath = buildThemePath(theme, basePath, useMinified, cdnPath);
|
|
2349
|
+
const themeLoadPromise = new Promise(((resolve, reject) => {
|
|
2350
|
+
// Start the async operation
|
|
2351
|
+
(async () => {
|
|
2352
|
+
try {
|
|
2066
2353
|
// Check if aborted
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2354
|
+
if (abortController.signal.aborted) return void resolve();
|
|
2355
|
+
if (!themes[theme]) throw new Error(`Theme metadata not found for theme: ${theme}`);
|
|
2356
|
+
{
|
|
2357
|
+
// Build CSS path using utility function
|
|
2358
|
+
const cssPath = buildThemePath(theme, basePath, useMinified, cdnPath);
|
|
2359
|
+
// Check if aborted
|
|
2360
|
+
if (abortController.signal.aborted) return void resolve();
|
|
2361
|
+
// Load CSS file (using loadThemeCSS from domUtils)
|
|
2362
|
+
const {loadThemeCSS: loadThemeCSS} = await Promise.resolve().then((() => domUtils));
|
|
2363
|
+
// Check if aborted after async operation
|
|
2364
|
+
if (await loadThemeCSS(cssPath, `theme-${theme}`), abortController.signal.aborted) return void resolve();
|
|
2365
|
+
// Remove any previously loaded theme CSS
|
|
2366
|
+
removeCSS(`theme-${String(currentTheme)}`), loadedThemesRef.current.add(theme),
|
|
2367
|
+
setCurrentTheme(theme), setActiveTokens(null), handleThemeChange(theme), resolve();
|
|
2368
|
+
}
|
|
2369
|
+
} catch (err) {
|
|
2370
|
+
// Don't reject if aborted
|
|
2371
|
+
if (abortController.signal.aborted) return void resolve();
|
|
2372
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2373
|
+
setError(error), handleError(error, String(theme)), reject(error);
|
|
2075
2374
|
}
|
|
2076
|
-
}
|
|
2077
|
-
// Don't reject if aborted
|
|
2078
|
-
if (abortController.signal.aborted) return void resolve();
|
|
2079
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
2080
|
-
setError(error), handleError(error, String(theme)), reject(error);
|
|
2081
|
-
}
|
|
2375
|
+
})();
|
|
2082
2376
|
}));
|
|
2083
2377
|
themePromisesRef.current[theme] = themeLoadPromise;
|
|
2084
2378
|
try {
|
|
@@ -2143,6 +2437,17 @@ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes:
|
|
|
2143
2437
|
});
|
|
2144
2438
|
};
|
|
2145
2439
|
|
|
2440
|
+
/**
|
|
2441
|
+
* Theme Provider
|
|
2442
|
+
*
|
|
2443
|
+
* React context provider for theme management with separated concerns.
|
|
2444
|
+
* Simplified version focusing on core functionality:
|
|
2445
|
+
* - String-based themes (CSS files)
|
|
2446
|
+
* - DesignTokens (dynamic themes)
|
|
2447
|
+
* - Persistence via localStorage
|
|
2448
|
+
*
|
|
2449
|
+
* Falls back to 'default' theme if no configuration is found.
|
|
2450
|
+
*/
|
|
2146
2451
|
/**
|
|
2147
2452
|
* useTheme Hook
|
|
2148
2453
|
*
|
|
@@ -2168,7 +2473,8 @@ const ThemeProvider = ({children: children, defaultTheme: defaultTheme, themes:
|
|
|
2168
2473
|
* );
|
|
2169
2474
|
* }
|
|
2170
2475
|
* ```
|
|
2171
|
-
*/
|
|
2476
|
+
*/
|
|
2477
|
+
function useTheme() {
|
|
2172
2478
|
const context = useContext(ThemeContext);
|
|
2173
2479
|
if (!context) throw new Error("useTheme must be used within a ThemeProvider");
|
|
2174
2480
|
return {
|
|
@@ -4241,8 +4547,8 @@ function createPaletteColor(color) {
|
|
|
4241
4547
|
* @returns Complete theme object
|
|
4242
4548
|
*/
|
|
4243
4549
|
function createThemeObject(...options) {
|
|
4244
|
-
// Merge all options
|
|
4245
|
-
const mergedOptions = _reduceInstanceProperty(options).call(options, ((acc, option) => deepMerge(acc, option)), {}), palette = {
|
|
4550
|
+
// Merge all options by spreading them into a single object
|
|
4551
|
+
const mergedOptions = _reduceInstanceProperty(options).call(options, ((acc, option) => deepMerge(acc, option || {})), {}), palette = {
|
|
4246
4552
|
primary: createPaletteColor(mergedOptions.palette?.primary || DEFAULT_PALETTE.primary),
|
|
4247
4553
|
secondary: createPaletteColor(mergedOptions.palette?.secondary || DEFAULT_PALETTE.secondary),
|
|
4248
4554
|
error: createPaletteColor(mergedOptions.palette?.error || DEFAULT_PALETTE.error),
|
|
@@ -4427,43 +4733,45 @@ function createThemeObject(...options) {
|
|
|
4427
4733
|
|
|
4428
4734
|
case "hsl":
|
|
4429
4735
|
case "hsla":
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4736
|
+
{
|
|
4737
|
+
// Convert RGB to HSL (simplified)
|
|
4738
|
+
const hsl =
|
|
4739
|
+
/**
|
|
4433
4740
|
* Convert RGB to HSL
|
|
4434
4741
|
*/
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4742
|
+
function(r, g, b) {
|
|
4743
|
+
r /= 255, g /= 255, b /= 255;
|
|
4744
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
4745
|
+
let h = 0, s = 0;
|
|
4746
|
+
const l = (max + min) / 2;
|
|
4747
|
+
if (max !== min) {
|
|
4748
|
+
const d = max - min;
|
|
4749
|
+
switch (s = l > .5 ? d / (2 - max - min) : d / (max + min), max) {
|
|
4750
|
+
case r:
|
|
4751
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
4752
|
+
break;
|
|
4446
4753
|
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4754
|
+
case g:
|
|
4755
|
+
h = ((b - r) / d + 2) / 6;
|
|
4756
|
+
break;
|
|
4450
4757
|
|
|
4451
|
-
|
|
4452
|
-
|
|
4758
|
+
case b:
|
|
4759
|
+
h = ((r - g) / d + 4) / 6;
|
|
4760
|
+
}
|
|
4453
4761
|
}
|
|
4762
|
+
return {
|
|
4763
|
+
h: Math.round(360 * h),
|
|
4764
|
+
s: Math.round(100 * s),
|
|
4765
|
+
l: Math.round(100 * l)
|
|
4766
|
+
};
|
|
4454
4767
|
}
|
|
4455
|
-
|
|
4456
|
-
h: Math.round(360 * h),
|
|
4457
|
-
s: Math.round(100 * s),
|
|
4458
|
-
l: Math.round(100 * l)
|
|
4459
|
-
};
|
|
4460
|
-
}
|
|
4461
|
-
/**
|
|
4768
|
+
/**
|
|
4462
4769
|
* Theme Live Editor Component
|
|
4463
4770
|
*
|
|
4464
4771
|
* Allows live editing of theme properties with instant preview
|
|
4465
4772
|
*/ (r, g, b);
|
|
4466
|
-
|
|
4773
|
+
return "hsl" === format ? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)` : `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
|
|
4774
|
+
}
|
|
4467
4775
|
|
|
4468
4776
|
default:
|
|
4469
4777
|
return color;
|
|
@@ -4789,8 +5097,312 @@ const ThemeLiveEditor = ({initialTheme: initialTheme, onChange: onChange, classN
|
|
|
4789
5097
|
children: "\n .atomix-theme-live-editor {\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .editor-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 24px;\n border-bottom: 1px solid #e0e0e0;\n background: #f5f5f5;\n border-radius: 8px 8px 0 0;\n }\n\n .editor-header h2 {\n margin: 0;\n font-size: 20px;\n color: #333;\n }\n\n .editor-controls {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .history-controls,\n .mode-controls,\n .action-controls {\n display: flex;\n gap: 8px;\n }\n\n .history-button,\n .mode-button,\n .export-button,\n .copy-button {\n padding: 8px 16px;\n border: 1px solid #e0e0e0;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n transition: all 0.2s;\n }\n\n .history-button:hover:not(:disabled),\n .mode-button:hover,\n .export-button:hover,\n .copy-button:hover {\n background: #f5f5f5;\n }\n\n .history-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .mode-button.active {\n background: #2196f3;\n color: white;\n border-color: #2196f3;\n }\n\n .export-button {\n background: #4caf50;\n color: white;\n border-color: #4caf50;\n }\n\n .copy-button {\n background: #ff9800;\n color: white;\n border-color: #ff9800;\n }\n\n .editor-content {\n display: flex;\n position: relative;\n min-height: 600px;\n }\n\n .editor-panel,\n .preview-panel {\n overflow-y: auto;\n padding: 24px;\n }\n\n .resizer {\n width: 4px;\n background: #e0e0e0;\n cursor: col-resize;\n position: relative;\n flex-shrink: 0;\n transition: background 0.2s;\n }\n\n .resizer:hover,\n .resizer.resizing {\n background: #2196f3;\n }\n\n .resizer::before {\n content: '';\n position: absolute;\n left: -2px;\n right: -2px;\n top: 0;\n bottom: 0;\n }\n\n .editor-panel h3,\n .preview-panel h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n color: #333;\n border-bottom: 2px solid #2196f3;\n padding-bottom: 8px;\n }\n\n .visual-editor {\n display: flex;\n flex-direction: column;\n gap: 24px;\n }\n\n .editor-section {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .color-format-selector {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n }\n\n .color-format-selector label {\n font-size: 14px;\n font-weight: 500;\n color: #666;\n }\n\n .color-format-selector select {\n padding: 6px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 14px;\n background: white;\n cursor: pointer;\n }\n\n .editor-field {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .editor-field label {\n font-size: 14px;\n font-weight: 500;\n color: #666;\n }\n\n .color-input-group {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n\n .color-input-group input[type=\"color\"] {\n width: 50px;\n height: 40px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .color-input-group input[type=\"text\"],\n .editor-field input[type=\"text\"],\n .editor-field input[type=\"number\"] {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 14px;\n font-family: 'Monaco', 'Menlo', monospace;\n }\n\n .json-editor {\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n\n .json-editor textarea {\n flex: 1;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-family: 'Monaco', 'Menlo', monospace;\n font-size: 12px;\n line-height: 1.5;\n resize: none;\n }\n\n .error-message {\n padding: 12px;\n background: #ffebee;\n color: #d32f2f;\n border-radius: 4px;\n margin-top: 8px;\n font-size: 14px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .error-dismiss {\n background: none;\n border: none;\n color: #d32f2f;\n font-size: 20px;\n cursor: pointer;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .error-dismiss:hover {\n background: rgba(211, 47, 47, 0.1);\n border-radius: 50%;\n }\n\n .preview-panel {\n border-left: 1px solid #e0e0e0;\n }\n "
|
|
4790
5098
|
}) ]
|
|
4791
5099
|
});
|
|
5100
|
+
}, DesignTokensCustomizer = ({initialTokens: initialTokens = {}, onTokensChange: onTokensChange, className: className, style: style}) => {
|
|
5101
|
+
// Current tokens state
|
|
5102
|
+
const [tokens, setTokens] = useState((() => createTokens(initialTokens))), [colorFormat, setColorFormat] = useState("hex"), [activeCategory, setActiveCategory] = useState("colors"), [cssPreview, setCssPreview] = useState("");
|
|
5103
|
+
// Generate CSS when tokens change
|
|
5104
|
+
useEffect((() => {
|
|
5105
|
+
const css = createTheme(tokens, {
|
|
5106
|
+
selector: ":root",
|
|
5107
|
+
prefix: "atomix-preview"
|
|
5108
|
+
});
|
|
5109
|
+
setCssPreview(css), onTokensChange?.(tokens);
|
|
5110
|
+
}), [ tokens, onTokensChange ]);
|
|
5111
|
+
// Create theme object for preview
|
|
5112
|
+
const previewTheme = useMemo((() => createThemeObject({
|
|
5113
|
+
palette: {
|
|
5114
|
+
primary: {
|
|
5115
|
+
main: tokens.primary
|
|
5116
|
+
},
|
|
5117
|
+
secondary: {
|
|
5118
|
+
main: tokens.secondary
|
|
5119
|
+
},
|
|
5120
|
+
error: {
|
|
5121
|
+
main: tokens.error
|
|
5122
|
+
},
|
|
5123
|
+
warning: {
|
|
5124
|
+
main: tokens.warning
|
|
5125
|
+
},
|
|
5126
|
+
info: {
|
|
5127
|
+
main: tokens.info
|
|
5128
|
+
},
|
|
5129
|
+
success: {
|
|
5130
|
+
main: tokens.success
|
|
5131
|
+
},
|
|
5132
|
+
background: {
|
|
5133
|
+
default: "#ffffff",
|
|
5134
|
+
subtle: tokens["secondary-bg-subtle"] || "#f3f4f6"
|
|
5135
|
+
},
|
|
5136
|
+
text: {
|
|
5137
|
+
primary: tokens["primary-text-emphasis"] || "#111827",
|
|
5138
|
+
secondary: tokens["secondary-text-emphasis"] || "#374151",
|
|
5139
|
+
disabled: tokens["disabled-text-emphasis"] || "#9ca3af"
|
|
5140
|
+
}
|
|
5141
|
+
},
|
|
5142
|
+
typography: {
|
|
5143
|
+
fontFamily: tokens["body-font-family"] || '"Roboto", sans-serif',
|
|
5144
|
+
fontSize: parseInt(tokens["body-font-size"] || "16")
|
|
5145
|
+
},
|
|
5146
|
+
spacing: factor => .25 * factor + "rem"
|
|
5147
|
+
})), [ tokens ]), updateToken = useCallback(((key, value) => {
|
|
5148
|
+
setTokens((prev => ({
|
|
5149
|
+
...prev,
|
|
5150
|
+
[key]: value
|
|
5151
|
+
})));
|
|
5152
|
+
}), []), convertColorFormat = useCallback(((color, format) => {
|
|
5153
|
+
// Parse current color
|
|
5154
|
+
const temp = document.createElement("div");
|
|
5155
|
+
temp.style.color = color, document.body.appendChild(temp);
|
|
5156
|
+
const computed = window.getComputedStyle(temp).color;
|
|
5157
|
+
document.body.removeChild(temp);
|
|
5158
|
+
const rgbMatch = computed.match(/\d+/g);
|
|
5159
|
+
if (!rgbMatch || rgbMatch.length < 3) return color;
|
|
5160
|
+
const r = parseInt(rgbMatch[0] || "0", 10), g = parseInt(rgbMatch[1] || "0", 10), b = parseInt(rgbMatch[2] || "0", 10), a = rgbMatch[3] ? parseFloat(rgbMatch[3]) : 1;
|
|
5161
|
+
switch (format) {
|
|
5162
|
+
case "hex":
|
|
5163
|
+
return `#${[ r, g, b ].map((x => x.toString(16).padStart(2, "0"))).join("")}`;
|
|
5164
|
+
|
|
5165
|
+
case "rgb":
|
|
5166
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
5167
|
+
|
|
5168
|
+
case "rgba":
|
|
5169
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
5170
|
+
|
|
5171
|
+
case "hsl":
|
|
5172
|
+
case "hsla":
|
|
5173
|
+
{
|
|
5174
|
+
const hsl =
|
|
5175
|
+
// RGB to HSL conversion
|
|
5176
|
+
function(r, g, b) {
|
|
5177
|
+
r /= 255, g /= 255, b /= 255;
|
|
5178
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
5179
|
+
let h = 0, s = 0;
|
|
5180
|
+
const l = (max + min) / 2;
|
|
5181
|
+
if (max !== min) {
|
|
5182
|
+
const d = max - min;
|
|
5183
|
+
switch (s = l > .5 ? d / (2 - max - min) : d / (max + min), max) {
|
|
5184
|
+
case r:
|
|
5185
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
5186
|
+
break;
|
|
5187
|
+
|
|
5188
|
+
case g:
|
|
5189
|
+
h = ((b - r) / d + 2) / 6;
|
|
5190
|
+
break;
|
|
5191
|
+
|
|
5192
|
+
case b:
|
|
5193
|
+
h = ((r - g) / d + 4) / 6;
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
return {
|
|
5197
|
+
h: Math.round(360 * h),
|
|
5198
|
+
s: Math.round(100 * s),
|
|
5199
|
+
l: Math.round(100 * l)
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
// Export functions
|
|
5203
|
+
(r, g, b);
|
|
5204
|
+
return "hsl" === format ? `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)` : `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${a})`;
|
|
5205
|
+
}
|
|
5206
|
+
|
|
5207
|
+
default:
|
|
5208
|
+
return color;
|
|
5209
|
+
}
|
|
5210
|
+
}), []), exportTokens = useCallback((() => {
|
|
5211
|
+
const dataStr = JSON.stringify(tokens, null, 2), dataUri = "data:application/json;charset=utf-8," + encodeURIComponent(dataStr), linkElement = document.createElement("a");
|
|
5212
|
+
linkElement.setAttribute("href", dataUri), linkElement.setAttribute("download", "design-tokens.json"),
|
|
5213
|
+
linkElement.click();
|
|
5214
|
+
}), [ tokens ]), exportTheme = useCallback((() => {
|
|
5215
|
+
const themeCss = createTheme(tokens), dataUri = "data:text/css;charset=utf-8," + encodeURIComponent(themeCss), linkElement = document.createElement("a");
|
|
5216
|
+
linkElement.setAttribute("href", dataUri), linkElement.setAttribute("download", "theme.css"),
|
|
5217
|
+
linkElement.click();
|
|
5218
|
+
}), [ tokens ]), copyToClipboard = useCallback((() => {
|
|
5219
|
+
navigator.clipboard?.writeText(JSON.stringify(tokens, null, 2));
|
|
5220
|
+
}), [ tokens ]), resetToDefaults = useCallback((() => {
|
|
5221
|
+
setTokens(defaultTokens);
|
|
5222
|
+
}), []), loadTokensFromFile = useCallback((event => {
|
|
5223
|
+
const file = event.target.files?.[0];
|
|
5224
|
+
if (!file) return;
|
|
5225
|
+
const reader = new FileReader;
|
|
5226
|
+
reader.onload = e => {
|
|
5227
|
+
try {
|
|
5228
|
+
const content = e.target?.result, mergedTokens = createTokens(JSON.parse(content));
|
|
5229
|
+
setTokens(mergedTokens);
|
|
5230
|
+
} catch (error) {
|
|
5231
|
+
console.error("Failed to load tokens:", error), alert("Invalid JSON file. Please select a valid design tokens JSON file.");
|
|
5232
|
+
}
|
|
5233
|
+
}, reader.readAsText(file);
|
|
5234
|
+
}), []), tokenCategories = {
|
|
5235
|
+
colors: {
|
|
5236
|
+
label: "Colors",
|
|
5237
|
+
tokens: [
|
|
5238
|
+
// Base colors
|
|
5239
|
+
"primary", "secondary", "success", "info", "warning", "error", "light", "dark",
|
|
5240
|
+
// RGB versions
|
|
5241
|
+
"primary-rgb", "secondary-rgb", "success-rgb", "info-rgb", "warning-rgb", "error-rgb", "light-rgb", "dark-rgb",
|
|
5242
|
+
// Gray scale
|
|
5243
|
+
"gray-1", "gray-2", "gray-3", "gray-4", "gray-5", "gray-6", "gray-7", "gray-8", "gray-9", "gray-10",
|
|
5244
|
+
// Primary scale
|
|
5245
|
+
"primary-1", "primary-2", "primary-3", "primary-4", "primary-5", "primary-6", "primary-7", "primary-8", "primary-9", "primary-10",
|
|
5246
|
+
// Text emphasis
|
|
5247
|
+
"primary-text-emphasis", "secondary-text-emphasis", "tertiary-text-emphasis", "disabled-text-emphasis", "invert-text-emphasis", "brand-text-emphasis", "error-text-emphasis", "success-text-emphasis", "warning-text-emphasis", "info-text-emphasis", "light-text-emphasis", "dark-text-emphasis",
|
|
5248
|
+
// Background subtle
|
|
5249
|
+
"primary-bg-subtle", "secondary-bg-subtle", "tertiary-bg-subtle", "invert-bg-subtle", "brand-bg-subtle", "error-bg-subtle", "success-bg-subtle", "warning-bg-subtle", "info-bg-subtle", "light-bg-subtle", "dark-bg-subtle",
|
|
5250
|
+
// Border subtle
|
|
5251
|
+
"primary-border-subtle", "secondary-border-subtle", "success-border-subtle", "error-border-subtle", "warning-border-subtle", "info-border-subtle", "brand-border-subtle", "light-border-subtle", "dark-border-subtle",
|
|
5252
|
+
// Hover states
|
|
5253
|
+
"primary-hover", "secondary-hover", "light-hover", "dark-hover", "error-hover", "success-hover", "warning-hover", "info-hover",
|
|
5254
|
+
// Gradients
|
|
5255
|
+
"primary-gradient", "secondary-gradient", "light-gradient", "dark-gradient", "success-gradient", "info-gradient", "warning-gradient", "error-gradient", "gradient" ]
|
|
5256
|
+
},
|
|
5257
|
+
typography: {
|
|
5258
|
+
label: "Typography",
|
|
5259
|
+
tokens: [ "font-sans-serif", "font-monospace", "body-font-family", "body-font-size", "body-font-weight", "body-line-height", "body-color", "body-bg", "heading-color", "font-size-xl", "font-size-2xl", "display-1", "font-weight-light", "font-weight-normal", "font-weight-medium", "font-weight-semibold", "font-weight-bold", "font-weight-heavy", "font-weight-black", "line-height-base", "line-height-sm", "line-height-lg", "letter-spacing-h1", "letter-spacing-h2", "letter-spacing-h3", "letter-spacing-h4", "letter-spacing-h5", "letter-spacing-h6", "link-color", "link-color-rgb", "link-decoration", "link-hover-color", "link-hover-color-rgb", "highlight-bg", "code-color" ]
|
|
5260
|
+
},
|
|
5261
|
+
spacing: {
|
|
5262
|
+
label: "Spacing",
|
|
5263
|
+
tokens: [ "spacing-0", "spacing-1", "spacing-px-6", "spacing-2", "spacing-px-10", "spacing-3", "spacing-px-14", "spacing-4", "spacing-5", "spacing-px-22", "spacing-6", "spacing-7", "spacing-px-30", "spacing-8", "spacing-9", "spacing-10", "spacing-11", "spacing-12", "spacing-14", "spacing-16", "spacing-20", "spacing-24", "spacing-28", "spacing-32", "spacing-36", "spacing-40", "spacing-44", "spacing-48", "spacing-52", "spacing-56", "spacing-60", "spacing-64", "spacing-72", "spacing-80", "spacing-90", "spacing-200" ]
|
|
5264
|
+
},
|
|
5265
|
+
shadows: {
|
|
5266
|
+
label: "Shadows",
|
|
5267
|
+
tokens: [ "box-shadow", "box-shadow-xs", "box-shadow-sm", "box-shadow-lg", "box-shadow-xl", "box-shadow-inset" ]
|
|
5268
|
+
},
|
|
5269
|
+
borders: {
|
|
5270
|
+
label: "Borders",
|
|
5271
|
+
tokens: [ "border-width", "border-style", "border-color", "border-color-translucent", "border-radius", "border-radius-sm", "border-radius-lg", "border-radius-xl", "border-radius-xxl", "border-radius-2xl", "border-radius-3xl", "border-radius-4xl", "border-radius-pill", "focus-border-color", "focus-ring-width", "focus-ring-offset", "focus-ring-opacity", "form-valid-color", "form-valid-border-color", "form-invalid-color", "form-invalid-border-color" ]
|
|
5272
|
+
},
|
|
5273
|
+
transitions: {
|
|
5274
|
+
label: "Transitions",
|
|
5275
|
+
tokens: [ "transition-duration-fast", "transition-duration-base", "transition-duration-slow", "transition-duration-slower", "easing-base", "easing-ease-in-out", "easing-ease-out", "easing-ease-in", "easing-ease-linear", "transition-fast", "transition-base", "transition-slow" ]
|
|
5276
|
+
},
|
|
5277
|
+
zindex: {
|
|
5278
|
+
label: "Z-Index",
|
|
5279
|
+
tokens: [ "z-n1", "z-0", "z-1", "z-2", "z-3", "z-4", "z-5", "z-dropdown", "z-sticky", "z-fixed", "z-modal", "z-popover", "z-tooltip", "z-drawer" ]
|
|
5280
|
+
},
|
|
5281
|
+
breakpoints: {
|
|
5282
|
+
label: "Breakpoints",
|
|
5283
|
+
tokens: [ "breakpoint-xs", "breakpoint-sm", "breakpoint-md", "breakpoint-lg", "breakpoint-xl", "breakpoint-xxl" ]
|
|
5284
|
+
}
|
|
5285
|
+
};
|
|
5286
|
+
// Update token value
|
|
5287
|
+
return jsxs("div", {
|
|
5288
|
+
className: `design-tokens-customizer ${className || ""}`,
|
|
5289
|
+
style: style,
|
|
5290
|
+
children: [ jsxs("div", {
|
|
5291
|
+
className: "customizer-header",
|
|
5292
|
+
children: [ jsx("h2", {
|
|
5293
|
+
children: "Interactive Theme Customizer"
|
|
5294
|
+
}), jsxs("div", {
|
|
5295
|
+
className: "customizer-controls",
|
|
5296
|
+
children: [ jsxs("select", {
|
|
5297
|
+
value: colorFormat,
|
|
5298
|
+
onChange: e => setColorFormat(e.target.value),
|
|
5299
|
+
children: [ jsx("option", {
|
|
5300
|
+
value: "hex",
|
|
5301
|
+
children: "HEX"
|
|
5302
|
+
}), jsx("option", {
|
|
5303
|
+
value: "rgb",
|
|
5304
|
+
children: "RGB"
|
|
5305
|
+
}), jsx("option", {
|
|
5306
|
+
value: "rgba",
|
|
5307
|
+
children: "RGBA"
|
|
5308
|
+
}), jsx("option", {
|
|
5309
|
+
value: "hsl",
|
|
5310
|
+
children: "HSL"
|
|
5311
|
+
}), jsx("option", {
|
|
5312
|
+
value: "hsla",
|
|
5313
|
+
children: "HSLA"
|
|
5314
|
+
}) ]
|
|
5315
|
+
}), jsx("button", {
|
|
5316
|
+
onClick: resetToDefaults,
|
|
5317
|
+
children: "Reset to Defaults"
|
|
5318
|
+
}), jsxs("label", {
|
|
5319
|
+
className: "file-input-button",
|
|
5320
|
+
children: [ "Load Tokens", jsx("input", {
|
|
5321
|
+
type: "file",
|
|
5322
|
+
accept: ".json",
|
|
5323
|
+
onChange: loadTokensFromFile,
|
|
5324
|
+
style: {
|
|
5325
|
+
display: "none"
|
|
5326
|
+
}
|
|
5327
|
+
}) ]
|
|
5328
|
+
}), jsx("button", {
|
|
5329
|
+
onClick: copyToClipboard,
|
|
5330
|
+
children: "Copy Tokens"
|
|
5331
|
+
}), jsx("button", {
|
|
5332
|
+
onClick: exportTokens,
|
|
5333
|
+
children: "Export Tokens"
|
|
5334
|
+
}), jsx("button", {
|
|
5335
|
+
onClick: exportTheme,
|
|
5336
|
+
children: "Export Theme CSS"
|
|
5337
|
+
}) ]
|
|
5338
|
+
}) ]
|
|
5339
|
+
}), jsxs("div", {
|
|
5340
|
+
className: "customizer-content",
|
|
5341
|
+
children: [ jsx("div", {
|
|
5342
|
+
className: "customizer-sidebar",
|
|
5343
|
+
children: Object.entries(tokenCategories).map((([key, category]) => jsx("button", {
|
|
5344
|
+
className: "category-button " + (activeCategory === key ? "active" : ""),
|
|
5345
|
+
onClick: () => setActiveCategory(key),
|
|
5346
|
+
children: category.label
|
|
5347
|
+
}, key)))
|
|
5348
|
+
}), jsxs("div", {
|
|
5349
|
+
className: "customizer-editor",
|
|
5350
|
+
children: [ jsx("h3", {
|
|
5351
|
+
children: tokenCategories[activeCategory].label
|
|
5352
|
+
}), jsx("div", {
|
|
5353
|
+
className: "tokens-grid",
|
|
5354
|
+
children: tokenCategories[activeCategory].tokens.map((tokenKey => {
|
|
5355
|
+
var _context;
|
|
5356
|
+
const value = tokens[tokenKey] || "", isColor = _includesInstanceProperty(tokenKey).call(tokenKey, "color") || _includesInstanceProperty(tokenKey).call(tokenKey, "bg") || _includesInstanceProperty(tokenKey).call(tokenKey, "gradient") || _includesInstanceProperty(_context = [ "primary", "secondary", "success", "info", "warning", "error", "light", "dark" ]).call(_context, tokenKey) || tokenKey.match(/^(gray|primary|red|green|blue|yellow)-\d+$/);
|
|
5357
|
+
return jsxs("div", {
|
|
5358
|
+
className: "token-item",
|
|
5359
|
+
children: [ jsx("label", {
|
|
5360
|
+
children: tokenKey
|
|
5361
|
+
}), isColor ? jsxs("div", {
|
|
5362
|
+
className: "color-input-group",
|
|
5363
|
+
children: [ jsx("input", {
|
|
5364
|
+
type: "color",
|
|
5365
|
+
value: value.startsWith("#") ? value : convertColorFormat(value, "hex"),
|
|
5366
|
+
onChange: e => updateToken(tokenKey, e.target.value)
|
|
5367
|
+
}), jsx("input", {
|
|
5368
|
+
type: "text",
|
|
5369
|
+
value: convertColorFormat(value, colorFormat),
|
|
5370
|
+
onChange: e => {
|
|
5371
|
+
const converted = convertColorFormat(e.target.value, "hex");
|
|
5372
|
+
updateToken(tokenKey, converted);
|
|
5373
|
+
}
|
|
5374
|
+
}) ]
|
|
5375
|
+
}) : jsx("input", {
|
|
5376
|
+
type: "text",
|
|
5377
|
+
value: value,
|
|
5378
|
+
onChange: e => updateToken(tokenKey, e.target.value)
|
|
5379
|
+
}) ]
|
|
5380
|
+
}, tokenKey);
|
|
5381
|
+
}))
|
|
5382
|
+
}) ]
|
|
5383
|
+
}), jsxs("div", {
|
|
5384
|
+
className: "customizer-preview",
|
|
5385
|
+
children: [ jsx("h3", {
|
|
5386
|
+
children: "Live Preview"
|
|
5387
|
+
}), jsx("style", {
|
|
5388
|
+
children: cssPreview
|
|
5389
|
+
}), jsx(ThemePreview, {
|
|
5390
|
+
theme: previewTheme,
|
|
5391
|
+
showDetails: !1,
|
|
5392
|
+
showPalette: !0,
|
|
5393
|
+
showTypography: !0,
|
|
5394
|
+
showSpacing: !0
|
|
5395
|
+
}) ]
|
|
5396
|
+
}) ]
|
|
5397
|
+
}), jsx("style", {
|
|
5398
|
+
children: "\n .design-tokens-customizer {\n display: flex;\n flex-direction: column;\n height: 100vh;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .customizer-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 24px;\n border-bottom: 1px solid #e0e0e0;\n background: #f5f5f5;\n }\n\n .customizer-header h2 {\n margin: 0;\n font-size: 24px;\n color: #333;\n }\n\n .customizer-controls {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n\n .customizer-controls select,\n .customizer-controls button {\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n }\n\n .customizer-controls button:hover,\n .file-input-button:hover {\n background: #f0f0f0;\n }\n\n .file-input-button {\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n display: inline-block;\n }\n\n .customizer-content {\n display: flex;\n flex: 1;\n overflow: hidden;\n }\n\n .customizer-sidebar {\n width: 200px;\n border-right: 1px solid #e0e0e0;\n padding: 16px;\n background: #fafafa;\n overflow-y: auto;\n }\n\n .category-button {\n display: block;\n width: 100%;\n padding: 12px;\n margin-bottom: 8px;\n border: none;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n text-align: left;\n font-size: 14px;\n }\n\n .category-button:hover,\n .category-button.active {\n background: #e0e0e0;\n }\n\n .customizer-editor {\n flex: 1;\n padding: 24px;\n overflow-y: auto;\n }\n\n .customizer-editor h3 {\n margin-top: 0;\n margin-bottom: 16px;\n color: #333;\n }\n\n .tokens-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 16px;\n }\n\n .token-item {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .token-item label {\n font-size: 14px;\n font-weight: 500;\n color: #666;\n }\n\n .color-input-group {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n\n .color-input-group input[type=\"color\"] {\n width: 50px;\n height: 40px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .color-input-group input[type=\"text\"],\n .token-item input[type=\"text\"] {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 14px;\n font-family: 'Monaco', 'Menlo', monospace;\n }\n\n .customizer-preview {\n width: 400px;\n border-left: 1px solid #e0e0e0;\n padding: 24px;\n overflow-y: auto;\n background: #fafafa;\n }\n\n .customizer-preview h3 {\n margin-top: 0;\n margin-bottom: 16px;\n color: #333;\n }\n "
|
|
5399
|
+
}) ]
|
|
5400
|
+
});
|
|
4792
5401
|
};
|
|
4793
5402
|
|
|
5403
|
+
/**
|
|
5404
|
+
* Design Tokens Customizer Component
|
|
5405
|
+
*/
|
|
4794
5406
|
/**
|
|
4795
5407
|
* Theme Adapter
|
|
4796
5408
|
*
|
|
@@ -4801,7 +5413,8 @@ const ThemeLiveEditor = ({initialTheme: initialTheme, onChange: onChange, classN
|
|
|
4801
5413
|
*
|
|
4802
5414
|
* @param tokens - DesignTokens object
|
|
4803
5415
|
* @returns CSS variables object compatible with Theme.cssVars
|
|
4804
|
-
*/
|
|
5416
|
+
*/
|
|
5417
|
+
function designTokensToCSSVars(tokens) {
|
|
4805
5418
|
const cssVars = {};
|
|
4806
5419
|
return Object.entries(tokens).forEach((([key, value]) => {
|
|
4807
5420
|
void 0 !== value && (cssVars[`--atomix-${key}`] = String(value));
|
|
@@ -5149,5 +5762,5 @@ class RTLManager {
|
|
|
5149
5762
|
removeCSS(id);
|
|
5150
5763
|
}
|
|
5151
5764
|
|
|
5152
|
-
export { RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeValidator, applyCSSVariables, applyComponentTheme, applyTheme, camelToKebab, clearThemes, createTheme, createThemeRegistry, createTokens, cssVarsToStyle, deepMerge, defaultTokens, designTokensToCSSVars, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateClassName, generateComponentCSSVars, getAllThemes, getCSSVariable, getComponentThemeValue, getTheme, getThemeApplicator, getThemeCount, getThemeIds, hasTheme, injectCSS$1 as injectCSS, injectTheme, isCSSInjected, isDesignTokens, isValidCSSVariableName, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, normalizeThemeTokens, registerTheme, removeCSS, removeCSSVariables, removeTheme, themePropertyToCSSVar, unregisterTheme, useComponentTheme, useHistory, useTheme, useThemeTokens };
|
|
5765
|
+
export { DesignTokensCustomizer, RTLManager, ThemeApplicator, ThemeComparator, ThemeContext, ThemeErrorBoundary, ThemeInspector, ThemeLiveEditor, ThemePreview, ThemeProvider, ThemeValidator, applyCSSVariables, applyComponentTheme, applyTheme, camelToKebab, clearThemes, createTheme, createThemeRegistry, createTokens, cssVarsToStyle, deepMerge, defaultTokens, designTokensToCSSVars, extendTheme, extractComponentName, generateCSSVariableName, generateCSSVariables$1 as generateCSSVariables, generateCSSVariablesForSelector, generateClassName, generateComponentCSSVars, getAllThemes, getCSSVariable, getComponentThemeValue, getTheme, getThemeApplicator, getThemeCount, getThemeIds, hasTheme, injectCSS$1 as injectCSS, injectTheme, isCSSInjected, isDesignTokens, isValidCSSVariableName, mapSCSSTokensToCSSVars, mergeCSSVars, mergeTheme, normalizeThemeTokens, registerTheme, removeCSS, removeCSSVariables, removeTheme, themePropertyToCSSVar, unregisterTheme, useComponentTheme, useHistory, useTheme, useThemeTokens };
|
|
5153
5766
|
//# sourceMappingURL=theme.js.map
|