@kbach/react 1.0.0-beta.1 → 1.0.0-beta.2
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 +45 -68
- package/dist/{chunk-FOQWQDF4.mjs → chunk-N5HCQIBB.mjs} +24 -9
- package/dist/chunk-NKR3PSWO.mjs +115 -0
- package/dist/{chunk-AD3G6RHY.mjs → chunk-PAJ6KHDB.mjs} +1 -1
- package/dist/{chunk-ROZXCQSM.mjs → chunk-ZQJKL2MV.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 +214 -42
- package/dist/index.mjs +89 -32
- package/dist/jsx-dev-runtime.js +24 -9
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +24 -9
- package/dist/jsx-runtime.mjs +3 -3
- package/dist/vite-plugin.d.mts +15 -1
- package/dist/vite-plugin.d.ts +15 -1
- package/dist/vite-plugin.js +99 -11
- package/dist/vite-plugin.mjs +21 -4
- package/package.json +2 -2
- package/dist/theme-BKAb78KP.d.mts +0 -20
- package/dist/theme-BKAb78KP.d.ts +0 -20
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { T as ThemeConfig, d as defaultTheme, g as getTheme, s as setTheme } from './
|
|
1
|
+
export { C as ColorEntry, a as ContainerConfig, D as DarkModeStrategy, K as KbachConfig, T as ThemeConfig, b as applyKbachConfig, d as defaultTheme, g as getTheme, r as resolveKbachConfig, s as setTheme } from './config-BQ_b-qxF.mjs';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
@@ -61,16 +61,23 @@ declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
|
61
61
|
declare function toggleGlobalDarkMode(): void;
|
|
62
62
|
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
63
63
|
|
|
64
|
+
interface ThemeState {
|
|
65
|
+
mode: ThemeMode;
|
|
66
|
+
isDark: boolean;
|
|
67
|
+
setMode: (mode: ThemeMode) => void;
|
|
68
|
+
toggle: () => void;
|
|
69
|
+
}
|
|
64
70
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
* The one hook for reading and writing Kbach's dark-mode state. Reads
|
|
72
|
+
* `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
|
|
73
|
+
* that module-level store, not React Context, is the actual source of
|
|
74
|
+
* truth (every provider used to just mirror it into a Context value), so
|
|
75
|
+
* this works from ANY component, with or without a `<ThemeProvider>`
|
|
76
|
+
* mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
|
|
77
|
+
* as an optional convenience for seeding an app's initial `defaultMode` —
|
|
78
|
+
* see its own doc comment.
|
|
72
79
|
*/
|
|
73
|
-
declare function
|
|
80
|
+
declare function useTheme(): ThemeState;
|
|
74
81
|
|
|
75
82
|
interface ThemeProviderProps {
|
|
76
83
|
children: ReactNode;
|
|
@@ -82,36 +89,60 @@ interface ThemeProviderProps {
|
|
|
82
89
|
defaultMode?: ThemeMode;
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
|
-
* Optional
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* environments where it's unavailable).
|
|
92
|
+
* Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
|
|
93
|
+
* and works anywhere without this mounted at all. The only thing this
|
|
94
|
+
* component does is seed that store's initial `defaultMode` once, for apps
|
|
95
|
+
* that want to declare a startup default in JSX rather than calling
|
|
96
|
+
* `setGlobalThemeMode()` imperatively before their first render. It
|
|
97
|
+
* renders `children` as-is — no Context.Provider, since there's no value
|
|
98
|
+
* left to distribute; `useTheme()` never reads from Context.
|
|
93
99
|
*
|
|
94
|
-
* Because the store is global, MULTIPLE mounted providers
|
|
95
|
-
* same
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* in dev, matching old-kbach's identical guard.
|
|
100
|
+
* Because the store is global, MULTIPLE mounted providers would each try
|
|
101
|
+
* to seed the exact same store rather than getting their own scoped
|
|
102
|
+
* state — mounting more than one is very likely a mistake, so this warns
|
|
103
|
+
* once in dev.
|
|
99
104
|
*/
|
|
100
105
|
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
101
106
|
|
|
102
|
-
interface
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
interface ColorScale {
|
|
108
|
+
/** `colors.blue[6]` -> raw value */
|
|
109
|
+
readonly [shade: number]: string;
|
|
110
|
+
/** `colors.blue['6/50']` -> shade 6 at 50% opacity */
|
|
111
|
+
readonly [key: string]: string;
|
|
107
112
|
}
|
|
113
|
+
type ColorsAPI = {
|
|
114
|
+
/** `colors.white`, `colors.blue` (a shade family), `colors.surface`, ... */
|
|
115
|
+
readonly [name: string]: string | ColorScale;
|
|
116
|
+
} & {
|
|
117
|
+
/**
|
|
118
|
+
* Applies an opacity (0–100) to any color string this hook can return —
|
|
119
|
+
* a hex, `rgb()`/`rgba()`, or one of this hook's own
|
|
120
|
+
* `rgb(var(--kb-color-x))` mode-aware references.
|
|
121
|
+
*/
|
|
122
|
+
readonly alpha: (color: string, opacity?: number) => string;
|
|
123
|
+
};
|
|
108
124
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
125
|
+
* Resolved theme colors as real values — for anywhere you need an actual
|
|
126
|
+
* color (an SVG `fill`, a canvas context, a chart library prop), not a
|
|
127
|
+
* `className`. Ported from old-kbach's `useColors()`
|
|
128
|
+
* (`old-kbach/packages/ui/src/useColors.ts`), adapted to this engine's flat
|
|
129
|
+
* `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
|
|
130
|
+
*
|
|
131
|
+
* Any color — a shade-family entry like `"blue-6"` or a standalone name
|
|
132
|
+
* like `"surface"` — can be defined in the theme as either a plain string
|
|
133
|
+
* (static, same in both modes) or a `{ light, dark }` pair — fully manual,
|
|
134
|
+
* no automatic shade-shifting or other assumption about what a "dark
|
|
135
|
+
* variant" should be; see `resolveEntry`'s own doc comment for how a
|
|
136
|
+
* `{ light, dark }` entry resolves.
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* const colors = useColors();
|
|
140
|
+
* colors.blue[6]; // "#3b82f6" — static, same value in both modes
|
|
141
|
+
* colors.blue['6/50']; // same, at 50% opacity
|
|
142
|
+
* colors.surface; // "rgb(var(--kb-color-surface))" if defined as { light, dark }
|
|
143
|
+
* colors.alpha(colors.surface, 50); // "rgba(var(--kb-color-surface), 0.5)"
|
|
144
|
+
* ```
|
|
114
145
|
*/
|
|
115
|
-
declare function
|
|
146
|
+
declare function useColors(): ColorsAPI;
|
|
116
147
|
|
|
117
|
-
export {
|
|
148
|
+
export { type ColorScale, type ColorsAPI, KbachReset, type ThemeMode, ThemeProvider, type ThemeProviderProps, type ThemeState, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useColors, useTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { T as ThemeConfig, d as defaultTheme, g as getTheme, s as setTheme } from './
|
|
1
|
+
export { C as ColorEntry, a as ContainerConfig, D as DarkModeStrategy, K as KbachConfig, T as ThemeConfig, b as applyKbachConfig, d as defaultTheme, g as getTheme, r as resolveKbachConfig, s as setTheme } from './config-BQ_b-qxF.js';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
@@ -61,16 +61,23 @@ declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
|
61
61
|
declare function toggleGlobalDarkMode(): void;
|
|
62
62
|
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
63
63
|
|
|
64
|
+
interface ThemeState {
|
|
65
|
+
mode: ThemeMode;
|
|
66
|
+
isDark: boolean;
|
|
67
|
+
setMode: (mode: ThemeMode) => void;
|
|
68
|
+
toggle: () => void;
|
|
69
|
+
}
|
|
64
70
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
* The one hook for reading and writing Kbach's dark-mode state. Reads
|
|
72
|
+
* `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
|
|
73
|
+
* that module-level store, not React Context, is the actual source of
|
|
74
|
+
* truth (every provider used to just mirror it into a Context value), so
|
|
75
|
+
* this works from ANY component, with or without a `<ThemeProvider>`
|
|
76
|
+
* mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
|
|
77
|
+
* as an optional convenience for seeding an app's initial `defaultMode` —
|
|
78
|
+
* see its own doc comment.
|
|
72
79
|
*/
|
|
73
|
-
declare function
|
|
80
|
+
declare function useTheme(): ThemeState;
|
|
74
81
|
|
|
75
82
|
interface ThemeProviderProps {
|
|
76
83
|
children: ReactNode;
|
|
@@ -82,36 +89,60 @@ interface ThemeProviderProps {
|
|
|
82
89
|
defaultMode?: ThemeMode;
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
|
-
* Optional
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* environments where it's unavailable).
|
|
92
|
+
* Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
|
|
93
|
+
* and works anywhere without this mounted at all. The only thing this
|
|
94
|
+
* component does is seed that store's initial `defaultMode` once, for apps
|
|
95
|
+
* that want to declare a startup default in JSX rather than calling
|
|
96
|
+
* `setGlobalThemeMode()` imperatively before their first render. It
|
|
97
|
+
* renders `children` as-is — no Context.Provider, since there's no value
|
|
98
|
+
* left to distribute; `useTheme()` never reads from Context.
|
|
93
99
|
*
|
|
94
|
-
* Because the store is global, MULTIPLE mounted providers
|
|
95
|
-
* same
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* in dev, matching old-kbach's identical guard.
|
|
100
|
+
* Because the store is global, MULTIPLE mounted providers would each try
|
|
101
|
+
* to seed the exact same store rather than getting their own scoped
|
|
102
|
+
* state — mounting more than one is very likely a mistake, so this warns
|
|
103
|
+
* once in dev.
|
|
99
104
|
*/
|
|
100
105
|
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
101
106
|
|
|
102
|
-
interface
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
interface ColorScale {
|
|
108
|
+
/** `colors.blue[6]` -> raw value */
|
|
109
|
+
readonly [shade: number]: string;
|
|
110
|
+
/** `colors.blue['6/50']` -> shade 6 at 50% opacity */
|
|
111
|
+
readonly [key: string]: string;
|
|
107
112
|
}
|
|
113
|
+
type ColorsAPI = {
|
|
114
|
+
/** `colors.white`, `colors.blue` (a shade family), `colors.surface`, ... */
|
|
115
|
+
readonly [name: string]: string | ColorScale;
|
|
116
|
+
} & {
|
|
117
|
+
/**
|
|
118
|
+
* Applies an opacity (0–100) to any color string this hook can return —
|
|
119
|
+
* a hex, `rgb()`/`rgba()`, or one of this hook's own
|
|
120
|
+
* `rgb(var(--kb-color-x))` mode-aware references.
|
|
121
|
+
*/
|
|
122
|
+
readonly alpha: (color: string, opacity?: number) => string;
|
|
123
|
+
};
|
|
108
124
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
125
|
+
* Resolved theme colors as real values — for anywhere you need an actual
|
|
126
|
+
* color (an SVG `fill`, a canvas context, a chart library prop), not a
|
|
127
|
+
* `className`. Ported from old-kbach's `useColors()`
|
|
128
|
+
* (`old-kbach/packages/ui/src/useColors.ts`), adapted to this engine's flat
|
|
129
|
+
* `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
|
|
130
|
+
*
|
|
131
|
+
* Any color — a shade-family entry like `"blue-6"` or a standalone name
|
|
132
|
+
* like `"surface"` — can be defined in the theme as either a plain string
|
|
133
|
+
* (static, same in both modes) or a `{ light, dark }` pair — fully manual,
|
|
134
|
+
* no automatic shade-shifting or other assumption about what a "dark
|
|
135
|
+
* variant" should be; see `resolveEntry`'s own doc comment for how a
|
|
136
|
+
* `{ light, dark }` entry resolves.
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* const colors = useColors();
|
|
140
|
+
* colors.blue[6]; // "#3b82f6" — static, same value in both modes
|
|
141
|
+
* colors.blue['6/50']; // same, at 50% opacity
|
|
142
|
+
* colors.surface; // "rgb(var(--kb-color-surface))" if defined as { light, dark }
|
|
143
|
+
* colors.alpha(colors.surface, 50); // "rgba(var(--kb-color-surface), 0.5)"
|
|
144
|
+
* ```
|
|
114
145
|
*/
|
|
115
|
-
declare function
|
|
146
|
+
declare function useColors(): ColorsAPI;
|
|
116
147
|
|
|
117
|
-
export {
|
|
148
|
+
export { type ColorScale, type ColorsAPI, KbachReset, type ThemeMode, ThemeProvider, type ThemeProviderProps, type ThemeState, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useColors, useTheme };
|
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);
|
|
@@ -360,14 +362,15 @@ var defaultTheme = {
|
|
|
360
362
|
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
361
363
|
// `n * 4px` (Tailwind's base spacing unit), including the fractional
|
|
362
364
|
// `.5` steps (`1.5` -> 6px, `2.5` -> 10px, `3.5` -> 14px) and the "px"
|
|
363
|
-
// literal (a bare 1px, for `p-px`-style hairline spacing).
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
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.
|
|
371
374
|
spacing: {
|
|
372
375
|
"0": 0,
|
|
373
376
|
"px": 1,
|
|
@@ -412,7 +415,21 @@ var defaultTheme = {
|
|
|
412
415
|
xl: 1280,
|
|
413
416
|
"2xl": 1536
|
|
414
417
|
},
|
|
415
|
-
|
|
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
|
+
darkMode: "attribute",
|
|
430
|
+
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
431
|
+
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
432
|
+
container: {}
|
|
416
433
|
};
|
|
417
434
|
var activeTheme = defaultTheme;
|
|
418
435
|
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
@@ -577,6 +594,113 @@ function KbachReset() {
|
|
|
577
594
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { id: RESET_STYLE_ID, children: BASE_RESET });
|
|
578
595
|
}
|
|
579
596
|
|
|
597
|
+
// src/colorVariables.ts
|
|
598
|
+
function parseHexRgb(hex) {
|
|
599
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
600
|
+
if (!m) return null;
|
|
601
|
+
let h = m[1];
|
|
602
|
+
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
|
|
603
|
+
const n = parseInt(h, 16);
|
|
604
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
605
|
+
}
|
|
606
|
+
function wrapDarkDecls(strategy, decls) {
|
|
607
|
+
switch (strategy) {
|
|
608
|
+
case "class":
|
|
609
|
+
return `.dark { ${decls} }`;
|
|
610
|
+
case "media":
|
|
611
|
+
return `@media (prefers-color-scheme: dark) { :root { ${decls} } }`;
|
|
612
|
+
case "attribute":
|
|
613
|
+
default:
|
|
614
|
+
return `[data-theme="dark"] { ${decls} }`;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
var STYLE_ID = "kbach-colors";
|
|
618
|
+
var injectedForTheme = null;
|
|
619
|
+
function ensureColorVariablesInjected(theme) {
|
|
620
|
+
if (typeof document === "undefined") return;
|
|
621
|
+
if (injectedForTheme === theme) return;
|
|
622
|
+
injectedForTheme = theme;
|
|
623
|
+
const lightDecls = [];
|
|
624
|
+
const darkDecls = [];
|
|
625
|
+
for (const [name, value] of Object.entries(theme.colors)) {
|
|
626
|
+
if (typeof value === "string") continue;
|
|
627
|
+
const light = parseHexRgb(value.light);
|
|
628
|
+
const dark = parseHexRgb(value.dark);
|
|
629
|
+
if (!light || !dark) continue;
|
|
630
|
+
lightDecls.push(`--kb-color-${name}: ${light.join(",")};`);
|
|
631
|
+
darkDecls.push(`--kb-color-${name}: ${dark.join(",")};`);
|
|
632
|
+
}
|
|
633
|
+
let el = document.getElementById(STYLE_ID);
|
|
634
|
+
if (lightDecls.length === 0) {
|
|
635
|
+
el?.remove();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (!el) {
|
|
639
|
+
el = document.createElement("style");
|
|
640
|
+
el.id = STYLE_ID;
|
|
641
|
+
document.head.insertBefore(el, document.head.firstChild);
|
|
642
|
+
}
|
|
643
|
+
el.textContent = `:root { ${lightDecls.join(" ")} }
|
|
644
|
+
${wrapDarkDecls(theme.darkMode, darkDecls.join(" "))}`;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/config.ts
|
|
648
|
+
function applyOpacityToHex(hex, opacityText) {
|
|
649
|
+
const rgb = parseHexRgb(hex);
|
|
650
|
+
if (!rgb) return hex;
|
|
651
|
+
const a = Number(opacityText) / 100;
|
|
652
|
+
const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
|
|
653
|
+
return `rgba(${rgb.join(",")},${clamped})`;
|
|
654
|
+
}
|
|
655
|
+
function resolveColorEntry(value, lookup) {
|
|
656
|
+
if (typeof value !== "string") return value;
|
|
657
|
+
const slash = value.indexOf("/");
|
|
658
|
+
const name = slash === -1 ? value : value.slice(0, slash);
|
|
659
|
+
const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
|
|
660
|
+
const base = lookup(name);
|
|
661
|
+
if (base === void 0) return value;
|
|
662
|
+
if (opacity === void 0) return base;
|
|
663
|
+
return typeof base === "string" ? applyOpacityToHex(base, opacity) : { light: applyOpacityToHex(base.light, opacity), dark: applyOpacityToHex(base.dark, opacity) };
|
|
664
|
+
}
|
|
665
|
+
function resolveColors(extend, existing) {
|
|
666
|
+
const resolved = {};
|
|
667
|
+
const lookup = (name) => resolved[name] ?? existing[name];
|
|
668
|
+
for (const [name, value] of Object.entries(extend)) {
|
|
669
|
+
resolved[name] = resolveColorEntry(value, lookup);
|
|
670
|
+
}
|
|
671
|
+
return resolved;
|
|
672
|
+
}
|
|
673
|
+
function resolveKbachConfig(config) {
|
|
674
|
+
let theme = { ...defaultTheme, ...config.theme };
|
|
675
|
+
if (config.extend?.colors) {
|
|
676
|
+
theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
|
|
677
|
+
}
|
|
678
|
+
if (config.extend?.spacing) {
|
|
679
|
+
theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
|
|
680
|
+
}
|
|
681
|
+
if (config.extend?.screens) {
|
|
682
|
+
theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
|
|
683
|
+
}
|
|
684
|
+
if (config.extend?.fontFamily) {
|
|
685
|
+
theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
|
|
686
|
+
}
|
|
687
|
+
if (config.extend?.container) {
|
|
688
|
+
theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
|
|
689
|
+
}
|
|
690
|
+
if (config.darkMode) {
|
|
691
|
+
theme = { ...theme, darkMode: config.darkMode };
|
|
692
|
+
}
|
|
693
|
+
return theme;
|
|
694
|
+
}
|
|
695
|
+
function applyKbachConfig(config) {
|
|
696
|
+
const theme = resolveKbachConfig(config);
|
|
697
|
+
setTheme(theme);
|
|
698
|
+
return theme;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/useTheme.ts
|
|
702
|
+
var import_react = require("react");
|
|
703
|
+
|
|
580
704
|
// src/darkModeStore.ts
|
|
581
705
|
var STORAGE_KEY = "kbach-theme";
|
|
582
706
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -664,64 +788,111 @@ function seedDefaultMode(fallback) {
|
|
|
664
788
|
notify();
|
|
665
789
|
}
|
|
666
790
|
|
|
667
|
-
// src/
|
|
668
|
-
var import_react = require("react");
|
|
669
|
-
var getServerSnapshot = () => false;
|
|
670
|
-
function useGlobalDarkMode() {
|
|
671
|
-
return (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, getServerSnapshot);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// src/ThemeProvider.tsx
|
|
675
|
-
var import_react3 = require("react");
|
|
676
|
-
|
|
677
|
-
// src/ThemeContext.tsx
|
|
678
|
-
var import_react2 = require("react");
|
|
679
|
-
var ThemeContext = (0, import_react2.createContext)(null);
|
|
791
|
+
// src/useTheme.ts
|
|
680
792
|
function useTheme() {
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return ctx;
|
|
793
|
+
const isDark2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
|
|
794
|
+
const mode2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalThemeMode, () => "system");
|
|
795
|
+
return (0, import_react.useMemo)(
|
|
796
|
+
() => ({ mode: mode2, isDark: isDark2, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
|
|
797
|
+
[mode2, isDark2]
|
|
798
|
+
);
|
|
688
799
|
}
|
|
689
800
|
|
|
690
801
|
// src/ThemeProvider.tsx
|
|
802
|
+
var import_react2 = require("react");
|
|
691
803
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
692
804
|
var mountedProviderCount = 0;
|
|
693
805
|
var warnedMultipleProviders = false;
|
|
694
806
|
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
695
|
-
(0,
|
|
807
|
+
(0, import_react2.useEffect)(() => {
|
|
696
808
|
mountedProviderCount++;
|
|
697
809
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
698
810
|
warnedMultipleProviders = true;
|
|
699
811
|
console.warn(
|
|
700
|
-
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store (
|
|
812
|
+
"[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."
|
|
701
813
|
);
|
|
702
814
|
}
|
|
703
815
|
return () => {
|
|
704
816
|
mountedProviderCount--;
|
|
705
817
|
};
|
|
706
818
|
}, []);
|
|
707
|
-
const seeded = (0,
|
|
708
|
-
(0,
|
|
819
|
+
const seeded = (0, import_react2.useRef)(false);
|
|
820
|
+
(0, import_react2.useEffect)(() => {
|
|
709
821
|
if (seeded.current) return;
|
|
710
822
|
seeded.current = true;
|
|
711
823
|
seedDefaultMode(defaultMode);
|
|
712
824
|
}, []);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
);
|
|
719
|
-
|
|
825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/useColors.ts
|
|
829
|
+
function applyOpacity(color, opacity) {
|
|
830
|
+
const a = Math.max(0, Math.min(1, opacity / 100));
|
|
831
|
+
const varMatch = /^rgb\(var\((--[\w-]+)\)\)$/.exec(color);
|
|
832
|
+
if (varMatch) return `rgba(var(${varMatch[1]}), ${a})`;
|
|
833
|
+
if (color.startsWith("#")) {
|
|
834
|
+
const rgb = parseHexRgb(color);
|
|
835
|
+
return rgb ? `rgba(${rgb.join(",")},${a})` : color;
|
|
836
|
+
}
|
|
837
|
+
if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
|
|
838
|
+
if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
|
|
839
|
+
return color;
|
|
840
|
+
}
|
|
841
|
+
function resolveEntry(name, entry, isDark2) {
|
|
842
|
+
if (typeof entry === "string") return entry;
|
|
843
|
+
const light = parseHexRgb(entry.light);
|
|
844
|
+
const dark = parseHexRgb(entry.dark);
|
|
845
|
+
if (light && dark) return `rgb(var(--kb-color-${name}))`;
|
|
846
|
+
return isDark2 ? entry.dark : entry.light;
|
|
847
|
+
}
|
|
848
|
+
function makeColorsProxy(theme, isDark2) {
|
|
849
|
+
const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
|
|
850
|
+
const cache = /* @__PURE__ */ new Map();
|
|
851
|
+
function resolveNamed(key) {
|
|
852
|
+
let name = key;
|
|
853
|
+
let opacity;
|
|
854
|
+
if (key.includes("/")) {
|
|
855
|
+
const slash = key.indexOf("/");
|
|
856
|
+
name = key.slice(0, slash);
|
|
857
|
+
opacity = Number(key.slice(slash + 1));
|
|
858
|
+
}
|
|
859
|
+
const entry = theme.colors[name];
|
|
860
|
+
if (entry === void 0) return void 0;
|
|
861
|
+
const value = resolveEntry(name, entry, isDark2);
|
|
862
|
+
return opacity === void 0 ? value : applyOpacity(value, opacity);
|
|
863
|
+
}
|
|
864
|
+
function makeFamilyProxy(family) {
|
|
865
|
+
return new Proxy({}, {
|
|
866
|
+
get(_, prop) {
|
|
867
|
+
const key = String(prop);
|
|
868
|
+
if (key === "then") return void 0;
|
|
869
|
+
return resolveNamed(`${family}-${key}`);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return new Proxy({ alpha }, {
|
|
874
|
+
get(_, prop) {
|
|
875
|
+
const key = String(prop);
|
|
876
|
+
if (key === "then") return void 0;
|
|
877
|
+
if (key === "alpha") return alpha;
|
|
878
|
+
const direct = resolveNamed(key);
|
|
879
|
+
if (direct !== void 0) return direct;
|
|
880
|
+
if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
|
|
881
|
+
return cache.get(key);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
function useColors() {
|
|
886
|
+
const { isDark: isDark2 } = useTheme();
|
|
887
|
+
const theme = getTheme();
|
|
888
|
+
ensureColorVariablesInjected(theme);
|
|
889
|
+
return makeColorsProxy(theme, isDark2);
|
|
720
890
|
}
|
|
721
891
|
// Annotate the CommonJS export names for ESM import in node:
|
|
722
892
|
0 && (module.exports = {
|
|
723
893
|
KbachReset,
|
|
724
894
|
ThemeProvider,
|
|
895
|
+
applyKbachConfig,
|
|
725
896
|
defaultTheme,
|
|
726
897
|
disableRuntimeCSS,
|
|
727
898
|
getGlobalDarkMode,
|
|
@@ -731,10 +902,11 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
731
902
|
isKbachReady,
|
|
732
903
|
isRuntimeCSSDisabled,
|
|
733
904
|
kb,
|
|
905
|
+
resolveKbachConfig,
|
|
734
906
|
setGlobalThemeMode,
|
|
735
907
|
setTheme,
|
|
736
908
|
subscribeGlobalDarkMode,
|
|
737
909
|
toggleGlobalDarkMode,
|
|
738
|
-
|
|
910
|
+
useColors,
|
|
739
911
|
useTheme
|
|
740
912
|
});
|