@kbach/react 1.0.0-beta.0 → 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-AFY36PRX.mjs → chunk-N5HCQIBB.mjs} +57 -10
- package/dist/chunk-NKR3PSWO.mjs +115 -0
- package/dist/{chunk-VBPQAIZL.mjs → chunk-PAJ6KHDB.mjs} +1 -1
- package/dist/{chunk-G3RJJNSY.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 +247 -43
- package/dist/index.mjs +89 -32
- package/dist/jsx-dev-runtime.js +57 -10
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +57 -10
- 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 +132 -12
- 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);
|
|
@@ -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,21 @@ 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
|
+
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: {}
|
|
384
433
|
};
|
|
385
434
|
var activeTheme = defaultTheme;
|
|
386
435
|
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
@@ -545,6 +594,113 @@ function KbachReset() {
|
|
|
545
594
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { id: RESET_STYLE_ID, children: BASE_RESET });
|
|
546
595
|
}
|
|
547
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
|
+
|
|
548
704
|
// src/darkModeStore.ts
|
|
549
705
|
var STORAGE_KEY = "kbach-theme";
|
|
550
706
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -632,64 +788,111 @@ function seedDefaultMode(fallback) {
|
|
|
632
788
|
notify();
|
|
633
789
|
}
|
|
634
790
|
|
|
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);
|
|
791
|
+
// src/useTheme.ts
|
|
648
792
|
function useTheme() {
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
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
|
+
);
|
|
656
799
|
}
|
|
657
800
|
|
|
658
801
|
// src/ThemeProvider.tsx
|
|
802
|
+
var import_react2 = require("react");
|
|
659
803
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
660
804
|
var mountedProviderCount = 0;
|
|
661
805
|
var warnedMultipleProviders = false;
|
|
662
806
|
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
663
|
-
(0,
|
|
807
|
+
(0, import_react2.useEffect)(() => {
|
|
664
808
|
mountedProviderCount++;
|
|
665
809
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
666
810
|
warnedMultipleProviders = true;
|
|
667
811
|
console.warn(
|
|
668
|
-
"[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."
|
|
669
813
|
);
|
|
670
814
|
}
|
|
671
815
|
return () => {
|
|
672
816
|
mountedProviderCount--;
|
|
673
817
|
};
|
|
674
818
|
}, []);
|
|
675
|
-
const seeded = (0,
|
|
676
|
-
(0,
|
|
819
|
+
const seeded = (0, import_react2.useRef)(false);
|
|
820
|
+
(0, import_react2.useEffect)(() => {
|
|
677
821
|
if (seeded.current) return;
|
|
678
822
|
seeded.current = true;
|
|
679
823
|
seedDefaultMode(defaultMode);
|
|
680
824
|
}, []);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
);
|
|
687
|
-
|
|
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);
|
|
688
890
|
}
|
|
689
891
|
// Annotate the CommonJS export names for ESM import in node:
|
|
690
892
|
0 && (module.exports = {
|
|
691
893
|
KbachReset,
|
|
692
894
|
ThemeProvider,
|
|
895
|
+
applyKbachConfig,
|
|
693
896
|
defaultTheme,
|
|
694
897
|
disableRuntimeCSS,
|
|
695
898
|
getGlobalDarkMode,
|
|
@@ -699,10 +902,11 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
699
902
|
isKbachReady,
|
|
700
903
|
isRuntimeCSSDisabled,
|
|
701
904
|
kb,
|
|
905
|
+
resolveKbachConfig,
|
|
702
906
|
setGlobalThemeMode,
|
|
703
907
|
setTheme,
|
|
704
908
|
subscribeGlobalDarkMode,
|
|
705
909
|
toggleGlobalDarkMode,
|
|
706
|
-
|
|
910
|
+
useColors,
|
|
707
911
|
useTheme
|
|
708
912
|
});
|