@kbach/ui 0.1.0-beta.0

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.mjs ADDED
@@ -0,0 +1,589 @@
1
+ 'use client';
2
+ import {
3
+ BASE_RESET,
4
+ EMPTY_BREAKPOINTS,
5
+ InteractiveWrapper,
6
+ RESET_STYLE_ID,
7
+ buildConfig,
8
+ chain,
9
+ clearCache,
10
+ composeNativeStyle,
11
+ defaultColors,
12
+ defaultTheme,
13
+ disableRuntimeCSS,
14
+ flatten,
15
+ generateKbachTypesDts,
16
+ getActiveBreakpoints,
17
+ getConfig,
18
+ getEffectiveIsWeb,
19
+ getGlobalScreens,
20
+ getImpliedRNClasses,
21
+ getWebTag,
22
+ hasInteractiveBuckets,
23
+ hasResponsiveBuckets,
24
+ initConfig,
25
+ isModeAwareColor,
26
+ isNative,
27
+ isWeb,
28
+ kbachWarn,
29
+ normalizeClassString,
30
+ onConfigChange,
31
+ parseClass,
32
+ parseClasses,
33
+ parseHexRgb,
34
+ registerWebElement,
35
+ resetConfig,
36
+ resolve,
37
+ setGlobalDarkMode,
38
+ setGlobalWidth,
39
+ setResolveTarget,
40
+ splitClassTokens,
41
+ syncGlobalDarkMode,
42
+ syncGlobalScreens,
43
+ syncGlobalWidth,
44
+ transformToWebProps,
45
+ updateConfig,
46
+ useConditionalGlobalDarkMode,
47
+ useConditionalWidth,
48
+ useGlobalDarkMode,
49
+ useGlobalWidth,
50
+ useSyncExternalStore
51
+ } from "./chunk-BPCFICND.mjs";
52
+
53
+ // src/context.tsx
54
+ import { createContext, useContext } from "react";
55
+ var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = createContext(null));
56
+ function useTheme() {
57
+ const ctx = useContext(ThemeContext);
58
+ if (!ctx) {
59
+ throw new Error(
60
+ "[Kbach] useTheme() must be called inside a <ThemeProvider>. Wrap your app root with <ThemeProvider>."
61
+ );
62
+ }
63
+ return ctx;
64
+ }
65
+ function useIsDark() {
66
+ return useTheme().isDark;
67
+ }
68
+
69
+ // src/useColors.ts
70
+ import { useMemo } from "react";
71
+ function applyOpacity(color, opacity) {
72
+ const a = Math.max(0, Math.min(1, opacity / 100));
73
+ if (color.startsWith("#")) {
74
+ const rgb = parseHexRgb(color);
75
+ if (!rgb) return color;
76
+ const [r, g, b] = rgb;
77
+ return `rgba(${r},${g},${b},${a})`;
78
+ }
79
+ if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
80
+ if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
81
+ return color;
82
+ }
83
+ function pickSide(value, isDark) {
84
+ return isModeAwareColor(value) ? isDark ? value.dark : value.light : value;
85
+ }
86
+ function makeShadeProxy(shades, isDark) {
87
+ return new Proxy(shades, {
88
+ get(target, prop) {
89
+ const key = String(prop);
90
+ if (key === "then") return void 0;
91
+ if (key.includes("/")) {
92
+ const slash = key.indexOf("/");
93
+ const shade = key.slice(0, slash);
94
+ const op = Number(key.slice(slash + 1));
95
+ const color2 = target[shade];
96
+ return color2 !== void 0 ? applyOpacity(pickSide(color2, isDark), op) : void 0;
97
+ }
98
+ const color = target[key];
99
+ return color !== void 0 ? pickSide(color, isDark) : void 0;
100
+ }
101
+ });
102
+ }
103
+ function wrapColors(rawColors, isDark = false) {
104
+ const cache = /* @__PURE__ */ new Map();
105
+ const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
106
+ return new Proxy({ alpha }, {
107
+ get(_, prop) {
108
+ const key = String(prop);
109
+ if (key === "then") return void 0;
110
+ if (key === "alpha") return alpha;
111
+ if (key.includes("/")) {
112
+ const slash = key.indexOf("/");
113
+ const name = key.slice(0, slash);
114
+ const op = Number(key.slice(slash + 1));
115
+ const entry2 = rawColors[name];
116
+ return entry2 !== void 0 && (typeof entry2 === "string" || isModeAwareColor(entry2)) ? applyOpacity(pickSide(entry2, isDark), op) : void 0;
117
+ }
118
+ const entry = rawColors[key];
119
+ if (entry === void 0) return void 0;
120
+ if (typeof entry === "string" || isModeAwareColor(entry)) return pickSide(entry, isDark);
121
+ const cacheKey = `${key}:${isDark}`;
122
+ if (!cache.has(cacheKey)) cache.set(cacheKey, makeShadeProxy(entry, isDark));
123
+ return cache.get(cacheKey);
124
+ }
125
+ });
126
+ }
127
+ function useColors() {
128
+ const { config, isDark } = useTheme();
129
+ return useMemo(() => wrapColors(config.theme.colors, isDark), [config.theme.colors, isDark]);
130
+ }
131
+
132
+ // src/useSpacing.ts
133
+ import { useMemo as useMemo2 } from "react";
134
+ function wrapSpacing(rawSpacing) {
135
+ return rawSpacing;
136
+ }
137
+ function useSpacing() {
138
+ const { config } = useTheme();
139
+ return useMemo2(() => wrapSpacing(config.theme.spacing), [config.theme.spacing]);
140
+ }
141
+
142
+ // src/ThemeProvider.tsx
143
+ import {
144
+ useCallback,
145
+ useEffect,
146
+ useLayoutEffect,
147
+ useMemo as useMemo3,
148
+ useRef,
149
+ useState
150
+ } from "react";
151
+ import { jsx } from "react/jsx-runtime";
152
+ var useIsomorphicLayoutEffect = isWeb || isNative ? useLayoutEffect : useEffect;
153
+ var STORAGE_KEY = "kbach-theme";
154
+ var _mountedProviderCount = 0;
155
+ var _warnedMultipleProviders = false;
156
+ var _mountedConfigOverrideCount = 0;
157
+ var _warnedConfigOverride = false;
158
+ var _warnedMediaModeToggle = false;
159
+ function loadPersistedMode() {
160
+ try {
161
+ if (isWeb) return localStorage.getItem(STORAGE_KEY);
162
+ return null;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ function persistMode(mode) {
168
+ try {
169
+ if (isWeb) localStorage.setItem(STORAGE_KEY, mode);
170
+ } catch {
171
+ }
172
+ }
173
+ function subscribeSystemScheme(callback) {
174
+ if (!isWeb || typeof window === "undefined") return () => {
175
+ };
176
+ const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
177
+ if (!mq) return () => {
178
+ };
179
+ mq.addEventListener("change", callback);
180
+ return () => mq.removeEventListener("change", callback);
181
+ }
182
+ function getSystemScheme() {
183
+ if (!isWeb || typeof window === "undefined") return "light";
184
+ return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
185
+ }
186
+ function getSystemSchemeServerSnapshot() {
187
+ return "light";
188
+ }
189
+ function applyWebTheme(resolvedMode, strategy) {
190
+ if (!isWeb) return;
191
+ const root = document.documentElement;
192
+ if (strategy === "attribute") {
193
+ root.setAttribute("data-theme", resolvedMode);
194
+ } else if (strategy === "class") {
195
+ root.classList.toggle("dark", resolvedMode === "dark");
196
+ root.classList.toggle("light", resolvedMode === "light");
197
+ }
198
+ }
199
+ function ThemeProvider({
200
+ children,
201
+ defaultMode = "system",
202
+ colorScheme,
203
+ windowWidth: windowWidthProp,
204
+ config: configOverride,
205
+ disablePersistence = false
206
+ }) {
207
+ const [resolvedConfig, setResolvedConfig] = useState(
208
+ () => configOverride ? buildConfig(configOverride) : getConfig()
209
+ );
210
+ const _prevConfigOverrideRef = useRef(void 0);
211
+ if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
212
+ _prevConfigOverrideRef.current = configOverride;
213
+ updateConfig(configOverride);
214
+ const fresh = getConfig();
215
+ if (fresh !== resolvedConfig) setResolvedConfig(fresh);
216
+ }
217
+ useEffect(() => {
218
+ _mountedProviderCount++;
219
+ if (configOverride) _mountedConfigOverrideCount++;
220
+ if (process.env.NODE_ENV !== "production") {
221
+ if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
222
+ _warnedMultipleProviders = true;
223
+ kbachWarn(
224
+ "Multiple <ThemeProvider> instances are mounted at once. Dark mode and responsive width are shared through one global store, so whichever provider rendered most recently wins for every consumer \u2014 nested or per-section theming is not isolated between providers."
225
+ );
226
+ }
227
+ if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
228
+ _warnedConfigOverride = true;
229
+ kbachWarn(
230
+ "Multiple <ThemeProvider config={...}> overrides are mounted at once. The resolved config is shared through the same global store as dark mode/width (see the multiple-providers warning), so per-tree config overrides are not actually isolated between providers \u2014 plain className/kb elements with no hover:/dark:/sm: modifier resolve against whichever override last committed, not necessarily their nearest ancestor <ThemeProvider>."
231
+ );
232
+ }
233
+ }
234
+ return () => {
235
+ _mountedProviderCount--;
236
+ if (configOverride) _mountedConfigOverrideCount--;
237
+ if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
238
+ resetConfig();
239
+ }
240
+ };
241
+ }, []);
242
+ const [webWidth, setWebWidth] = useState(0);
243
+ const effectiveWidth = getEffectiveIsWeb() ? webWidth : windowWidthProp ?? 0;
244
+ const numericScreens = useMemo3(() => {
245
+ const out = {};
246
+ for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
247
+ out[k] = typeof v === "number" ? v : parseInt(String(v), 10);
248
+ }
249
+ return out;
250
+ }, [resolvedConfig.theme.screens]);
251
+ const _prevWidthRef = useRef(-1);
252
+ if (_prevWidthRef.current !== effectiveWidth) {
253
+ _prevWidthRef.current = effectiveWidth;
254
+ syncGlobalWidth(effectiveWidth);
255
+ }
256
+ const _prevScreensRef = useRef(null);
257
+ if (_prevScreensRef.current !== numericScreens) {
258
+ _prevScreensRef.current = numericScreens;
259
+ syncGlobalScreens(numericScreens);
260
+ }
261
+ useEffect(() => {
262
+ setGlobalWidth(effectiveWidth);
263
+ }, [effectiveWidth]);
264
+ useEffect(() => {
265
+ if (!isWeb || typeof window === "undefined") return;
266
+ setWebWidth(window.innerWidth);
267
+ let raf = 0;
268
+ const update = () => {
269
+ cancelAnimationFrame(raf);
270
+ raf = requestAnimationFrame(() => {
271
+ const w = window.innerWidth;
272
+ setWebWidth(w);
273
+ setGlobalWidth(w);
274
+ });
275
+ };
276
+ window.addEventListener("resize", update);
277
+ return () => {
278
+ window.removeEventListener("resize", update);
279
+ cancelAnimationFrame(raf);
280
+ };
281
+ }, []);
282
+ const webScheme = useSyncExternalStore(
283
+ subscribeSystemScheme,
284
+ getSystemScheme,
285
+ getSystemSchemeServerSnapshot
286
+ );
287
+ const systemScheme = getEffectiveIsWeb() ? webScheme : colorScheme === "dark" ? "dark" : "light";
288
+ const [mode, _setMode] = useState(defaultMode);
289
+ useEffect(() => {
290
+ if (disablePersistence) return;
291
+ const persisted = loadPersistedMode();
292
+ if (persisted) _setMode(persisted);
293
+ }, []);
294
+ useEffect(() => {
295
+ if (disablePersistence || !isWeb || typeof window === "undefined") return;
296
+ const onStorage = (e) => {
297
+ if (e.key !== STORAGE_KEY || e.newValue == null) return;
298
+ if (e.newValue === "light" || e.newValue === "dark" || e.newValue === "system") {
299
+ _setMode(e.newValue);
300
+ }
301
+ };
302
+ window.addEventListener("storage", onStorage);
303
+ return () => window.removeEventListener("storage", onStorage);
304
+ }, [disablePersistence]);
305
+ const setMode = useCallback((next) => {
306
+ if (process.env.NODE_ENV !== "production" && isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
307
+ _warnedMediaModeToggle = true;
308
+ kbachWarn(
309
+ 'setMode()/toggle() was called while darkMode is "media" \u2014 the resolved mode/isDark from useTheme()/useIsDark() will update, but the actual CSS stays driven by prefers-color-scheme and will not change to match. Use darkMode: "attribute" or "class" if the app needs manual toggling.'
310
+ );
311
+ }
312
+ _setMode(next);
313
+ if (!disablePersistence) persistMode(next);
314
+ }, [disablePersistence, resolvedConfig.darkMode]);
315
+ const toggle = useCallback(() => {
316
+ setMode(mode === "dark" || mode === "system" && systemScheme === "dark" ? "light" : "dark");
317
+ }, [mode, systemScheme, setMode]);
318
+ const resolvedMode = mode === "system" ? systemScheme : mode;
319
+ const isDark = resolvedMode === "dark";
320
+ syncGlobalDarkMode(isDark);
321
+ useIsomorphicLayoutEffect(() => {
322
+ applyWebTheme(resolvedMode, resolvedConfig.darkMode);
323
+ setGlobalDarkMode(isDark);
324
+ }, [isDark, resolvedMode, resolvedConfig.darkMode]);
325
+ useEffect(() => {
326
+ if (configOverride) return;
327
+ let mounted = true;
328
+ const unsub = onConfigChange((config) => {
329
+ if (mounted) setResolvedConfig(config);
330
+ });
331
+ return () => {
332
+ mounted = false;
333
+ unsub();
334
+ };
335
+ }, [configOverride]);
336
+ const contextValue = useMemo3(
337
+ () => ({ mode, resolvedMode, isDark, setMode, toggle, config: resolvedConfig }),
338
+ [mode, resolvedMode, isDark, setMode, toggle, resolvedConfig]
339
+ );
340
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
341
+ }
342
+
343
+ // src/KbachReset.tsx
344
+ import { jsx as jsx2 } from "react/jsx-runtime";
345
+ function KbachReset() {
346
+ if (isNative) return null;
347
+ return /* @__PURE__ */ jsx2("style", { id: RESET_STYLE_ID, children: BASE_RESET });
348
+ }
349
+
350
+ // src/styled.tsx
351
+ import React2, {
352
+ forwardRef,
353
+ useState as useState2,
354
+ useCallback as useCallback2,
355
+ useMemo as useMemo4
356
+ } from "react";
357
+ function styled(Component, baseClasses = "") {
358
+ const Styled = forwardRef(
359
+ (props, ref) => {
360
+ const {
361
+ kb: extraClasses,
362
+ style: styleProp,
363
+ onPressIn,
364
+ onPressOut,
365
+ onPointerDown,
366
+ onPointerUp,
367
+ onPointerLeave,
368
+ onPointerCancel,
369
+ onMouseEnter,
370
+ onMouseLeave,
371
+ onFocus,
372
+ onBlur,
373
+ ...rest
374
+ } = props;
375
+ const disabled = !!props.disabled;
376
+ const checked = !!props.checked;
377
+ const { config } = useTheme();
378
+ const isWebPlatform = getEffectiveIsWeb();
379
+ const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
380
+ const [pressed, setPressed] = useState2(false);
381
+ const [hovered, setHovered] = useState2(false);
382
+ const [focused, setFocused] = useState2(false);
383
+ const handlePressIn = useCallback2(chain(onPressIn, () => {
384
+ if (!isWebPlatform) setPressed(true);
385
+ }), [onPressIn, isWebPlatform]);
386
+ const handlePressOut = useCallback2(chain(onPressOut, () => {
387
+ if (!isWebPlatform) setPressed(false);
388
+ }), [onPressOut, isWebPlatform]);
389
+ const handlePointerDown = useCallback2(chain(onPointerDown, () => {
390
+ if (!isWebPlatform) setPressed(true);
391
+ }), [onPointerDown, isWebPlatform]);
392
+ const handlePointerUp = useCallback2(chain(onPointerUp, () => {
393
+ if (!isWebPlatform) setPressed(false);
394
+ }), [onPointerUp, isWebPlatform]);
395
+ const handlePointerLeave = useCallback2(chain(onPointerLeave, () => {
396
+ if (!isWebPlatform) setPressed(false);
397
+ }), [onPointerLeave, isWebPlatform]);
398
+ const handlePointerCancel = useCallback2(chain(onPointerCancel, () => {
399
+ if (!isWebPlatform) setPressed(false);
400
+ }), [onPointerCancel, isWebPlatform]);
401
+ const handleMouseEnter = useCallback2(chain(onMouseEnter, () => {
402
+ if (!isWebPlatform) setHovered(true);
403
+ }), [onMouseEnter, isWebPlatform]);
404
+ const handleMouseLeave = useCallback2(chain(onMouseLeave, () => {
405
+ if (!isWebPlatform) setHovered(false);
406
+ }), [onMouseLeave, isWebPlatform]);
407
+ const handleFocus = useCallback2(chain(onFocus, () => {
408
+ if (!isWebPlatform) setFocused(true);
409
+ }), [onFocus, isWebPlatform]);
410
+ const handleBlur = useCallback2(chain(onBlur, () => {
411
+ if (!isWebPlatform) setFocused(false);
412
+ }), [onBlur, isWebPlatform]);
413
+ const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
414
+ const firstPassResolved = useMemo4(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
415
+ const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
416
+ const impliedClasses = getImpliedRNClasses(webTag, firstPassResolved.base);
417
+ const finalClassStr = impliedClasses ? `${combined} ${impliedClasses}` : combined;
418
+ const resolved = useMemo4(() => resolve(finalClassStr, config.theme, config.darkMode), [finalClassStr, config.theme, config.darkMode]);
419
+ const hasInteractive = useMemo4(() => hasInteractiveBuckets(resolved), [resolved]);
420
+ const needsWidth = hasResponsiveBuckets(resolved);
421
+ const width = useConditionalWidth(needsWidth && !isWebPlatform);
422
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
423
+ const screens = getGlobalScreens();
424
+ const computedStyle = useMemo4(
425
+ () => isWebPlatform ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
426
+ [resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
427
+ // eslint-disable-line react-hooks/exhaustive-deps
428
+ );
429
+ const finalStyle = isWebPlatform ? (Array.isArray(styleProp) ? Object.assign({}, ...styleProp) : styleProp) ?? void 0 : composeNativeStyle(computedStyle, styleProp);
430
+ const effectiveComponent = webTag ?? Component;
431
+ const componentName2 = Component.displayName ?? Component.name ?? "";
432
+ const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
433
+ return React2.createElement(effectiveComponent, {
434
+ ref,
435
+ ...effectiveRest,
436
+ style: finalStyle,
437
+ // className lets injected CSS rules (group-hover:, before:, print:, etc.) match the element.
438
+ ...isWebPlatform && finalClassStr ? { className: normalizeClassString(finalClassStr) } : {},
439
+ // Only attach state-tracking handlers when interactive modifiers are present.
440
+ // Always forward user-provided handlers to avoid silently swallowing them.
441
+ // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
442
+ // and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
443
+ // getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
444
+ // a non-string RN-style reference server-side, and this used to forward onPressIn/
445
+ // onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
446
+ // covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
447
+ // non-string effectiveComponent on the web/SSR side is just as likely to be an
448
+ // ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
449
+ // wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
450
+ // reliable signal either way. Forwarding them made React DOM warn "Unknown event
451
+ // handler property" the moment any such component got an interactive modifier
452
+ // (hover:, active:, …), which is the common case, not the exception.
453
+ ...!isNative ? hasInteractive ? {
454
+ onPointerDown: handlePointerDown,
455
+ onPointerUp: handlePointerUp,
456
+ onPointerLeave: handlePointerLeave,
457
+ onPointerCancel: handlePointerCancel
458
+ } : {
459
+ onPointerDown,
460
+ onPointerUp,
461
+ onPointerLeave,
462
+ onPointerCancel
463
+ } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
464
+ onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
465
+ onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
466
+ onFocus: hasInteractive ? handleFocus : onFocus,
467
+ onBlur: hasInteractive ? handleBlur : onBlur
468
+ });
469
+ }
470
+ );
471
+ const componentName = Component.displayName ?? Component.name ?? "Component";
472
+ Styled.displayName = `Styled(${componentName})`;
473
+ return Styled;
474
+ }
475
+
476
+ // src/useStyles.ts
477
+ import { useMemo as useMemo5 } from "react";
478
+ function useStyles(classString, state = {}) {
479
+ const { isDark, config } = useTheme();
480
+ const width = useGlobalWidth();
481
+ const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
482
+ return useMemo5(() => {
483
+ const resolved = resolve(normalised, config.theme, config.darkMode);
484
+ const breakpoints = getActiveBreakpoints(width);
485
+ return flatten(resolved, isDark, state, breakpoints);
486
+ }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
487
+ }
488
+ function useResolvedStyle(classString) {
489
+ const { config } = useTheme();
490
+ const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
491
+ return useMemo5(
492
+ () => resolve(normalised, config.theme, config.darkMode),
493
+ [normalised, config]
494
+ );
495
+ }
496
+
497
+ // src/useBreakpoint.ts
498
+ import { useMemo as useMemo6 } from "react";
499
+ function toNumericScreens(screens) {
500
+ const out = {};
501
+ for (const [name, v] of Object.entries(screens)) {
502
+ const n = typeof v === "number" ? v : parseInt(String(v), 10);
503
+ if (!Number.isNaN(n)) out[name] = n;
504
+ }
505
+ return out;
506
+ }
507
+ function useBreakpoint() {
508
+ const { config } = useTheme();
509
+ const width = useGlobalWidth();
510
+ const screens = config.theme.screens;
511
+ const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
512
+ return useMemo6(() => {
513
+ const active = getActiveBreakpoints(width, numericScreens);
514
+ let best = null;
515
+ let bestMinW = -Infinity;
516
+ for (const name of active) {
517
+ const minW = numericScreens[name];
518
+ if (minW > bestMinW) {
519
+ bestMinW = minW;
520
+ best = name;
521
+ }
522
+ }
523
+ return best ?? "xs";
524
+ }, [numericScreens, width]);
525
+ }
526
+ function useResponsive() {
527
+ const { config } = useTheme();
528
+ const width = useGlobalWidth();
529
+ const screens = config.theme.screens;
530
+ const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
531
+ return useMemo6(() => {
532
+ const active = getActiveBreakpoints(width, numericScreens);
533
+ const result = {};
534
+ for (const name of Object.keys(numericScreens)) {
535
+ result[name] = active.has(name);
536
+ }
537
+ return result;
538
+ }, [numericScreens, width]);
539
+ }
540
+
541
+ // src/kb.ts
542
+ function kb(classString, isDark = false) {
543
+ const config = getConfig();
544
+ const resolved = resolve(classString, config.theme, config.darkMode);
545
+ if (getEffectiveIsWeb()) {
546
+ return classString;
547
+ }
548
+ return flatten(resolved, isDark);
549
+ }
550
+ function cx(...classes) {
551
+ return classes.filter(Boolean).join(" ");
552
+ }
553
+ export {
554
+ InteractiveWrapper,
555
+ KbachReset,
556
+ ThemeContext,
557
+ ThemeProvider,
558
+ buildConfig,
559
+ clearCache,
560
+ cx,
561
+ defaultColors,
562
+ defaultTheme,
563
+ disableRuntimeCSS,
564
+ flatten,
565
+ generateKbachTypesDts,
566
+ getConfig,
567
+ initConfig,
568
+ kb,
569
+ normalizeClassString,
570
+ parseClass,
571
+ parseClasses,
572
+ registerWebElement,
573
+ resolve,
574
+ setResolveTarget,
575
+ splitClassTokens,
576
+ styled,
577
+ updateConfig,
578
+ useBreakpoint,
579
+ useColors,
580
+ useGlobalDarkMode,
581
+ useIsDark,
582
+ useResolvedStyle,
583
+ useResponsive,
584
+ useSpacing,
585
+ useStyles,
586
+ useTheme,
587
+ wrapColors,
588
+ wrapSpacing
589
+ };
@@ -0,0 +1,21 @@
1
+ import { ReactElement } from 'react';
2
+ export { Fragment } from 'react';
3
+
4
+ /**
5
+ * @kbach/ui/jsx-dev-runtime
6
+ *
7
+ * Development variant of the custom JSX runtime.
8
+ * Babel uses jsxDEV (instead of jsx/jsxs) in dev builds.
9
+ *
10
+ * We intercept `className`/`kb` props exactly like the production runtime, but
11
+ * forward `_source` to React's own jsxDEV so React DevTools can display the
12
+ * correct file + line number for every element.
13
+ */
14
+
15
+ declare function jsxDEV(type: unknown, props: Record<string, unknown> | null, key?: string, isStaticChildren?: boolean, source?: {
16
+ fileName: string;
17
+ lineNumber: number;
18
+ columnNumber: number;
19
+ }, self?: unknown): ReactElement;
20
+
21
+ export { jsxDEV };