@kbach/react 1.0.0-beta.0 → 1.0.0-beta.10
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/README.md +216 -73
- package/dist/chunk-4Y2QXNJY.mjs +115 -0
- package/dist/{chunk-AFY36PRX.mjs → chunk-EOSN5CYG.mjs} +69 -10
- package/dist/chunk-J2M6QWJW.mjs +1385 -0
- package/dist/{chunk-VBPQAIZL.mjs → chunk-L6B4DTLD.mjs} +1 -1
- package/dist/{chunk-G3RJJNSY.mjs → chunk-ROKZPEHA.mjs} +1 -1
- package/dist/config-BQ_b-qxF.d.mts +105 -0
- package/dist/config-BQ_b-qxF.d.ts +105 -0
- package/dist/index.d.mts +65 -34
- package/dist/index.d.ts +65 -34
- package/dist/index.js +260 -44
- package/dist/index.mjs +90 -33
- package/dist/jsx-dev-runtime.js +69 -10
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +69 -10
- package/dist/jsx-runtime.mjs +3 -3
- package/dist/postcss-plugin.d.mts +36 -0
- package/dist/postcss-plugin.d.ts +36 -0
- package/dist/postcss-plugin.js +1152 -0
- package/dist/postcss-plugin.mjs +79 -0
- package/dist/vite-plugin.d.mts +108 -6
- package/dist/vite-plugin.d.ts +108 -6
- package/dist/vite-plugin.js +545 -226
- package/dist/vite-plugin.mjs +15 -1181
- package/package.json +13 -2
- package/dist/theme-BKAb78KP.d.mts +0 -20
- package/dist/theme-BKAb78KP.d.ts +0 -20
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
KbachReset: () => KbachReset,
|
|
34
34
|
ThemeProvider: () => ThemeProvider,
|
|
35
|
+
applyKbachConfig: () => applyKbachConfig,
|
|
35
36
|
defaultTheme: () => defaultTheme,
|
|
36
37
|
disableRuntimeCSS: () => disableRuntimeCSS,
|
|
37
38
|
getGlobalDarkMode: () => getGlobalDarkMode,
|
|
@@ -41,11 +42,12 @@ __export(src_exports, {
|
|
|
41
42
|
isKbachReady: () => isKbachReady,
|
|
42
43
|
isRuntimeCSSDisabled: () => isRuntimeCSSDisabled,
|
|
43
44
|
kb: () => kb,
|
|
45
|
+
resolveKbachConfig: () => resolveKbachConfig,
|
|
44
46
|
setGlobalThemeMode: () => setGlobalThemeMode,
|
|
45
47
|
setTheme: () => setTheme,
|
|
46
48
|
subscribeGlobalDarkMode: () => subscribeGlobalDarkMode,
|
|
47
49
|
toggleGlobalDarkMode: () => toggleGlobalDarkMode,
|
|
48
|
-
|
|
50
|
+
useColors: () => useColors,
|
|
49
51
|
useTheme: () => useTheme
|
|
50
52
|
});
|
|
51
53
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -357,21 +359,54 @@ var defaultTheme = {
|
|
|
357
359
|
...PALETTE,
|
|
358
360
|
surface: { light: "#f9fafb", dark: "#111827" }
|
|
359
361
|
},
|
|
362
|
+
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
363
|
+
// `n * 4px` (Tailwind's base spacing unit), including the fractional
|
|
364
|
+
// `.5` steps (`1.5` -> 6px, `2.5` -> 10px, `3.5` -> 14px) and the "px"
|
|
365
|
+
// literal (a bare 1px, for `p-px`-style hairline spacing). This table is
|
|
366
|
+
// only the "usual stops", though — the Rust/JS engines' shared
|
|
367
|
+
// `spacing_px`/`spacingPx` helper falls back to Tailwind v4's own live
|
|
368
|
+
// `n * 4px` formula for any bare numeric step NOT listed here (`p-0.25`,
|
|
369
|
+
// `gap-1.75`, `w-13`, ...), so this table exists to name the common
|
|
370
|
+
// values and give a theme customization something to override, not to
|
|
371
|
+
// enumerate every resolvable step. "px" is the one exception that must
|
|
372
|
+
// stay listed explicitly — it isn't a number, so the formula can't
|
|
373
|
+
// derive it.
|
|
360
374
|
spacing: {
|
|
361
|
-
// "0" is always a hardcoded zero in real Tailwind's own scale too,
|
|
362
|
-
// regardless of the base spacing unit — needed for `p-0`/`m-0`/
|
|
363
|
-
// `top-0`/`inset-0`/etc., and specifically for `sticky top-0` (a
|
|
364
|
-
// sticky element needs an explicit offset to have any stick point at
|
|
365
|
-
// all; without one it behaves just like `relative`, no different from
|
|
366
|
-
// omitting `top-0` outright). Confirmed missing by hand: this
|
|
367
|
-
// sandbox's own header used `sticky top-0`, which silently never
|
|
368
|
-
// resolved (no "0" spacing key existed) until this was added.
|
|
369
375
|
"0": 0,
|
|
376
|
+
"px": 1,
|
|
377
|
+
"0.5": 2,
|
|
370
378
|
"1": 4,
|
|
379
|
+
"1.5": 6,
|
|
371
380
|
"2": 8,
|
|
381
|
+
"2.5": 10,
|
|
382
|
+
"3": 12,
|
|
383
|
+
"3.5": 14,
|
|
372
384
|
"4": 16,
|
|
385
|
+
"5": 20,
|
|
373
386
|
"6": 24,
|
|
374
|
-
"
|
|
387
|
+
"7": 28,
|
|
388
|
+
"8": 32,
|
|
389
|
+
"9": 36,
|
|
390
|
+
"10": 40,
|
|
391
|
+
"11": 44,
|
|
392
|
+
"12": 48,
|
|
393
|
+
"14": 56,
|
|
394
|
+
"16": 64,
|
|
395
|
+
"20": 80,
|
|
396
|
+
"24": 96,
|
|
397
|
+
"28": 112,
|
|
398
|
+
"32": 128,
|
|
399
|
+
"36": 144,
|
|
400
|
+
"40": 160,
|
|
401
|
+
"44": 176,
|
|
402
|
+
"48": 192,
|
|
403
|
+
"52": 208,
|
|
404
|
+
"56": 224,
|
|
405
|
+
"60": 240,
|
|
406
|
+
"64": 256,
|
|
407
|
+
"72": 288,
|
|
408
|
+
"80": 320,
|
|
409
|
+
"96": 384
|
|
375
410
|
},
|
|
376
411
|
screens: {
|
|
377
412
|
sm: 640,
|
|
@@ -380,7 +415,33 @@ var defaultTheme = {
|
|
|
380
415
|
xl: 1280,
|
|
381
416
|
"2xl": 1536
|
|
382
417
|
},
|
|
383
|
-
|
|
418
|
+
// Byte-for-byte the same three stacks
|
|
419
|
+
// packages/core-engine/src/resolvers/typography.rs's `font_family_value`
|
|
420
|
+
// falls back to — kept here too (rather than leaving this empty and
|
|
421
|
+
// relying purely on the Rust-side fallback) so `defaultTheme.fontFamily.sans`
|
|
422
|
+
// is a real, readable value for anything that wants to extend rather
|
|
423
|
+
// than replace it (see `KbachConfig.extend.fontFamily` in config.ts).
|
|
424
|
+
fontFamily: {
|
|
425
|
+
sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
426
|
+
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
427
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
428
|
+
},
|
|
429
|
+
// Matches real Tailwind's own default (`darkMode: 'media'`): pure
|
|
430
|
+
// `@media (prefers-color-scheme: dark)`, works with zero JS — a project
|
|
431
|
+
// using only static `className` strings gets working `dark:` classes
|
|
432
|
+
// out of the box, with no runtime import required to write `data-theme`/
|
|
433
|
+
// `.dark` onto `<html>` (see darkModeStore.ts's `applyToDom`). The
|
|
434
|
+
// tradeoff: `'media'` can't be overridden by a manual light/dark toggle —
|
|
435
|
+
// `useTheme()`/`toggleGlobalDarkMode()` still track `isDark` correctly
|
|
436
|
+
// for JS consumers, but flipping it has no visible effect on `dark:`
|
|
437
|
+
// classes, since the browser's media query only ever reflects the real
|
|
438
|
+
// OS setting. Opt into `darkMode: 'class'` or `'attribute'` explicitly
|
|
439
|
+
// (via `kbach.config.js` or the `config`/`theme` plugin option) as soon
|
|
440
|
+
// as an app wants a real in-app toggle.
|
|
441
|
+
darkMode: "media",
|
|
442
|
+
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
443
|
+
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
444
|
+
container: {}
|
|
384
445
|
};
|
|
385
446
|
var activeTheme = defaultTheme;
|
|
386
447
|
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
@@ -545,6 +606,113 @@ function KbachReset() {
|
|
|
545
606
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { id: RESET_STYLE_ID, children: BASE_RESET });
|
|
546
607
|
}
|
|
547
608
|
|
|
609
|
+
// src/colorVariables.ts
|
|
610
|
+
function parseHexRgb(hex) {
|
|
611
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
612
|
+
if (!m) return null;
|
|
613
|
+
let h = m[1];
|
|
614
|
+
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
|
|
615
|
+
const n = parseInt(h, 16);
|
|
616
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
617
|
+
}
|
|
618
|
+
function wrapDarkDecls(strategy, decls) {
|
|
619
|
+
switch (strategy) {
|
|
620
|
+
case "class":
|
|
621
|
+
return `.dark { ${decls} }`;
|
|
622
|
+
case "media":
|
|
623
|
+
return `@media (prefers-color-scheme: dark) { :root { ${decls} } }`;
|
|
624
|
+
case "attribute":
|
|
625
|
+
default:
|
|
626
|
+
return `[data-theme="dark"] { ${decls} }`;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
var STYLE_ID = "kbach-colors";
|
|
630
|
+
var injectedForTheme = null;
|
|
631
|
+
function ensureColorVariablesInjected(theme) {
|
|
632
|
+
if (typeof document === "undefined") return;
|
|
633
|
+
if (injectedForTheme === theme) return;
|
|
634
|
+
injectedForTheme = theme;
|
|
635
|
+
const lightDecls = [];
|
|
636
|
+
const darkDecls = [];
|
|
637
|
+
for (const [name, value] of Object.entries(theme.colors)) {
|
|
638
|
+
if (typeof value === "string") continue;
|
|
639
|
+
const light = parseHexRgb(value.light);
|
|
640
|
+
const dark = parseHexRgb(value.dark);
|
|
641
|
+
if (!light || !dark) continue;
|
|
642
|
+
lightDecls.push(`--kb-color-${name}: ${light.join(",")};`);
|
|
643
|
+
darkDecls.push(`--kb-color-${name}: ${dark.join(",")};`);
|
|
644
|
+
}
|
|
645
|
+
let el = document.getElementById(STYLE_ID);
|
|
646
|
+
if (lightDecls.length === 0) {
|
|
647
|
+
el?.remove();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (!el) {
|
|
651
|
+
el = document.createElement("style");
|
|
652
|
+
el.id = STYLE_ID;
|
|
653
|
+
document.head.insertBefore(el, document.head.firstChild);
|
|
654
|
+
}
|
|
655
|
+
el.textContent = `:root { ${lightDecls.join(" ")} }
|
|
656
|
+
${wrapDarkDecls(theme.darkMode, darkDecls.join(" "))}`;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/config.ts
|
|
660
|
+
function applyOpacityToHex(hex, opacityText) {
|
|
661
|
+
const rgb = parseHexRgb(hex);
|
|
662
|
+
if (!rgb) return hex;
|
|
663
|
+
const a = Number(opacityText) / 100;
|
|
664
|
+
const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
|
|
665
|
+
return `rgba(${rgb.join(",")},${clamped})`;
|
|
666
|
+
}
|
|
667
|
+
function resolveColorEntry(value, lookup) {
|
|
668
|
+
if (typeof value !== "string") return value;
|
|
669
|
+
const slash = value.indexOf("/");
|
|
670
|
+
const name = slash === -1 ? value : value.slice(0, slash);
|
|
671
|
+
const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
|
|
672
|
+
const base = lookup(name);
|
|
673
|
+
if (base === void 0) return value;
|
|
674
|
+
if (opacity === void 0) return base;
|
|
675
|
+
return typeof base === "string" ? applyOpacityToHex(base, opacity) : { light: applyOpacityToHex(base.light, opacity), dark: applyOpacityToHex(base.dark, opacity) };
|
|
676
|
+
}
|
|
677
|
+
function resolveColors(extend, existing) {
|
|
678
|
+
const resolved = {};
|
|
679
|
+
const lookup = (name) => resolved[name] ?? existing[name];
|
|
680
|
+
for (const [name, value] of Object.entries(extend)) {
|
|
681
|
+
resolved[name] = resolveColorEntry(value, lookup);
|
|
682
|
+
}
|
|
683
|
+
return resolved;
|
|
684
|
+
}
|
|
685
|
+
function resolveKbachConfig(config) {
|
|
686
|
+
let theme = { ...defaultTheme, ...config.theme };
|
|
687
|
+
if (config.extend?.colors) {
|
|
688
|
+
theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
|
|
689
|
+
}
|
|
690
|
+
if (config.extend?.spacing) {
|
|
691
|
+
theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
|
|
692
|
+
}
|
|
693
|
+
if (config.extend?.screens) {
|
|
694
|
+
theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
|
|
695
|
+
}
|
|
696
|
+
if (config.extend?.fontFamily) {
|
|
697
|
+
theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
|
|
698
|
+
}
|
|
699
|
+
if (config.extend?.container) {
|
|
700
|
+
theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
|
|
701
|
+
}
|
|
702
|
+
if (config.darkMode) {
|
|
703
|
+
theme = { ...theme, darkMode: config.darkMode };
|
|
704
|
+
}
|
|
705
|
+
return theme;
|
|
706
|
+
}
|
|
707
|
+
function applyKbachConfig(config) {
|
|
708
|
+
const theme = resolveKbachConfig(config);
|
|
709
|
+
setTheme(theme);
|
|
710
|
+
return theme;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/useTheme.ts
|
|
714
|
+
var import_react = require("react");
|
|
715
|
+
|
|
548
716
|
// src/darkModeStore.ts
|
|
549
717
|
var STORAGE_KEY = "kbach-theme";
|
|
550
718
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -632,64 +800,111 @@ function seedDefaultMode(fallback) {
|
|
|
632
800
|
notify();
|
|
633
801
|
}
|
|
634
802
|
|
|
635
|
-
// src/
|
|
636
|
-
var import_react = require("react");
|
|
637
|
-
var getServerSnapshot = () => false;
|
|
638
|
-
function useGlobalDarkMode() {
|
|
639
|
-
return (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, getServerSnapshot);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// src/ThemeProvider.tsx
|
|
643
|
-
var import_react3 = require("react");
|
|
644
|
-
|
|
645
|
-
// src/ThemeContext.tsx
|
|
646
|
-
var import_react2 = require("react");
|
|
647
|
-
var ThemeContext = (0, import_react2.createContext)(null);
|
|
803
|
+
// src/useTheme.ts
|
|
648
804
|
function useTheme() {
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
return ctx;
|
|
805
|
+
const isDark2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
|
|
806
|
+
const mode2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalThemeMode, () => "system");
|
|
807
|
+
return (0, import_react.useMemo)(
|
|
808
|
+
() => ({ mode: mode2, isDark: isDark2, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
|
|
809
|
+
[mode2, isDark2]
|
|
810
|
+
);
|
|
656
811
|
}
|
|
657
812
|
|
|
658
813
|
// src/ThemeProvider.tsx
|
|
814
|
+
var import_react2 = require("react");
|
|
659
815
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
660
816
|
var mountedProviderCount = 0;
|
|
661
817
|
var warnedMultipleProviders = false;
|
|
662
818
|
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
663
|
-
(0,
|
|
819
|
+
(0, import_react2.useEffect)(() => {
|
|
664
820
|
mountedProviderCount++;
|
|
665
|
-
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
821
|
+
if (mountedProviderCount > 1 && !warnedMultipleProviders && process.env.NODE_ENV !== "production") {
|
|
666
822
|
warnedMultipleProviders = true;
|
|
667
823
|
console.warn(
|
|
668
|
-
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store (
|
|
824
|
+
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
|
|
669
825
|
);
|
|
670
826
|
}
|
|
671
827
|
return () => {
|
|
672
828
|
mountedProviderCount--;
|
|
673
829
|
};
|
|
674
830
|
}, []);
|
|
675
|
-
const seeded = (0,
|
|
676
|
-
(0,
|
|
831
|
+
const seeded = (0, import_react2.useRef)(false);
|
|
832
|
+
(0, import_react2.useEffect)(() => {
|
|
677
833
|
if (seeded.current) return;
|
|
678
834
|
seeded.current = true;
|
|
679
835
|
seedDefaultMode(defaultMode);
|
|
680
836
|
}, []);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
);
|
|
687
|
-
|
|
837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/useColors.ts
|
|
841
|
+
function applyOpacity(color, opacity) {
|
|
842
|
+
const a = Math.max(0, Math.min(1, opacity / 100));
|
|
843
|
+
const varMatch = /^rgb\(var\((--[\w-]+)\)\)$/.exec(color);
|
|
844
|
+
if (varMatch) return `rgba(var(${varMatch[1]}), ${a})`;
|
|
845
|
+
if (color.startsWith("#")) {
|
|
846
|
+
const rgb = parseHexRgb(color);
|
|
847
|
+
return rgb ? `rgba(${rgb.join(",")},${a})` : color;
|
|
848
|
+
}
|
|
849
|
+
if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
|
|
850
|
+
if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
|
|
851
|
+
return color;
|
|
852
|
+
}
|
|
853
|
+
function resolveEntry(name, entry, isDark2) {
|
|
854
|
+
if (typeof entry === "string") return entry;
|
|
855
|
+
const light = parseHexRgb(entry.light);
|
|
856
|
+
const dark = parseHexRgb(entry.dark);
|
|
857
|
+
if (light && dark) return `rgb(var(--kb-color-${name}))`;
|
|
858
|
+
return isDark2 ? entry.dark : entry.light;
|
|
859
|
+
}
|
|
860
|
+
function makeColorsProxy(theme, isDark2) {
|
|
861
|
+
const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
|
|
862
|
+
const cache = /* @__PURE__ */ new Map();
|
|
863
|
+
function resolveNamed(key) {
|
|
864
|
+
let name = key;
|
|
865
|
+
let opacity;
|
|
866
|
+
if (key.includes("/")) {
|
|
867
|
+
const slash = key.indexOf("/");
|
|
868
|
+
name = key.slice(0, slash);
|
|
869
|
+
opacity = Number(key.slice(slash + 1));
|
|
870
|
+
}
|
|
871
|
+
const entry = theme.colors[name];
|
|
872
|
+
if (entry === void 0) return void 0;
|
|
873
|
+
const value = resolveEntry(name, entry, isDark2);
|
|
874
|
+
return opacity === void 0 ? value : applyOpacity(value, opacity);
|
|
875
|
+
}
|
|
876
|
+
function makeFamilyProxy(family) {
|
|
877
|
+
return new Proxy({}, {
|
|
878
|
+
get(_, prop) {
|
|
879
|
+
const key = String(prop);
|
|
880
|
+
if (key === "then") return void 0;
|
|
881
|
+
return resolveNamed(`${family}-${key}`);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
return new Proxy({ alpha }, {
|
|
886
|
+
get(_, prop) {
|
|
887
|
+
const key = String(prop);
|
|
888
|
+
if (key === "then") return void 0;
|
|
889
|
+
if (key === "alpha") return alpha;
|
|
890
|
+
const direct = resolveNamed(key);
|
|
891
|
+
if (direct !== void 0) return direct;
|
|
892
|
+
if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
|
|
893
|
+
return cache.get(key);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
function useColors() {
|
|
898
|
+
const { isDark: isDark2 } = useTheme();
|
|
899
|
+
const theme = getTheme();
|
|
900
|
+
ensureColorVariablesInjected(theme);
|
|
901
|
+
return makeColorsProxy(theme, isDark2);
|
|
688
902
|
}
|
|
689
903
|
// Annotate the CommonJS export names for ESM import in node:
|
|
690
904
|
0 && (module.exports = {
|
|
691
905
|
KbachReset,
|
|
692
906
|
ThemeProvider,
|
|
907
|
+
applyKbachConfig,
|
|
693
908
|
defaultTheme,
|
|
694
909
|
disableRuntimeCSS,
|
|
695
910
|
getGlobalDarkMode,
|
|
@@ -699,10 +914,11 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
699
914
|
isKbachReady,
|
|
700
915
|
isRuntimeCSSDisabled,
|
|
701
916
|
kb,
|
|
917
|
+
resolveKbachConfig,
|
|
702
918
|
setGlobalThemeMode,
|
|
703
919
|
setTheme,
|
|
704
920
|
subscribeGlobalDarkMode,
|
|
705
921
|
toggleGlobalDarkMode,
|
|
706
|
-
|
|
922
|
+
useColors,
|
|
707
923
|
useTheme
|
|
708
924
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -4,14 +4,20 @@ import {
|
|
|
4
4
|
isKbachReady,
|
|
5
5
|
isRuntimeCSSDisabled,
|
|
6
6
|
kb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ROKZPEHA.mjs";
|
|
8
|
+
import {
|
|
9
|
+
applyKbachConfig,
|
|
10
|
+
ensureColorVariablesInjected,
|
|
11
|
+
parseHexRgb,
|
|
12
|
+
resolveKbachConfig
|
|
13
|
+
} from "./chunk-4Y2QXNJY.mjs";
|
|
8
14
|
import {
|
|
9
15
|
BASE_RESET,
|
|
10
16
|
RESET_STYLE_ID,
|
|
11
17
|
defaultTheme,
|
|
12
18
|
getTheme,
|
|
13
19
|
setTheme
|
|
14
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-EOSN5CYG.mjs";
|
|
15
21
|
|
|
16
22
|
// src/KbachReset.tsx
|
|
17
23
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -20,6 +26,9 @@ function KbachReset() {
|
|
|
20
26
|
return /* @__PURE__ */ jsx("style", { id: RESET_STYLE_ID, children: BASE_RESET });
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
// src/useTheme.ts
|
|
30
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
31
|
+
|
|
23
32
|
// src/darkModeStore.ts
|
|
24
33
|
var STORAGE_KEY = "kbach-theme";
|
|
25
34
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -107,40 +116,28 @@ function seedDefaultMode(fallback) {
|
|
|
107
116
|
notify();
|
|
108
117
|
}
|
|
109
118
|
|
|
110
|
-
// src/
|
|
111
|
-
import { useSyncExternalStore } from "react";
|
|
112
|
-
var getServerSnapshot = () => false;
|
|
113
|
-
function useGlobalDarkMode() {
|
|
114
|
-
return useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode, getServerSnapshot);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/ThemeProvider.tsx
|
|
118
|
-
import { useEffect, useMemo, useRef, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
119
|
-
|
|
120
|
-
// src/ThemeContext.tsx
|
|
121
|
-
import { createContext, useContext } from "react";
|
|
122
|
-
var ThemeContext = createContext(null);
|
|
119
|
+
// src/useTheme.ts
|
|
123
120
|
function useTheme() {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return ctx;
|
|
121
|
+
const isDark2 = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
|
|
122
|
+
const mode2 = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalThemeMode, () => "system");
|
|
123
|
+
return useMemo(
|
|
124
|
+
() => ({ mode: mode2, isDark: isDark2, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
|
|
125
|
+
[mode2, isDark2]
|
|
126
|
+
);
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
// src/ThemeProvider.tsx
|
|
134
|
-
import {
|
|
130
|
+
import { useEffect, useRef } from "react";
|
|
131
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
135
132
|
var mountedProviderCount = 0;
|
|
136
133
|
var warnedMultipleProviders = false;
|
|
137
134
|
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
138
135
|
useEffect(() => {
|
|
139
136
|
mountedProviderCount++;
|
|
140
|
-
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
137
|
+
if (mountedProviderCount > 1 && !warnedMultipleProviders && process.env.NODE_ENV !== "production") {
|
|
141
138
|
warnedMultipleProviders = true;
|
|
142
139
|
console.warn(
|
|
143
|
-
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store (
|
|
140
|
+
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
|
|
144
141
|
);
|
|
145
142
|
}
|
|
146
143
|
return () => {
|
|
@@ -153,17 +150,76 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
153
150
|
seeded.current = true;
|
|
154
151
|
seedDefaultMode(defaultMode);
|
|
155
152
|
}, []);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
);
|
|
162
|
-
|
|
153
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/useColors.ts
|
|
157
|
+
function applyOpacity(color, opacity) {
|
|
158
|
+
const a = Math.max(0, Math.min(1, opacity / 100));
|
|
159
|
+
const varMatch = /^rgb\(var\((--[\w-]+)\)\)$/.exec(color);
|
|
160
|
+
if (varMatch) return `rgba(var(${varMatch[1]}), ${a})`;
|
|
161
|
+
if (color.startsWith("#")) {
|
|
162
|
+
const rgb = parseHexRgb(color);
|
|
163
|
+
return rgb ? `rgba(${rgb.join(",")},${a})` : color;
|
|
164
|
+
}
|
|
165
|
+
if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
|
|
166
|
+
if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
|
|
167
|
+
return color;
|
|
168
|
+
}
|
|
169
|
+
function resolveEntry(name, entry, isDark2) {
|
|
170
|
+
if (typeof entry === "string") return entry;
|
|
171
|
+
const light = parseHexRgb(entry.light);
|
|
172
|
+
const dark = parseHexRgb(entry.dark);
|
|
173
|
+
if (light && dark) return `rgb(var(--kb-color-${name}))`;
|
|
174
|
+
return isDark2 ? entry.dark : entry.light;
|
|
175
|
+
}
|
|
176
|
+
function makeColorsProxy(theme, isDark2) {
|
|
177
|
+
const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
|
|
178
|
+
const cache = /* @__PURE__ */ new Map();
|
|
179
|
+
function resolveNamed(key) {
|
|
180
|
+
let name = key;
|
|
181
|
+
let opacity;
|
|
182
|
+
if (key.includes("/")) {
|
|
183
|
+
const slash = key.indexOf("/");
|
|
184
|
+
name = key.slice(0, slash);
|
|
185
|
+
opacity = Number(key.slice(slash + 1));
|
|
186
|
+
}
|
|
187
|
+
const entry = theme.colors[name];
|
|
188
|
+
if (entry === void 0) return void 0;
|
|
189
|
+
const value = resolveEntry(name, entry, isDark2);
|
|
190
|
+
return opacity === void 0 ? value : applyOpacity(value, opacity);
|
|
191
|
+
}
|
|
192
|
+
function makeFamilyProxy(family) {
|
|
193
|
+
return new Proxy({}, {
|
|
194
|
+
get(_, prop) {
|
|
195
|
+
const key = String(prop);
|
|
196
|
+
if (key === "then") return void 0;
|
|
197
|
+
return resolveNamed(`${family}-${key}`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return new Proxy({ alpha }, {
|
|
202
|
+
get(_, prop) {
|
|
203
|
+
const key = String(prop);
|
|
204
|
+
if (key === "then") return void 0;
|
|
205
|
+
if (key === "alpha") return alpha;
|
|
206
|
+
const direct = resolveNamed(key);
|
|
207
|
+
if (direct !== void 0) return direct;
|
|
208
|
+
if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
|
|
209
|
+
return cache.get(key);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function useColors() {
|
|
214
|
+
const { isDark: isDark2 } = useTheme();
|
|
215
|
+
const theme = getTheme();
|
|
216
|
+
ensureColorVariablesInjected(theme);
|
|
217
|
+
return makeColorsProxy(theme, isDark2);
|
|
163
218
|
}
|
|
164
219
|
export {
|
|
165
220
|
KbachReset,
|
|
166
221
|
ThemeProvider,
|
|
222
|
+
applyKbachConfig,
|
|
167
223
|
defaultTheme,
|
|
168
224
|
disableRuntimeCSS,
|
|
169
225
|
getGlobalDarkMode,
|
|
@@ -173,10 +229,11 @@ export {
|
|
|
173
229
|
isKbachReady,
|
|
174
230
|
isRuntimeCSSDisabled,
|
|
175
231
|
kb,
|
|
232
|
+
resolveKbachConfig,
|
|
176
233
|
setGlobalThemeMode,
|
|
177
234
|
setTheme,
|
|
178
235
|
subscribeGlobalDarkMode,
|
|
179
236
|
toggleGlobalDarkMode,
|
|
180
|
-
|
|
237
|
+
useColors,
|
|
181
238
|
useTheme
|
|
182
239
|
};
|
package/dist/jsx-dev-runtime.js
CHANGED
|
@@ -334,21 +334,54 @@ var defaultTheme = {
|
|
|
334
334
|
...PALETTE,
|
|
335
335
|
surface: { light: "#f9fafb", dark: "#111827" }
|
|
336
336
|
},
|
|
337
|
+
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
338
|
+
// `n * 4px` (Tailwind's base spacing unit), including the fractional
|
|
339
|
+
// `.5` steps (`1.5` -> 6px, `2.5` -> 10px, `3.5` -> 14px) and the "px"
|
|
340
|
+
// literal (a bare 1px, for `p-px`-style hairline spacing). This table is
|
|
341
|
+
// only the "usual stops", though — the Rust/JS engines' shared
|
|
342
|
+
// `spacing_px`/`spacingPx` helper falls back to Tailwind v4's own live
|
|
343
|
+
// `n * 4px` formula for any bare numeric step NOT listed here (`p-0.25`,
|
|
344
|
+
// `gap-1.75`, `w-13`, ...), so this table exists to name the common
|
|
345
|
+
// values and give a theme customization something to override, not to
|
|
346
|
+
// enumerate every resolvable step. "px" is the one exception that must
|
|
347
|
+
// stay listed explicitly — it isn't a number, so the formula can't
|
|
348
|
+
// derive it.
|
|
337
349
|
spacing: {
|
|
338
|
-
// "0" is always a hardcoded zero in real Tailwind's own scale too,
|
|
339
|
-
// regardless of the base spacing unit — needed for `p-0`/`m-0`/
|
|
340
|
-
// `top-0`/`inset-0`/etc., and specifically for `sticky top-0` (a
|
|
341
|
-
// sticky element needs an explicit offset to have any stick point at
|
|
342
|
-
// all; without one it behaves just like `relative`, no different from
|
|
343
|
-
// omitting `top-0` outright). Confirmed missing by hand: this
|
|
344
|
-
// sandbox's own header used `sticky top-0`, which silently never
|
|
345
|
-
// resolved (no "0" spacing key existed) until this was added.
|
|
346
350
|
"0": 0,
|
|
351
|
+
"px": 1,
|
|
352
|
+
"0.5": 2,
|
|
347
353
|
"1": 4,
|
|
354
|
+
"1.5": 6,
|
|
348
355
|
"2": 8,
|
|
356
|
+
"2.5": 10,
|
|
357
|
+
"3": 12,
|
|
358
|
+
"3.5": 14,
|
|
349
359
|
"4": 16,
|
|
360
|
+
"5": 20,
|
|
350
361
|
"6": 24,
|
|
351
|
-
"
|
|
362
|
+
"7": 28,
|
|
363
|
+
"8": 32,
|
|
364
|
+
"9": 36,
|
|
365
|
+
"10": 40,
|
|
366
|
+
"11": 44,
|
|
367
|
+
"12": 48,
|
|
368
|
+
"14": 56,
|
|
369
|
+
"16": 64,
|
|
370
|
+
"20": 80,
|
|
371
|
+
"24": 96,
|
|
372
|
+
"28": 112,
|
|
373
|
+
"32": 128,
|
|
374
|
+
"36": 144,
|
|
375
|
+
"40": 160,
|
|
376
|
+
"44": 176,
|
|
377
|
+
"48": 192,
|
|
378
|
+
"52": 208,
|
|
379
|
+
"56": 224,
|
|
380
|
+
"60": 240,
|
|
381
|
+
"64": 256,
|
|
382
|
+
"72": 288,
|
|
383
|
+
"80": 320,
|
|
384
|
+
"96": 384
|
|
352
385
|
},
|
|
353
386
|
screens: {
|
|
354
387
|
sm: 640,
|
|
@@ -357,7 +390,33 @@ var defaultTheme = {
|
|
|
357
390
|
xl: 1280,
|
|
358
391
|
"2xl": 1536
|
|
359
392
|
},
|
|
360
|
-
|
|
393
|
+
// Byte-for-byte the same three stacks
|
|
394
|
+
// packages/core-engine/src/resolvers/typography.rs's `font_family_value`
|
|
395
|
+
// falls back to — kept here too (rather than leaving this empty and
|
|
396
|
+
// relying purely on the Rust-side fallback) so `defaultTheme.fontFamily.sans`
|
|
397
|
+
// is a real, readable value for anything that wants to extend rather
|
|
398
|
+
// than replace it (see `KbachConfig.extend.fontFamily` in config.ts).
|
|
399
|
+
fontFamily: {
|
|
400
|
+
sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
401
|
+
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
402
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
403
|
+
},
|
|
404
|
+
// Matches real Tailwind's own default (`darkMode: 'media'`): pure
|
|
405
|
+
// `@media (prefers-color-scheme: dark)`, works with zero JS — a project
|
|
406
|
+
// using only static `className` strings gets working `dark:` classes
|
|
407
|
+
// out of the box, with no runtime import required to write `data-theme`/
|
|
408
|
+
// `.dark` onto `<html>` (see darkModeStore.ts's `applyToDom`). The
|
|
409
|
+
// tradeoff: `'media'` can't be overridden by a manual light/dark toggle —
|
|
410
|
+
// `useTheme()`/`toggleGlobalDarkMode()` still track `isDark` correctly
|
|
411
|
+
// for JS consumers, but flipping it has no visible effect on `dark:`
|
|
412
|
+
// classes, since the browser's media query only ever reflects the real
|
|
413
|
+
// OS setting. Opt into `darkMode: 'class'` or `'attribute'` explicitly
|
|
414
|
+
// (via `kbach.config.js` or the `config`/`theme` plugin option) as soon
|
|
415
|
+
// as an app wants a real in-app toggle.
|
|
416
|
+
darkMode: "media",
|
|
417
|
+
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
418
|
+
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
419
|
+
container: {}
|
|
361
420
|
};
|
|
362
421
|
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
363
422
|
function getThemeJson() {
|
package/dist/jsx-dev-runtime.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
Fragment,
|
|
3
3
|
jsx,
|
|
4
4
|
jsxs
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-L6B4DTLD.mjs";
|
|
6
|
+
import "./chunk-ROKZPEHA.mjs";
|
|
7
|
+
import "./chunk-EOSN5CYG.mjs";
|
|
8
8
|
|
|
9
9
|
// src/jsx-dev-runtime.tsx
|
|
10
10
|
function jsxDEV(type, props, key, isStaticChildren, source, self) {
|