@bug-on/m3-expressive 1.1.0 → 1.2.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/buttons.d.mts +3 -3
  3. package/dist/buttons.d.ts +3 -3
  4. package/dist/buttons.js +708 -606
  5. package/dist/buttons.js.map +1 -1
  6. package/dist/buttons.mjs +707 -605
  7. package/dist/buttons.mjs.map +1 -1
  8. package/dist/core-Bc5Wj_pc.d.ts +497 -0
  9. package/dist/core-D4048_K5.d.mts +497 -0
  10. package/dist/core.d.mts +6 -422
  11. package/dist/core.d.ts +6 -422
  12. package/dist/core.js +223 -129
  13. package/dist/core.js.map +1 -1
  14. package/dist/core.mjs +224 -130
  15. package/dist/core.mjs.map +1 -1
  16. package/dist/feedback.js +85 -61
  17. package/dist/feedback.js.map +1 -1
  18. package/dist/feedback.mjs +85 -61
  19. package/dist/feedback.mjs.map +1 -1
  20. package/dist/forms.d.mts +2 -2
  21. package/dist/forms.d.ts +2 -2
  22. package/dist/forms.js +70 -8
  23. package/dist/forms.js.map +1 -1
  24. package/dist/forms.mjs +70 -8
  25. package/dist/forms.mjs.map +1 -1
  26. package/dist/{icon-button-D-gs0gfj.d.mts → icon-button-sSt6PPLg.d.mts} +6 -0
  27. package/dist/{icon-button-D-gs0gfj.d.ts → icon-button-sSt6PPLg.d.ts} +6 -0
  28. package/dist/index.css +6 -1
  29. package/dist/index.d.mts +68 -6
  30. package/dist/index.d.ts +68 -6
  31. package/dist/index.js +1177 -580
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +1172 -583
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/layout.d.mts +18 -1
  36. package/dist/layout.d.ts +18 -1
  37. package/dist/layout.js +94 -8
  38. package/dist/layout.js.map +1 -1
  39. package/dist/layout.mjs +94 -8
  40. package/dist/layout.mjs.map +1 -1
  41. package/dist/{md3-DFhj-NZj.d.mts → md3-Dty-Qcad.d.mts} +7 -1
  42. package/dist/{md3-DFhj-NZj.d.ts → md3-Dty-Qcad.d.ts} +7 -1
  43. package/dist/navigation.d.mts +134 -9
  44. package/dist/navigation.d.ts +134 -9
  45. package/dist/navigation.js +403 -93
  46. package/dist/navigation.js.map +1 -1
  47. package/dist/navigation.mjs +397 -94
  48. package/dist/navigation.mjs.map +1 -1
  49. package/dist/overlays.js +214 -189
  50. package/dist/overlays.js.map +1 -1
  51. package/dist/overlays.mjs +176 -151
  52. package/dist/overlays.mjs.map +1 -1
  53. package/dist/pickers.js +222 -197
  54. package/dist/pickers.js.map +1 -1
  55. package/dist/pickers.mjs +189 -164
  56. package/dist/pickers.mjs.map +1 -1
  57. package/dist/{split-button-trailing-uncheckable-BRPuTqi1.d.mts → split-button-trailing-uncheckable-BcPD_7uK.d.ts} +73 -7
  58. package/dist/{split-button-trailing-uncheckable-CjOFCoyW.d.ts → split-button-trailing-uncheckable-DtFJkTFr.d.mts} +73 -7
  59. package/dist/{text-field-eAIpz9z1.d.mts → text-field-T4Rg-9Bw.d.mts} +7 -0
  60. package/dist/{text-field-eAIpz9z1.d.ts → text-field-T4Rg-9Bw.d.ts} +7 -0
  61. package/package.json +4 -4
package/dist/core.mjs CHANGED
@@ -2,10 +2,11 @@
2
2
  import * as React11 from 'react';
3
3
  import { createContext, useState, useEffect, useRef, useCallback, useMemo, useContext } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { argbFromHex, themeFromSourceColor, hexFromArgb } from '@material/material-color-utilities';
5
+ import { hexFromArgb, Hct, SchemeExpressive, SchemeNeutral, SchemeMonochrome, SchemeContent, SchemeFidelity, SchemeVibrant, SchemeTonalSpot } from '@material/material-color-utilities';
6
6
  import { clsx } from 'clsx';
7
7
  import { twMerge } from 'tailwind-merge';
8
8
  import { LazyMotion, domMax, m, useMotionValue, animate, useAnimationFrame, AnimatePresence, useReducedMotion } from 'motion/react';
9
+ import { Slot } from '@radix-ui/react-slot';
9
10
 
10
11
  var __defProp = Object.defineProperty;
11
12
  var __defProps = Object.defineProperties;
@@ -151,66 +152,110 @@ function resolveMode(mode) {
151
152
  if (typeof window === "undefined") return "light";
152
153
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
153
154
  }
154
- function generateM3Theme(sourceColorHex, mode = "light") {
155
- const sourceColor = argbFromHex(sourceColorHex);
156
- const theme = themeFromSourceColor(sourceColor);
157
- const scheme = mode === "light" ? theme.schemes.light : theme.schemes.dark;
158
- const palettes = theme.palettes;
159
- const tone = (palette, t) => hexFromArgb(palette.tone(t));
155
+ function createDynamicScheme(sourceColorHex, isDark, variant = "expressive", contrastLevel = 0) {
156
+ const hct = Hct.fromInt(argbFromHex(sourceColorHex));
157
+ const spec = "2025";
158
+ switch (variant) {
159
+ case "tonal_spot":
160
+ return new SchemeTonalSpot(hct, isDark, contrastLevel, spec);
161
+ case "vibrant":
162
+ return new SchemeVibrant(hct, isDark, contrastLevel, spec);
163
+ case "fidelity":
164
+ return new SchemeFidelity(hct, isDark, contrastLevel, spec);
165
+ case "content":
166
+ return new SchemeContent(hct, isDark, contrastLevel, spec);
167
+ case "monochrome":
168
+ return new SchemeMonochrome(hct, isDark, contrastLevel, spec);
169
+ case "neutral":
170
+ return new SchemeNeutral(hct, isDark, contrastLevel, spec);
171
+ default:
172
+ return new SchemeExpressive(hct, isDark, contrastLevel, spec);
173
+ }
174
+ }
175
+ function argbFromHex(hex) {
176
+ const sanitized = hex.replace(/^#/, "");
177
+ const r = Number.parseInt(sanitized.substring(0, 2), 16);
178
+ const g = Number.parseInt(sanitized.substring(2, 4), 16);
179
+ const b = Number.parseInt(sanitized.substring(4, 6), 16);
180
+ return (4278190080 | r << 16 | g << 8 | b) >>> 0;
181
+ }
182
+ function generateM3Theme(sourceColorHex, mode = "light", options = {}) {
183
+ const { variant = "expressive", contrastLevel = 0 } = options;
184
+ const isDark = mode === "dark";
185
+ const scheme = createDynamicScheme(
186
+ sourceColorHex,
187
+ isDark,
188
+ variant,
189
+ contrastLevel
190
+ );
191
+ const hex = (argb) => hexFromArgb(argb);
160
192
  return {
161
- primary: hexFromArgb(scheme.primary),
162
- onPrimary: hexFromArgb(scheme.onPrimary),
163
- primaryContainer: hexFromArgb(scheme.primaryContainer),
164
- onPrimaryContainer: hexFromArgb(scheme.onPrimaryContainer),
165
- inversePrimary: hexFromArgb(scheme.inversePrimary),
166
- primaryFixed: tone(palettes.primary, 90),
167
- primaryFixedDim: tone(palettes.primary, 80),
168
- onPrimaryFixed: tone(palettes.primary, 10),
169
- onPrimaryFixedVariant: tone(palettes.primary, 30),
170
- secondary: hexFromArgb(scheme.secondary),
171
- onSecondary: hexFromArgb(scheme.onSecondary),
172
- secondaryContainer: hexFromArgb(scheme.secondaryContainer),
173
- onSecondaryContainer: hexFromArgb(scheme.onSecondaryContainer),
174
- secondaryFixed: tone(palettes.secondary, 90),
175
- secondaryFixedDim: tone(palettes.secondary, 80),
176
- onSecondaryFixed: tone(palettes.secondary, 10),
177
- onSecondaryFixedVariant: tone(palettes.secondary, 30),
178
- tertiary: hexFromArgb(scheme.tertiary),
179
- onTertiary: hexFromArgb(scheme.onTertiary),
180
- tertiaryContainer: hexFromArgb(scheme.tertiaryContainer),
181
- onTertiaryContainer: hexFromArgb(scheme.onTertiaryContainer),
182
- tertiaryFixed: tone(palettes.tertiary, 90),
183
- tertiaryFixedDim: tone(palettes.tertiary, 80),
184
- onTertiaryFixed: tone(palettes.tertiary, 10),
185
- onTertiaryFixedVariant: tone(palettes.tertiary, 30),
186
- error: hexFromArgb(scheme.error),
187
- onError: hexFromArgb(scheme.onError),
188
- errorContainer: hexFromArgb(scheme.errorContainer),
189
- onErrorContainer: hexFromArgb(scheme.onErrorContainer),
190
- surface: hexFromArgb(scheme.surface),
191
- onSurface: hexFromArgb(scheme.onSurface),
192
- surfaceVariant: hexFromArgb(scheme.surfaceVariant),
193
- onSurfaceVariant: hexFromArgb(scheme.onSurfaceVariant),
194
- surfaceTint: hexFromArgb(scheme.primary),
195
- // Surface container roles from neutral palette tones
196
- surfaceContainerLowest: mode === "light" ? tone(palettes.neutral, 100) : tone(palettes.neutral, 4),
197
- surfaceContainerLow: mode === "light" ? tone(palettes.neutral, 96) : tone(palettes.neutral, 10),
198
- surfaceContainer: mode === "light" ? tone(palettes.neutral, 94) : tone(palettes.neutral, 12),
199
- surfaceContainerHigh: mode === "light" ? tone(palettes.neutral, 92) : tone(palettes.neutral, 17),
200
- surfaceContainerHighest: mode === "light" ? tone(palettes.neutral, 90) : tone(palettes.neutral, 22),
201
- inverseSurface: hexFromArgb(scheme.inverseSurface),
202
- inverseOnSurface: hexFromArgb(scheme.inverseOnSurface),
203
- background: hexFromArgb(scheme.background),
204
- onBackground: hexFromArgb(scheme.onBackground),
205
- outline: hexFromArgb(scheme.outline),
206
- outlineVariant: hexFromArgb(scheme.outlineVariant),
207
- shadow: hexFromArgb(scheme.shadow),
208
- scrim: hexFromArgb(scheme.scrim)
193
+ // Primary
194
+ primary: hex(scheme.primary),
195
+ onPrimary: hex(scheme.onPrimary),
196
+ primaryContainer: hex(scheme.primaryContainer),
197
+ onPrimaryContainer: hex(scheme.onPrimaryContainer),
198
+ inversePrimary: hex(scheme.inversePrimary),
199
+ primaryFixed: hex(scheme.primaryFixed),
200
+ primaryFixedDim: hex(scheme.primaryFixedDim),
201
+ onPrimaryFixed: hex(scheme.onPrimaryFixed),
202
+ onPrimaryFixedVariant: hex(scheme.onPrimaryFixedVariant),
203
+ primaryDim: hex(scheme.primaryDim),
204
+ // Secondary
205
+ secondary: hex(scheme.secondary),
206
+ onSecondary: hex(scheme.onSecondary),
207
+ secondaryContainer: hex(scheme.secondaryContainer),
208
+ onSecondaryContainer: hex(scheme.onSecondaryContainer),
209
+ secondaryFixed: hex(scheme.secondaryFixed),
210
+ secondaryFixedDim: hex(scheme.secondaryFixedDim),
211
+ onSecondaryFixed: hex(scheme.onSecondaryFixed),
212
+ onSecondaryFixedVariant: hex(scheme.onSecondaryFixedVariant),
213
+ secondaryDim: hex(scheme.secondaryDim),
214
+ // Tertiary
215
+ tertiary: hex(scheme.tertiary),
216
+ onTertiary: hex(scheme.onTertiary),
217
+ tertiaryContainer: hex(scheme.tertiaryContainer),
218
+ onTertiaryContainer: hex(scheme.onTertiaryContainer),
219
+ tertiaryFixed: hex(scheme.tertiaryFixed),
220
+ tertiaryFixedDim: hex(scheme.tertiaryFixedDim),
221
+ onTertiaryFixed: hex(scheme.onTertiaryFixed),
222
+ onTertiaryFixedVariant: hex(scheme.onTertiaryFixedVariant),
223
+ tertiaryDim: hex(scheme.tertiaryDim),
224
+ // Error
225
+ error: hex(scheme.error),
226
+ onError: hex(scheme.onError),
227
+ errorContainer: hex(scheme.errorContainer),
228
+ onErrorContainer: hex(scheme.onErrorContainer),
229
+ // Surface
230
+ surface: hex(scheme.surface),
231
+ onSurface: hex(scheme.onSurface),
232
+ surfaceVariant: hex(scheme.surfaceVariant),
233
+ onSurfaceVariant: hex(scheme.onSurfaceVariant),
234
+ surfaceTint: hex(scheme.surfaceTint),
235
+ surfaceDim: hex(scheme.surfaceDim),
236
+ surfaceBright: hex(scheme.surfaceBright),
237
+ surfaceContainerLowest: hex(scheme.surfaceContainerLowest),
238
+ surfaceContainerLow: hex(scheme.surfaceContainerLow),
239
+ surfaceContainer: hex(scheme.surfaceContainer),
240
+ surfaceContainerHigh: hex(scheme.surfaceContainerHigh),
241
+ surfaceContainerHighest: hex(scheme.surfaceContainerHighest),
242
+ // Inverse
243
+ inverseSurface: hex(scheme.inverseSurface),
244
+ inverseOnSurface: hex(scheme.inverseOnSurface),
245
+ // Background
246
+ background: hex(scheme.background),
247
+ onBackground: hex(scheme.onBackground),
248
+ // Outline
249
+ outline: hex(scheme.outline),
250
+ outlineVariant: hex(scheme.outlineVariant),
251
+ // Utility
252
+ shadow: hex(scheme.shadow),
253
+ scrim: hex(scheme.scrim)
209
254
  };
210
255
  }
211
- function applyTheme(sourceColorHex, mode = "light", root = document.documentElement) {
256
+ function applyTheme(sourceColorHex, mode = "light", root = document.documentElement, options = {}) {
212
257
  const resolved = resolveMode(mode);
213
- const colors = generateM3Theme(sourceColorHex, resolved);
258
+ const colors = generateM3Theme(sourceColorHex, resolved, options);
214
259
  for (const [key, value] of Object.entries(colors)) {
215
260
  const kebabKey = key.replace(/[A-Z]/g, (m7) => `-${m7.toLowerCase()}`);
216
261
  root.style.setProperty(`--md-sys-color-${kebabKey}`, value);
@@ -1436,6 +1481,7 @@ var IconButtonComponent = React11.forwardRef(
1436
1481
  loading = false,
1437
1482
  loadingVariant = "loading-indicator",
1438
1483
  iconSize,
1484
+ asChild = false,
1439
1485
  children,
1440
1486
  onClick,
1441
1487
  onKeyDown,
@@ -1451,6 +1497,7 @@ var IconButtonComponent = React11.forwardRef(
1451
1497
  "loading",
1452
1498
  "loadingVariant",
1453
1499
  "iconSize",
1500
+ "asChild",
1454
1501
  "children",
1455
1502
  "onClick",
1456
1503
  "onKeyDown",
@@ -1512,7 +1559,87 @@ var IconButtonComponent = React11.forwardRef(
1512
1559
  },
1513
1560
  [loading, onClick, onKeyDown]
1514
1561
  );
1515
- return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
1562
+ const innerContent = /* @__PURE__ */ jsxs(Fragment, { children: [
1563
+ needsTouchTarget && /* @__PURE__ */ jsx(TouchTarget, {}),
1564
+ /* @__PURE__ */ jsx(Ripple, { ripples, onRippleDone: removeRipple }),
1565
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: loading ? /* @__PURE__ */ jsx(
1566
+ m.span,
1567
+ __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1568
+ transition: SPRING_TRANSITION,
1569
+ className: cn(
1570
+ "flex items-center justify-center shrink-0",
1571
+ iconSize != null ? void 0 : iconClass
1572
+ ),
1573
+ style: isCustomSize ? { width: `${iconSize}px`, height: `${iconSize}px` } : void 0,
1574
+ children: loadingVariant === "loading-indicator" ? /* @__PURE__ */ jsx(
1575
+ LoadingIndicator,
1576
+ {
1577
+ size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1578
+ color: "currentColor",
1579
+ "aria-label": "Loading"
1580
+ }
1581
+ ) : /* @__PURE__ */ jsx(
1582
+ ProgressIndicator,
1583
+ {
1584
+ variant: "circular",
1585
+ size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1586
+ color: "currentColor",
1587
+ trackColor: "transparent",
1588
+ "aria-label": "Loading"
1589
+ }
1590
+ )
1591
+ }),
1592
+ "loading"
1593
+ ) : /* @__PURE__ */ jsx(
1594
+ m.span,
1595
+ __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1596
+ transition: SPRING_TRANSITION,
1597
+ "aria-hidden": "true",
1598
+ className: cn(
1599
+ "flex items-center justify-center shrink-0 [&_svg]:w-full [&_svg]:h-full [&_.md-icon]:text-[length:inherit]!",
1600
+ iconSize != null ? void 0 : iconClass
1601
+ ),
1602
+ style: {
1603
+ fontSize: typeof iconPx === "number" ? `${iconPx}px` : iconPx,
1604
+ width: isCustomSize ? `${iconSize}px` : void 0,
1605
+ height: isCustomSize ? `${iconSize}px` : void 0
1606
+ },
1607
+ children: asChild ? React11.Children.only(children).props.children : children
1608
+ }),
1609
+ "content"
1610
+ ) })
1611
+ ] });
1612
+ const containerClassName = cn(
1613
+ baseIconButtonClasses,
1614
+ resolvedColorClass,
1615
+ outlineWidthClass,
1616
+ disabledBgClass,
1617
+ "overflow-hidden",
1618
+ SIZE_STYLES[size],
1619
+ loading && "pointer-events-none opacity-75 cursor-not-allowed",
1620
+ className
1621
+ );
1622
+ if (asChild) {
1623
+ const child = React11.Children.only(children);
1624
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsx(
1625
+ Slot,
1626
+ __spreadProps(__spreadValues({
1627
+ ref,
1628
+ "aria-pressed": isToggle ? isSelected : void 0,
1629
+ "aria-label": ariaLabel,
1630
+ "aria-busy": loading || void 0,
1631
+ "aria-disabled": loading || restProps.disabled,
1632
+ onClick: handleClick,
1633
+ onPointerDown,
1634
+ onKeyDown: handleKeyDown,
1635
+ style: __spreadProps(__spreadValues({}, style), { borderRadius: `${animateRadius}px` }),
1636
+ className: containerClassName
1637
+ }, restProps), {
1638
+ children: React11.cloneElement(child, { children: innerContent })
1639
+ })
1640
+ ) });
1641
+ }
1642
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsx(
1516
1643
  m.button,
1517
1644
  __spreadProps(__spreadValues({
1518
1645
  ref,
@@ -1528,67 +1655,9 @@ var IconButtonComponent = React11.forwardRef(
1528
1655
  animate: { borderRadius: animateRadius },
1529
1656
  whileTap: { borderRadius: pressedRadius },
1530
1657
  transition: { borderRadius: SPRING_TRANSITION_FAST },
1531
- className: cn(
1532
- baseIconButtonClasses,
1533
- resolvedColorClass,
1534
- outlineWidthClass,
1535
- disabledBgClass,
1536
- "overflow-hidden",
1537
- SIZE_STYLES[size],
1538
- loading && "pointer-events-none opacity-75 cursor-not-allowed",
1539
- className
1540
- )
1658
+ className: containerClassName
1541
1659
  }, restProps), {
1542
- children: [
1543
- needsTouchTarget && /* @__PURE__ */ jsx(TouchTarget, {}),
1544
- /* @__PURE__ */ jsx(Ripple, { ripples, onRippleDone: removeRipple }),
1545
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: loading ? /* @__PURE__ */ jsx(
1546
- m.span,
1547
- __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1548
- transition: SPRING_TRANSITION,
1549
- className: cn(
1550
- "flex items-center justify-center shrink-0",
1551
- iconSize != null ? void 0 : iconClass
1552
- ),
1553
- style: isCustomSize ? { width: `${iconSize}px`, height: `${iconSize}px` } : void 0,
1554
- children: loadingVariant === "loading-indicator" ? /* @__PURE__ */ jsx(
1555
- LoadingIndicator,
1556
- {
1557
- size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1558
- color: "currentColor",
1559
- "aria-label": "Loading"
1560
- }
1561
- ) : /* @__PURE__ */ jsx(
1562
- ProgressIndicator,
1563
- {
1564
- variant: "circular",
1565
- size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1566
- color: "currentColor",
1567
- trackColor: "transparent",
1568
- "aria-label": "Loading"
1569
- }
1570
- )
1571
- }),
1572
- "loading"
1573
- ) : /* @__PURE__ */ jsx(
1574
- m.span,
1575
- __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1576
- transition: SPRING_TRANSITION,
1577
- "aria-hidden": "true",
1578
- className: cn(
1579
- "flex items-center justify-center shrink-0 [&_svg]:w-full [&_svg]:h-full [&_.md-icon]:text-[length:inherit]!",
1580
- iconSize != null ? void 0 : iconClass
1581
- ),
1582
- style: {
1583
- fontSize: typeof iconPx === "number" ? `${iconPx}px` : iconPx,
1584
- width: isCustomSize ? `${iconSize}px` : void 0,
1585
- height: isCustomSize ? `${iconSize}px` : void 0
1586
- },
1587
- children
1588
- }),
1589
- "content"
1590
- ) })
1591
- ]
1660
+ children: innerContent
1592
1661
  })
1593
1662
  ) });
1594
1663
  }
@@ -2469,6 +2538,8 @@ function MD3ThemeProvider({
2469
2538
  sourceColor: initialSourceColor = "#6750A4",
2470
2539
  defaultMode = "light",
2471
2540
  persistToLocalStorage = false,
2541
+ variant = "expressive",
2542
+ contrastLevel = 0,
2472
2543
  typography: typographyProp,
2473
2544
  fontFamily,
2474
2545
  fontVariationAxes,
@@ -2477,6 +2548,17 @@ function MD3ThemeProvider({
2477
2548
  const [sourceColor, setSourceColor] = useState(initialSourceColor);
2478
2549
  const [mode, setMode] = useState(defaultMode);
2479
2550
  const [isHydrated, setIsHydrated] = useState(!persistToLocalStorage);
2551
+ const [systemMode, setSystemMode] = useState("light");
2552
+ useEffect(() => {
2553
+ if (typeof window === "undefined") return;
2554
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
2555
+ const updateSystemMode = () => {
2556
+ setSystemMode(mediaQuery.matches ? "dark" : "light");
2557
+ };
2558
+ updateSystemMode();
2559
+ mediaQuery.addEventListener("change", updateSystemMode);
2560
+ return () => mediaQuery.removeEventListener("change", updateSystemMode);
2561
+ }, []);
2480
2562
  useEffect(() => {
2481
2563
  if (!persistToLocalStorage) return;
2482
2564
  const savedColor = localStorage.getItem(STORAGE_KEY_COLOR);
@@ -2488,25 +2570,37 @@ function MD3ThemeProvider({
2488
2570
  setMode(savedMode);
2489
2571
  setIsHydrated(true);
2490
2572
  }, [persistToLocalStorage]);
2573
+ const effectiveMode = mode === "system" ? systemMode : mode;
2491
2574
  useEffect(() => {
2492
2575
  if (!isHydrated) return;
2493
- applyTheme(sourceColor, mode);
2576
+ applyTheme(sourceColor, effectiveMode, document.documentElement, {
2577
+ variant,
2578
+ contrastLevel
2579
+ });
2494
2580
  if (persistToLocalStorage) {
2495
2581
  localStorage.setItem(STORAGE_KEY_COLOR, sourceColor);
2496
2582
  localStorage.setItem(STORAGE_KEY_MODE, mode);
2497
2583
  }
2498
- }, [sourceColor, mode, persistToLocalStorage, isHydrated]);
2499
- useEffect(() => {
2500
- if (mode !== "system" || typeof window === "undefined") return;
2501
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
2502
- const handleChange = () => applyTheme(sourceColor, "system");
2503
- mediaQuery.addEventListener("change", handleChange);
2504
- return () => mediaQuery.removeEventListener("change", handleChange);
2505
- }, [mode, sourceColor]);
2506
- const effectiveMode = resolveMode(mode);
2584
+ }, [
2585
+ sourceColor,
2586
+ mode,
2587
+ effectiveMode,
2588
+ persistToLocalStorage,
2589
+ isHydrated,
2590
+ variant,
2591
+ contrastLevel
2592
+ ]);
2507
2593
  const themeValue = useMemo(
2508
- () => ({ sourceColor, setSourceColor, mode, setMode, effectiveMode }),
2509
- [sourceColor, mode, effectiveMode]
2594
+ () => ({
2595
+ sourceColor,
2596
+ setSourceColor,
2597
+ mode,
2598
+ setMode,
2599
+ effectiveMode,
2600
+ variant,
2601
+ contrastLevel
2602
+ }),
2603
+ [sourceColor, mode, effectiveMode, variant, contrastLevel]
2510
2604
  );
2511
2605
  const typographyValue = useMemo(() => {
2512
2606
  if (typographyProp) return typographyProp;