@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  kb
3
- } from "./chunk-G3RJJNSY.mjs";
3
+ } from "./chunk-ROKZPEHA.mjs";
4
4
 
5
5
  // src/jsx-runtime.tsx
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -2,7 +2,7 @@ import {
2
2
  BASE_RESET,
3
3
  RESET_STYLE_ID,
4
4
  getThemeJson
5
- } from "./chunk-AFY36PRX.mjs";
5
+ } from "./chunk-EOSN5CYG.mjs";
6
6
 
7
7
  // src/wasmLoader.ts
8
8
  import init, { generate_css as wasmGenerateCss } from "@kbach/core-engine";
@@ -0,0 +1,105 @@
1
+ /** A plain hex color, or a mode-aware pair auto-expanded into a light/dark class pair by the Rust engine. */
2
+ type ColorEntry = string | {
3
+ light: string;
4
+ dark: string;
5
+ };
6
+ type DarkModeStrategy = 'attribute' | 'class' | 'media';
7
+ /**
8
+ * Real Tailwind's `theme.container` config, mirroring
9
+ * `packages/core-engine/src/theme.rs`'s `ContainerConfig` exactly (same
10
+ * field names, `camelCase` on both sides via serde). `padding` is one
11
+ * uniform value applied at every breakpoint (including below the smallest
12
+ * one) rather than real Tailwind's optional per-breakpoint object form —
13
+ * the common case in practice, and simpler to reason about; a
14
+ * per-breakpoint padding map is a natural follow-up if ever needed.
15
+ */
16
+ interface ContainerConfig {
17
+ center?: boolean;
18
+ padding?: string;
19
+ }
20
+ interface ThemeConfig {
21
+ /** e.g. "blue-6" -> "#2563eb", or "surface" -> { light, dark } */
22
+ colors: Record<string, ColorEntry>;
23
+ /** e.g. "4" -> 16 (px) */
24
+ spacing: Record<string, number>;
25
+ /** e.g. "sm" -> 640 (min-width px) */
26
+ screens: Record<string, number>;
27
+ /**
28
+ * e.g. "sans" -> "ui-sans-serif, system-ui, ...". Optional — the Rust
29
+ * engine's own `font_family_value` already carries a hardcoded fallback
30
+ * for "sans"/"serif"/"mono" (see its own doc comment) for when a theme
31
+ * omits this field (or a name within it) entirely, matching this field's
32
+ * `#[serde(default)]` on the Rust side. `defaultTheme` below still
33
+ * populates all three explicitly, matching the Rust fallback strings
34
+ * byte-for-byte, so a caller reading `defaultTheme.fontFamily.sans`
35
+ * (e.g. to extend rather than replace it) gets the real value rather
36
+ * than needing to already know the Rust-side fallback text.
37
+ */
38
+ fontFamily?: Record<string, string>;
39
+ darkMode: DarkModeStrategy;
40
+ /** Optional, matching `ContainerConfig`'s own `#[serde(default)]` on the Rust side — an omitted theme.container behaves exactly like `{}`. */
41
+ container?: ContainerConfig;
42
+ }
43
+ declare const defaultTheme: ThemeConfig;
44
+ declare function setTheme(theme: ThemeConfig): void;
45
+ declare function getTheme(): ThemeConfig;
46
+
47
+ /**
48
+ * A `kbach.config.js`-style theme customization, in one plain object — same
49
+ * `theme`/`extend`/`darkMode` shape real Tailwind's own config file uses,
50
+ * simplified from old-kbach's version (no plugins, no color alias chains
51
+ * with cycle detection, no live-update-listener store): a config object
52
+ * goes in, a resolved `ThemeConfig` comes out, via one PURE function
53
+ * (`resolveKbachConfig`) — same input always produces the same output, so
54
+ * nothing needs to stay "in sync" the way old-kbach's Babel-injected config
55
+ * singleton did. Call it identically from a Node.js build script
56
+ * (`vite.config.ts`) and from browser/app code (`applyKbachConfig`) and
57
+ * both get the exact same theme, no synchronization mechanism required.
58
+ */
59
+ interface KbachConfig {
60
+ /** Applied on top of `defaultTheme`; `undefined` means "keep the default". */
61
+ darkMode?: DarkModeStrategy;
62
+ /**
63
+ * REPLACES the named section of `defaultTheme` entirely (e.g. supplying
64
+ * `theme.colors` drops the rest of the default palette) — use `extend`
65
+ * instead to add to the defaults rather than replace them. `container`
66
+ * isn't here (only under `extend` below) — it has no "defaults to
67
+ * replace" the way colors/spacing/screens/fontFamily do, since real
68
+ * Tailwind's own container config is inherently additive (center/padding
69
+ * on top of the always-present breakpoint ladder), so there's no
70
+ * meaningful distinction between "replace" and "extend" for it.
71
+ */
72
+ theme?: Partial<Pick<ThemeConfig, 'colors' | 'spacing' | 'screens' | 'fontFamily'>>;
73
+ /** Merges INTO `defaultTheme` (or `theme` above, if also supplied) — the common case. */
74
+ extend?: {
75
+ /**
76
+ * A value is either a literal color (a hex string, or `{ light, dark }`
77
+ * — same mode-aware shape `useColors()`/`useTheme()` already support)
78
+ * or a REFERENCE to another color name, optionally with an opacity
79
+ * suffix. Two things can be referenced:
80
+ * - A BUILT-IN palette color, always available regardless of ordering
81
+ * (`accent: 'orange-5'`, `accentSoft: 'orange-5/50'`).
82
+ * - A custom color defined EARLIER in this same object
83
+ * (`{ brand: '#ff6b35', brandSoft: 'brand/30' }` — `brandSoft`
84
+ * resolves to `brand`'s own value at 30% opacity, computed once here
85
+ * rather than needing a `colors.alpha()` call at every use site).
86
+ * A string that doesn't match any known color name (built-in or
87
+ * already-defined-above) is treated as a literal CSS value instead
88
+ * (`accent: 'red'` — REAL CSS `red`, since there's no color named "red"
89
+ * to alias), never an error either way.
90
+ */
91
+ colors?: Record<string, ColorEntry | string>;
92
+ spacing?: Record<string, number>;
93
+ screens?: Record<string, number>;
94
+ /** Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` — adds to (or overrides one of) `defaultTheme.fontFamily`'s three names. */
95
+ fontFamily?: Record<string, string>;
96
+ /** `{ center: true, padding: '2rem' }` — see `ContainerConfig`'s own doc comment. */
97
+ container?: ContainerConfig;
98
+ };
99
+ }
100
+ /** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
101
+ declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
102
+ /** `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point from app code. Returns the resolved theme (e.g. to also hand to the Vite plugin's own `theme` option). */
103
+ declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
104
+
105
+ export { type ColorEntry as C, type DarkModeStrategy as D, type KbachConfig as K, type ThemeConfig as T, type ContainerConfig as a, applyKbachConfig as b, defaultTheme as d, getTheme as g, resolveKbachConfig as r, setTheme as s };
@@ -0,0 +1,105 @@
1
+ /** A plain hex color, or a mode-aware pair auto-expanded into a light/dark class pair by the Rust engine. */
2
+ type ColorEntry = string | {
3
+ light: string;
4
+ dark: string;
5
+ };
6
+ type DarkModeStrategy = 'attribute' | 'class' | 'media';
7
+ /**
8
+ * Real Tailwind's `theme.container` config, mirroring
9
+ * `packages/core-engine/src/theme.rs`'s `ContainerConfig` exactly (same
10
+ * field names, `camelCase` on both sides via serde). `padding` is one
11
+ * uniform value applied at every breakpoint (including below the smallest
12
+ * one) rather than real Tailwind's optional per-breakpoint object form —
13
+ * the common case in practice, and simpler to reason about; a
14
+ * per-breakpoint padding map is a natural follow-up if ever needed.
15
+ */
16
+ interface ContainerConfig {
17
+ center?: boolean;
18
+ padding?: string;
19
+ }
20
+ interface ThemeConfig {
21
+ /** e.g. "blue-6" -> "#2563eb", or "surface" -> { light, dark } */
22
+ colors: Record<string, ColorEntry>;
23
+ /** e.g. "4" -> 16 (px) */
24
+ spacing: Record<string, number>;
25
+ /** e.g. "sm" -> 640 (min-width px) */
26
+ screens: Record<string, number>;
27
+ /**
28
+ * e.g. "sans" -> "ui-sans-serif, system-ui, ...". Optional — the Rust
29
+ * engine's own `font_family_value` already carries a hardcoded fallback
30
+ * for "sans"/"serif"/"mono" (see its own doc comment) for when a theme
31
+ * omits this field (or a name within it) entirely, matching this field's
32
+ * `#[serde(default)]` on the Rust side. `defaultTheme` below still
33
+ * populates all three explicitly, matching the Rust fallback strings
34
+ * byte-for-byte, so a caller reading `defaultTheme.fontFamily.sans`
35
+ * (e.g. to extend rather than replace it) gets the real value rather
36
+ * than needing to already know the Rust-side fallback text.
37
+ */
38
+ fontFamily?: Record<string, string>;
39
+ darkMode: DarkModeStrategy;
40
+ /** Optional, matching `ContainerConfig`'s own `#[serde(default)]` on the Rust side — an omitted theme.container behaves exactly like `{}`. */
41
+ container?: ContainerConfig;
42
+ }
43
+ declare const defaultTheme: ThemeConfig;
44
+ declare function setTheme(theme: ThemeConfig): void;
45
+ declare function getTheme(): ThemeConfig;
46
+
47
+ /**
48
+ * A `kbach.config.js`-style theme customization, in one plain object — same
49
+ * `theme`/`extend`/`darkMode` shape real Tailwind's own config file uses,
50
+ * simplified from old-kbach's version (no plugins, no color alias chains
51
+ * with cycle detection, no live-update-listener store): a config object
52
+ * goes in, a resolved `ThemeConfig` comes out, via one PURE function
53
+ * (`resolveKbachConfig`) — same input always produces the same output, so
54
+ * nothing needs to stay "in sync" the way old-kbach's Babel-injected config
55
+ * singleton did. Call it identically from a Node.js build script
56
+ * (`vite.config.ts`) and from browser/app code (`applyKbachConfig`) and
57
+ * both get the exact same theme, no synchronization mechanism required.
58
+ */
59
+ interface KbachConfig {
60
+ /** Applied on top of `defaultTheme`; `undefined` means "keep the default". */
61
+ darkMode?: DarkModeStrategy;
62
+ /**
63
+ * REPLACES the named section of `defaultTheme` entirely (e.g. supplying
64
+ * `theme.colors` drops the rest of the default palette) — use `extend`
65
+ * instead to add to the defaults rather than replace them. `container`
66
+ * isn't here (only under `extend` below) — it has no "defaults to
67
+ * replace" the way colors/spacing/screens/fontFamily do, since real
68
+ * Tailwind's own container config is inherently additive (center/padding
69
+ * on top of the always-present breakpoint ladder), so there's no
70
+ * meaningful distinction between "replace" and "extend" for it.
71
+ */
72
+ theme?: Partial<Pick<ThemeConfig, 'colors' | 'spacing' | 'screens' | 'fontFamily'>>;
73
+ /** Merges INTO `defaultTheme` (or `theme` above, if also supplied) — the common case. */
74
+ extend?: {
75
+ /**
76
+ * A value is either a literal color (a hex string, or `{ light, dark }`
77
+ * — same mode-aware shape `useColors()`/`useTheme()` already support)
78
+ * or a REFERENCE to another color name, optionally with an opacity
79
+ * suffix. Two things can be referenced:
80
+ * - A BUILT-IN palette color, always available regardless of ordering
81
+ * (`accent: 'orange-5'`, `accentSoft: 'orange-5/50'`).
82
+ * - A custom color defined EARLIER in this same object
83
+ * (`{ brand: '#ff6b35', brandSoft: 'brand/30' }` — `brandSoft`
84
+ * resolves to `brand`'s own value at 30% opacity, computed once here
85
+ * rather than needing a `colors.alpha()` call at every use site).
86
+ * A string that doesn't match any known color name (built-in or
87
+ * already-defined-above) is treated as a literal CSS value instead
88
+ * (`accent: 'red'` — REAL CSS `red`, since there's no color named "red"
89
+ * to alias), never an error either way.
90
+ */
91
+ colors?: Record<string, ColorEntry | string>;
92
+ spacing?: Record<string, number>;
93
+ screens?: Record<string, number>;
94
+ /** Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` — adds to (or overrides one of) `defaultTheme.fontFamily`'s three names. */
95
+ fontFamily?: Record<string, string>;
96
+ /** `{ center: true, padding: '2rem' }` — see `ContainerConfig`'s own doc comment. */
97
+ container?: ContainerConfig;
98
+ };
99
+ }
100
+ /** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
101
+ declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
102
+ /** `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point from app code. Returns the resolved theme (e.g. to also hand to the Vite plugin's own `theme` option). */
103
+ declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
104
+
105
+ export { type ColorEntry as C, type DarkModeStrategy as D, type KbachConfig as K, type ThemeConfig as T, type ContainerConfig as a, applyKbachConfig as b, defaultTheme as d, getTheme as g, resolveKbachConfig as r, setTheme as s };
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 './theme-BKAb78KP.mjs';
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
- * Reactive read of the global dark-mode store — works with or without a
66
- * `<ThemeProvider>` mounted anywhere in the tree, since it subscribes to
67
- * `darkModeStore.ts` directly. `useSyncExternalStore`'s SSR snapshot always
68
- * returns `false` (light), matching `applyToDom`'s own server no-op — the
69
- * server and the client's very first render agree, so there's no
70
- * hydration-mismatch warning; the real value (if different) applies on the
71
- * client immediately after.
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 useGlobalDarkMode(): boolean;
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 React Context wrapper around `darkModeStore.ts`'s single global
86
- * store — port of old-kbach's `ThemeProvider` (`packages/ui/src/
87
- * ThemeProvider.tsx`), same "provider is a thin wrapper around a global
88
- * store, not the source of truth itself" shape, simplified to this
89
- * package's web-only scope (no native `useColorScheme()`/`windowWidth`
90
- * branch, no `disablePersistence` prop — persistence is always on, since
91
- * `darkModeStore.ts` already guards every `localStorage` access for
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 share the exact
95
- * same `mode`/`isDark` state rather than each getting their own scoped
96
- * value — mounting more than one is very likely a mistake (each fighting
97
- * over the same state via a different `defaultMode`), so this warns once
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 ThemeContextValue {
103
- mode: ThemeMode;
104
- isDark: boolean;
105
- setMode: (mode: ThemeMode) => void;
106
- toggle: () => void;
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
- * Requires a `<ThemeProvider>` ancestor — throws otherwise, since a
110
- * component that destructures `{ mode, setMode }` genuinely needs Context
111
- * (unlike a component that just wants the dark/light boolean, which should
112
- * reach for `useGlobalDarkMode()` instead and never needs a provider at
113
- * all).
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 useTheme(): ThemeContextValue;
146
+ declare function useColors(): ColorsAPI;
116
147
 
117
- export { KbachReset, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useGlobalDarkMode, useTheme };
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 './theme-BKAb78KP.js';
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
- * Reactive read of the global dark-mode store — works with or without a
66
- * `<ThemeProvider>` mounted anywhere in the tree, since it subscribes to
67
- * `darkModeStore.ts` directly. `useSyncExternalStore`'s SSR snapshot always
68
- * returns `false` (light), matching `applyToDom`'s own server no-op — the
69
- * server and the client's very first render agree, so there's no
70
- * hydration-mismatch warning; the real value (if different) applies on the
71
- * client immediately after.
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 useGlobalDarkMode(): boolean;
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 React Context wrapper around `darkModeStore.ts`'s single global
86
- * store — port of old-kbach's `ThemeProvider` (`packages/ui/src/
87
- * ThemeProvider.tsx`), same "provider is a thin wrapper around a global
88
- * store, not the source of truth itself" shape, simplified to this
89
- * package's web-only scope (no native `useColorScheme()`/`windowWidth`
90
- * branch, no `disablePersistence` prop — persistence is always on, since
91
- * `darkModeStore.ts` already guards every `localStorage` access for
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 share the exact
95
- * same `mode`/`isDark` state rather than each getting their own scoped
96
- * value — mounting more than one is very likely a mistake (each fighting
97
- * over the same state via a different `defaultMode`), so this warns once
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 ThemeContextValue {
103
- mode: ThemeMode;
104
- isDark: boolean;
105
- setMode: (mode: ThemeMode) => void;
106
- toggle: () => void;
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
- * Requires a `<ThemeProvider>` ancestor — throws otherwise, since a
110
- * component that destructures `{ mode, setMode }` genuinely needs Context
111
- * (unlike a component that just wants the dark/light boolean, which should
112
- * reach for `useGlobalDarkMode()` instead and never needs a provider at
113
- * all).
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 useTheme(): ThemeContextValue;
146
+ declare function useColors(): ColorsAPI;
116
147
 
117
- export { KbachReset, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useGlobalDarkMode, useTheme };
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 };