@kbach/react 0.3.3 → 0.3.5

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/dist/index.js CHANGED
@@ -47,10 +47,12 @@ __export(index_exports, {
47
47
  resolve: () => resolve,
48
48
  styled: () => styled,
49
49
  updateConfig: () => updateConfig,
50
+ useBreakpoint: () => useBreakpoint,
50
51
  useColors: () => useColors,
51
52
  useGlobalDarkMode: () => useGlobalDarkMode,
52
53
  useIsDark: () => useIsDark,
53
54
  useResolvedStyle: () => useResolvedStyle,
55
+ useResponsive: () => useResponsive,
54
56
  useStyles: () => useStyles,
55
57
  useTheme: () => useTheme,
56
58
  wrapColors: () => wrapColors
@@ -1966,6 +1968,48 @@ function getPluginStandaloneMap() {
1966
1968
  return PLUGIN_STANDALONE;
1967
1969
  }
1968
1970
 
1971
+ // src/core/responsiveStore.ts
1972
+ var RESPONSIVE_KEY = "__kbach_responsive__";
1973
+ function getStore() {
1974
+ const g = globalThis;
1975
+ if (!g[RESPONSIVE_KEY]) {
1976
+ g[RESPONSIVE_KEY] = { width: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
1977
+ }
1978
+ return g[RESPONSIVE_KEY];
1979
+ }
1980
+ function syncGlobalWidth(width) {
1981
+ getStore().width = width;
1982
+ }
1983
+ function syncGlobalScreens(screens) {
1984
+ getStore().screens = screens;
1985
+ }
1986
+ function getGlobalScreens() {
1987
+ return getStore().screens;
1988
+ }
1989
+ function setGlobalWidth(width) {
1990
+ const store = getStore();
1991
+ if (store.width === width) return;
1992
+ store.width = width;
1993
+ for (const l of store.listeners) l(width);
1994
+ }
1995
+ function getGlobalWidth() {
1996
+ return getStore().width;
1997
+ }
1998
+ function subscribeGlobalWidth(listener) {
1999
+ const store = getStore();
2000
+ store.listeners.add(listener);
2001
+ return () => store.listeners.delete(listener);
2002
+ }
2003
+ function getActiveBreakpoints(width) {
2004
+ const store = getStore();
2005
+ const w = width ?? store.width;
2006
+ const active = /* @__PURE__ */ new Set();
2007
+ for (const [name, minW] of Object.entries(store.screens)) {
2008
+ if (typeof minW === "number" && w >= minW) active.add(name);
2009
+ }
2010
+ return active;
2011
+ }
2012
+
1969
2013
  // src/core/resolver.ts
1970
2014
  var styleCache = new LRUCache(1e4);
1971
2015
  var _styleEl = null;
@@ -2016,6 +2060,7 @@ var GROUP_PEER_PREFIX = {
2016
2060
  "peer-focus": ".peer:focus ~"
2017
2061
  };
2018
2062
  var MODE_MODS = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
2063
+ var RESPONSIVE_MODS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2019
2064
  function injectClassRule(cls, bucketKey, styles, darkMode) {
2020
2065
  const cssDecls = styleValueToCSS(styles);
2021
2066
  if (!cssDecls) return;
@@ -2025,10 +2070,12 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
2025
2070
  return;
2026
2071
  }
2027
2072
  const mods = bucketKey.split(":");
2028
- const hasDark = mods.includes("dark") || mods.includes("not-light");
2029
- const hasLight = mods.includes("light") || mods.includes("not-dark");
2030
- const ancestorMods = mods.filter((m) => m in GROUP_PEER_PREFIX);
2031
- const pseudoMods = mods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
2073
+ const responsiveMods = mods.filter((m) => RESPONSIVE_MODS.has(m));
2074
+ const nonResponsiveMods = mods.filter((m) => !RESPONSIVE_MODS.has(m));
2075
+ const hasDark = nonResponsiveMods.includes("dark") || nonResponsiveMods.includes("not-light");
2076
+ const hasLight = nonResponsiveMods.includes("light") || nonResponsiveMods.includes("not-dark");
2077
+ const ancestorMods = nonResponsiveMods.filter((m) => m in GROUP_PEER_PREFIX);
2078
+ const pseudoMods = nonResponsiveMods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
2032
2079
  const pseudoSuffix = pseudoMods.map((p) => MODIFIER_TO_PSEUDO[p] ?? `:${p}`).join("");
2033
2080
  const elementSelector = `.${escaped}${pseudoSuffix}`;
2034
2081
  const ancestorPrefix = ancestorMods.length ? ancestorMods.map((m) => GROUP_PEER_PREFIX[m]).join(" ") + " " : "";
@@ -2053,6 +2100,13 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
2053
2100
  } else {
2054
2101
  rule = `${selector} { ${cssDecls} }`;
2055
2102
  }
2103
+ if (responsiveMods.length > 0) {
2104
+ const screens = getGlobalScreens();
2105
+ const minWidth = Math.max(...responsiveMods.map((m) => screens[m] ?? 0));
2106
+ if (minWidth > 0) {
2107
+ rule = `@media (min-width: ${minWidth}px) { ${rule} }`;
2108
+ }
2109
+ }
2056
2110
  injectRule(rule);
2057
2111
  }
2058
2112
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2121,18 +2175,18 @@ function resolve(classString, theme, darkMode = "attribute") {
2121
2175
  }
2122
2176
  return result;
2123
2177
  }
2124
- function flatten(resolved, isDark, state = {}) {
2178
+ function flatten(resolved, isDark, state = {}, breakpoints = /* @__PURE__ */ new Set()) {
2125
2179
  const result = { ...resolved.base ?? {} };
2126
2180
  for (const [key, styles] of Object.entries(resolved)) {
2127
2181
  if (key === "base" || !styles) continue;
2128
2182
  const mods = key.split(":");
2129
- if (matchesMods(mods, isDark, state)) {
2183
+ if (matchesMods(mods, isDark, state, breakpoints)) {
2130
2184
  Object.assign(result, styles);
2131
2185
  }
2132
2186
  }
2133
2187
  return result;
2134
2188
  }
2135
- function matchesMods(mods, isDark, state) {
2189
+ function matchesMods(mods, isDark, state, breakpoints) {
2136
2190
  return mods.every((mod) => {
2137
2191
  switch (mod) {
2138
2192
  case "dark":
@@ -2170,6 +2224,12 @@ function matchesMods(mods, isDark, state) {
2170
2224
  return !state.visited;
2171
2225
  case "placeholder":
2172
2226
  return !!state.placeholder;
2227
+ case "sm":
2228
+ case "md":
2229
+ case "lg":
2230
+ case "xl":
2231
+ case "2xl":
2232
+ return breakpoints.has(mod);
2173
2233
  default:
2174
2234
  return false;
2175
2235
  }
@@ -2210,6 +2270,28 @@ function getConfig() {
2210
2270
  store.resolved = buildConfig({});
2211
2271
  return store.resolved;
2212
2272
  }
2273
+ function resolveOneRef(ref, colors) {
2274
+ const m = /^([a-z][a-z0-9]*)-(\d+)$/.exec(ref);
2275
+ if (!m) return null;
2276
+ const entry = colors[m[1]];
2277
+ if (!entry || typeof entry !== "object") return null;
2278
+ return entry[m[2]] ?? null;
2279
+ }
2280
+ function resolveColorRefs(colors) {
2281
+ const out = {};
2282
+ for (const [key, val] of Object.entries(colors)) {
2283
+ if (typeof val === "string") {
2284
+ out[key] = resolveOneRef(val, colors) ?? val;
2285
+ } else {
2286
+ const shades = {};
2287
+ for (const [shade, hex] of Object.entries(val)) {
2288
+ shades[shade] = resolveOneRef(hex, colors) ?? hex;
2289
+ }
2290
+ out[key] = shades;
2291
+ }
2292
+ }
2293
+ return out;
2294
+ }
2213
2295
  function buildConfig(userConfig) {
2214
2296
  let theme = { ...defaultTheme };
2215
2297
  if (userConfig.theme) {
@@ -2228,6 +2310,7 @@ function buildConfig(userConfig) {
2228
2310
  }
2229
2311
  }
2230
2312
  }
2313
+ theme = { ...theme, colors: resolveColorRefs(theme.colors) };
2231
2314
  const resolved = {
2232
2315
  darkMode: userConfig.darkMode ?? "attribute",
2233
2316
  theme,
@@ -2306,7 +2389,7 @@ var customVariants = new Proxy(
2306
2389
 
2307
2390
  // src/core/darkModeStore.ts
2308
2391
  var KEY = "__kbach_dark_store__";
2309
- function getStore() {
2392
+ function getStore2() {
2310
2393
  const g = globalThis;
2311
2394
  if (!g[KEY]) {
2312
2395
  g[KEY] = { isDark: false, lastNotified: false, subscribers: /* @__PURE__ */ new Set() };
@@ -2314,20 +2397,20 @@ function getStore() {
2314
2397
  return g[KEY];
2315
2398
  }
2316
2399
  function syncGlobalDarkMode(isDark) {
2317
- getStore().isDark = isDark;
2400
+ getStore2().isDark = isDark;
2318
2401
  }
2319
2402
  function setGlobalDarkMode(isDark) {
2320
- const store = getStore();
2403
+ const store = getStore2();
2321
2404
  store.isDark = isDark;
2322
2405
  if (store.lastNotified === isDark) return;
2323
2406
  store.lastNotified = isDark;
2324
2407
  for (const sub of store.subscribers) sub();
2325
2408
  }
2326
2409
  function getGlobalDarkMode() {
2327
- return getStore().isDark;
2410
+ return getStore2().isDark;
2328
2411
  }
2329
2412
  function subscribeGlobalDarkMode(callback) {
2330
- const store = getStore();
2413
+ const store = getStore2();
2331
2414
  store.subscribers.add(callback);
2332
2415
  return () => store.subscribers.delete(callback);
2333
2416
  }
@@ -2363,12 +2446,36 @@ function ThemeProvider({
2363
2446
  children,
2364
2447
  defaultMode = "system",
2365
2448
  colorScheme,
2449
+ windowWidth: windowWidthProp,
2366
2450
  config: configOverride,
2367
2451
  disablePersistence = false
2368
2452
  }) {
2369
2453
  const [resolvedConfig, setResolvedConfig] = (0, import_react3.useState)(
2370
2454
  () => configOverride ? buildConfig(configOverride) : getConfig()
2371
2455
  );
2456
+ const [webWidth, setWebWidth] = (0, import_react3.useState)(
2457
+ () => isWeb && typeof window !== "undefined" ? window.innerWidth : 0
2458
+ );
2459
+ const effectiveWidth = windowWidthProp ?? (isWeb ? webWidth : 0);
2460
+ syncGlobalWidth(effectiveWidth);
2461
+ syncGlobalScreens(resolvedConfig.theme.screens);
2462
+ (0, import_react3.useEffect)(() => {
2463
+ if (!isWeb || typeof window === "undefined") return;
2464
+ let raf = 0;
2465
+ const update = () => {
2466
+ cancelAnimationFrame(raf);
2467
+ raf = requestAnimationFrame(() => {
2468
+ const w = window.innerWidth;
2469
+ setWebWidth(w);
2470
+ setGlobalWidth(w);
2471
+ });
2472
+ };
2473
+ window.addEventListener("resize", update);
2474
+ return () => {
2475
+ window.removeEventListener("resize", update);
2476
+ cancelAnimationFrame(raf);
2477
+ };
2478
+ }, []);
2372
2479
  const [webScheme, setWebScheme] = (0, import_react3.useState)(() => {
2373
2480
  if (isWeb && typeof window !== "undefined") {
2374
2481
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
@@ -2637,33 +2744,59 @@ function styled(Component, baseClasses = "") {
2637
2744
  }
2638
2745
 
2639
2746
  // src/useStyles.ts
2640
- var import_react6 = require("react");
2747
+ var import_react7 = require("react");
2748
+
2749
+ // src/useGlobalWidth.ts
2750
+ var import_react6 = __toESM(require("react"));
2751
+ var useSyncExternalStore = import_react6.default.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
2752
+ const [, forceUpdate] = import_react6.default.useReducer((n) => n + 1, 0);
2753
+ import_react6.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
2754
+ return getSnapshot();
2755
+ };
2756
+ var NOOP_SUB = (_) => () => {
2757
+ };
2758
+ var ZERO_SNAP = () => 0;
2759
+ var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
2760
+ function useGlobalWidth() {
2761
+ return useSyncExternalStore(subscribeGlobalWidth, getGlobalWidth, ZERO_SNAP);
2762
+ }
2763
+ function useConditionalWidth(active) {
2764
+ return useSyncExternalStore(
2765
+ active ? subscribeGlobalWidth : NOOP_SUB,
2766
+ active ? getGlobalWidth : ZERO_SNAP,
2767
+ ZERO_SNAP
2768
+ );
2769
+ }
2770
+
2771
+ // src/useStyles.ts
2641
2772
  function useStyles(classString, state = {}) {
2642
2773
  const { isDark, config } = useTheme();
2774
+ const width = useGlobalWidth();
2775
+ const breakpoints = isWeb ? EMPTY_BREAKPOINTS : getActiveBreakpoints(width);
2643
2776
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
2644
- return (0, import_react6.useMemo)(() => {
2777
+ return (0, import_react7.useMemo)(() => {
2645
2778
  const resolved = resolve(normalised, config.theme, config.darkMode);
2646
- return flatten(resolved, isDark, state);
2647
- }, [normalised, isDark, config, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
2779
+ return flatten(resolved, isDark, state, breakpoints);
2780
+ }, [normalised, isDark, config, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
2648
2781
  }
2649
2782
  function useResolvedStyle(classString) {
2650
2783
  const { config } = useTheme();
2651
2784
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
2652
- return (0, import_react6.useMemo)(
2785
+ return (0, import_react7.useMemo)(
2653
2786
  () => resolve(normalised, config.theme, config.darkMode),
2654
2787
  [normalised, config]
2655
2788
  );
2656
2789
  }
2657
2790
 
2658
2791
  // src/useGlobalDarkMode.ts
2659
- var import_react7 = __toESM(require("react"));
2660
- var useSyncExternalStore = import_react7.default.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
2661
- const [, forceUpdate] = import_react7.default.useReducer((n) => n + 1, 0);
2662
- import_react7.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
2792
+ var import_react8 = __toESM(require("react"));
2793
+ var useSyncExternalStore2 = import_react8.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
2794
+ const [, forceUpdate] = import_react8.default.useReducer((n) => n + 1, 0);
2795
+ import_react8.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
2663
2796
  return getSnapshot();
2664
2797
  };
2665
2798
  function useGlobalDarkMode() {
2666
- return useSyncExternalStore(
2799
+ return useSyncExternalStore2(
2667
2800
  subscribeGlobalDarkMode,
2668
2801
  getGlobalDarkMode,
2669
2802
  () => false
@@ -2671,15 +2804,47 @@ function useGlobalDarkMode() {
2671
2804
  );
2672
2805
  }
2673
2806
 
2807
+ // src/useBreakpoint.ts
2808
+ function useBreakpoint() {
2809
+ const { config } = useTheme();
2810
+ const width = useGlobalWidth();
2811
+ const screens = config.theme.screens;
2812
+ const sorted = Object.entries(screens).filter(([, v]) => typeof v === "number").sort(([, a], [, b]) => b - a);
2813
+ for (const [name, minW] of sorted) {
2814
+ if (width >= minW) return name;
2815
+ }
2816
+ return "xs";
2817
+ }
2818
+ function useResponsive() {
2819
+ const { config } = useTheme();
2820
+ const width = useGlobalWidth();
2821
+ const screens = config.theme.screens;
2822
+ const result = {};
2823
+ for (const [name, minW] of Object.entries(screens)) {
2824
+ if (typeof minW === "number") result[name] = width >= minW;
2825
+ }
2826
+ return result;
2827
+ }
2828
+
2674
2829
  // src/InteractiveWrapper.tsx
2675
- var import_react8 = __toESM(require("react"));
2830
+ var import_react9 = __toESM(require("react"));
2676
2831
  function chain(original, extra) {
2677
2832
  return (...args) => {
2678
2833
  original?.(...args);
2679
2834
  extra();
2680
2835
  };
2681
2836
  }
2682
- var InteractiveWrapper = (0, import_react8.forwardRef)(
2837
+ var RESPONSIVE_MODS2 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2838
+ function hasResponsiveBuckets(resolved) {
2839
+ for (const key of Object.keys(resolved)) {
2840
+ if (key === "base") continue;
2841
+ for (const mod of key.split(":")) {
2842
+ if (RESPONSIVE_MODS2.has(mod)) return true;
2843
+ }
2844
+ }
2845
+ return false;
2846
+ }
2847
+ var InteractiveWrapper = (0, import_react9.forwardRef)(
2683
2848
  function InteractiveWrapper2({
2684
2849
  Component,
2685
2850
  resolvedStyle,
@@ -2694,18 +2859,22 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
2694
2859
  ...rest
2695
2860
  }, ref) {
2696
2861
  const isDark = useGlobalDarkMode();
2697
- const [pressed, setPressed] = (0, import_react8.useState)(false);
2698
- const [hovered, setHovered] = (0, import_react8.useState)(false);
2699
- const [focused, setFocused] = (0, import_react8.useState)(false);
2700
- const handlePressIn = (0, import_react8.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2701
- const handlePressOut = (0, import_react8.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2702
- const handleMouseEnter = (0, import_react8.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2703
- const handleMouseLeave = (0, import_react8.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2704
- const handleFocus = (0, import_react8.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2705
- const handleBlur = (0, import_react8.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2706
- const computedStyle = (0, import_react8.useMemo)(
2707
- () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }),
2708
- [resolvedStyle, isDark, pressed, hovered, focused]
2862
+ const needsWidth = !isWeb && hasResponsiveBuckets(resolvedStyle);
2863
+ const width = useConditionalWidth(needsWidth);
2864
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
2865
+ const [pressed, setPressed] = (0, import_react9.useState)(false);
2866
+ const [hovered, setHovered] = (0, import_react9.useState)(false);
2867
+ const [focused, setFocused] = (0, import_react9.useState)(false);
2868
+ const handlePressIn = (0, import_react9.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2869
+ const handlePressOut = (0, import_react9.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2870
+ const handleMouseEnter = (0, import_react9.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2871
+ const handleMouseLeave = (0, import_react9.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2872
+ const handleFocus = (0, import_react9.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2873
+ const handleBlur = (0, import_react9.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2874
+ const computedStyle = (0, import_react9.useMemo)(
2875
+ () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }, breakpoints),
2876
+ [resolvedStyle, isDark, pressed, hovered, focused, width]
2877
+ // eslint-disable-line react-hooks/exhaustive-deps
2709
2878
  );
2710
2879
  const finalStyle = styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
2711
2880
  const { children, ...restWithoutChildren } = rest;
@@ -2721,7 +2890,7 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
2721
2890
  onFocus: handleFocus,
2722
2891
  onBlur: handleBlur
2723
2892
  };
2724
- return Array.isArray(children) ? import_react8.default.createElement(Component, componentProps, ...children) : import_react8.default.createElement(Component, componentProps, children);
2893
+ return Array.isArray(children) ? import_react9.default.createElement(Component, componentProps, ...children) : import_react9.default.createElement(Component, componentProps, children);
2725
2894
  }
2726
2895
  );
2727
2896
  InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
@@ -2757,10 +2926,12 @@ function cx(...classes) {
2757
2926
  resolve,
2758
2927
  styled,
2759
2928
  updateConfig,
2929
+ useBreakpoint,
2760
2930
  useColors,
2761
2931
  useGlobalDarkMode,
2762
2932
  useIsDark,
2763
2933
  useResolvedStyle,
2934
+ useResponsive,
2764
2935
  useStyles,
2765
2936
  useTheme,
2766
2937
  wrapColors
package/dist/index.mjs CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
+ EMPTY_BREAKPOINTS,
2
3
  InteractiveWrapper,
3
4
  __require,
4
5
  buildConfig,
5
6
  defaultColors,
6
7
  defaultTheme,
7
8
  flatten,
9
+ getActiveBreakpoints,
8
10
  getConfig,
9
11
  initConfig,
10
12
  isWeb,
@@ -13,10 +15,14 @@ import {
13
15
  parseClasses,
14
16
  resolve,
15
17
  setGlobalDarkMode,
18
+ setGlobalWidth,
16
19
  syncGlobalDarkMode,
20
+ syncGlobalScreens,
21
+ syncGlobalWidth,
17
22
  updateConfig,
18
- useGlobalDarkMode
19
- } from "./chunk-7XL6VQDW.mjs";
23
+ useGlobalDarkMode,
24
+ useGlobalWidth
25
+ } from "./chunk-6YT4ECUI.mjs";
20
26
 
21
27
  // src/context.tsx
22
28
  import { createContext, useContext } from "react";
@@ -129,12 +135,36 @@ function ThemeProvider({
129
135
  children,
130
136
  defaultMode = "system",
131
137
  colorScheme,
138
+ windowWidth: windowWidthProp,
132
139
  config: configOverride,
133
140
  disablePersistence = false
134
141
  }) {
135
142
  const [resolvedConfig, setResolvedConfig] = useState(
136
143
  () => configOverride ? buildConfig(configOverride) : getConfig()
137
144
  );
145
+ const [webWidth, setWebWidth] = useState(
146
+ () => isWeb && typeof window !== "undefined" ? window.innerWidth : 0
147
+ );
148
+ const effectiveWidth = windowWidthProp ?? (isWeb ? webWidth : 0);
149
+ syncGlobalWidth(effectiveWidth);
150
+ syncGlobalScreens(resolvedConfig.theme.screens);
151
+ useEffect(() => {
152
+ if (!isWeb || typeof window === "undefined") return;
153
+ let raf = 0;
154
+ const update = () => {
155
+ cancelAnimationFrame(raf);
156
+ raf = requestAnimationFrame(() => {
157
+ const w = window.innerWidth;
158
+ setWebWidth(w);
159
+ setGlobalWidth(w);
160
+ });
161
+ };
162
+ window.addEventListener("resize", update);
163
+ return () => {
164
+ window.removeEventListener("resize", update);
165
+ cancelAnimationFrame(raf);
166
+ };
167
+ }, []);
138
168
  const [webScheme, setWebScheme] = useState(() => {
139
169
  if (isWeb && typeof window !== "undefined") {
140
170
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
@@ -411,11 +441,13 @@ function styled(Component, baseClasses = "") {
411
441
  import { useMemo as useMemo4 } from "react";
412
442
  function useStyles(classString, state = {}) {
413
443
  const { isDark, config } = useTheme();
444
+ const width = useGlobalWidth();
445
+ const breakpoints = isWeb ? EMPTY_BREAKPOINTS : getActiveBreakpoints(width);
414
446
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
415
447
  return useMemo4(() => {
416
448
  const resolved = resolve(normalised, config.theme, config.darkMode);
417
- return flatten(resolved, isDark, state);
418
- }, [normalised, isDark, config, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
449
+ return flatten(resolved, isDark, state, breakpoints);
450
+ }, [normalised, isDark, config, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
419
451
  }
420
452
  function useResolvedStyle(classString) {
421
453
  const { config } = useTheme();
@@ -426,6 +458,28 @@ function useResolvedStyle(classString) {
426
458
  );
427
459
  }
428
460
 
461
+ // src/useBreakpoint.ts
462
+ function useBreakpoint() {
463
+ const { config } = useTheme();
464
+ const width = useGlobalWidth();
465
+ const screens = config.theme.screens;
466
+ const sorted = Object.entries(screens).filter(([, v]) => typeof v === "number").sort(([, a], [, b]) => b - a);
467
+ for (const [name, minW] of sorted) {
468
+ if (width >= minW) return name;
469
+ }
470
+ return "xs";
471
+ }
472
+ function useResponsive() {
473
+ const { config } = useTheme();
474
+ const width = useGlobalWidth();
475
+ const screens = config.theme.screens;
476
+ const result = {};
477
+ for (const [name, minW] of Object.entries(screens)) {
478
+ if (typeof minW === "number") result[name] = width >= minW;
479
+ }
480
+ return result;
481
+ }
482
+
429
483
  // src/kb.ts
430
484
  function kb(classString, isDark = false) {
431
485
  const config = getConfig();
@@ -456,10 +510,12 @@ export {
456
510
  resolve,
457
511
  styled,
458
512
  updateConfig,
513
+ useBreakpoint,
459
514
  useColors,
460
515
  useGlobalDarkMode,
461
516
  useIsDark,
462
517
  useResolvedStyle,
518
+ useResponsive,
463
519
  useStyles,
464
520
  useTheme,
465
521
  wrapColors