@kbach/react 1.0.0-beta.2 → 1.0.0-beta.20
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 +204 -16
- package/dist/chunk-43FOLRQA.mjs +234 -0
- package/dist/{chunk-PAJ6KHDB.mjs → chunk-5EXGGZMA.mjs} +1 -1
- package/dist/{chunk-ZQJKL2MV.mjs → chunk-BJEOK2VK.mjs} +18 -4
- package/dist/chunk-MRBB55KV.mjs +687 -0
- package/dist/{chunk-N5HCQIBB.mjs → chunk-OVKVZA7B.mjs} +21 -1
- package/dist/{config-BQ_b-qxF.d.mts → config-N74_lKa0.d.mts} +33 -2
- package/dist/{config-BQ_b-qxF.d.ts → config-N74_lKa0.d.ts} +33 -2
- package/dist/index.d.mts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +157 -69
- package/dist/index.mjs +55 -106
- package/dist/jsx-dev-runtime.js +30 -4
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +30 -4
- 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 +1250 -0
- package/dist/postcss-plugin.mjs +84 -0
- package/dist/vite-plugin.d.mts +94 -6
- package/dist/vite-plugin.d.ts +94 -6
- package/dist/vite-plugin.js +495 -910
- package/dist/vite-plugin.mjs +15 -1198
- package/package.json +26 -4
- package/dist/chunk-NKR3PSWO.mjs +0 -115
|
@@ -348,7 +348,19 @@ var defaultTheme = {
|
|
|
348
348
|
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
349
349
|
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
350
350
|
},
|
|
351
|
-
darkMode:
|
|
351
|
+
// Matches real Tailwind's own default (`darkMode: 'media'`): pure
|
|
352
|
+
// `@media (prefers-color-scheme: dark)`, works with zero JS — a project
|
|
353
|
+
// using only static `className` strings gets working `dark:` classes
|
|
354
|
+
// out of the box, with no runtime import required to write `data-theme`/
|
|
355
|
+
// `.dark` onto `<html>` (see darkModeStore.ts's `applyToDom`). The
|
|
356
|
+
// tradeoff: `'media'` can't be overridden by a manual light/dark toggle —
|
|
357
|
+
// `useTheme()`/`toggleGlobalDarkMode()` still track `isDark` correctly
|
|
358
|
+
// for JS consumers, but flipping it has no visible effect on `dark:`
|
|
359
|
+
// classes, since the browser's media query only ever reflects the real
|
|
360
|
+
// OS setting. Opt into `darkMode: 'class'` or `'attribute'` explicitly
|
|
361
|
+
// (via `kbach.config.js` or the `config`/`theme` plugin option) as soon
|
|
362
|
+
// as an app wants a real in-app toggle.
|
|
363
|
+
darkMode: "media",
|
|
352
364
|
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
353
365
|
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
354
366
|
container: {}
|
|
@@ -365,6 +377,13 @@ function getTheme() {
|
|
|
365
377
|
function getThemeJson() {
|
|
366
378
|
return activeThemeJson;
|
|
367
379
|
}
|
|
380
|
+
var DARK_MODE_STRATEGIES = /* @__PURE__ */ new Set(["attribute", "class", "media"]);
|
|
381
|
+
function detectDarkModeStrategyFromDom() {
|
|
382
|
+
if (typeof document === "undefined") return;
|
|
383
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("--kb-dark-mode").trim();
|
|
384
|
+
if (!DARK_MODE_STRATEGIES.has(raw) || raw === activeTheme.darkMode) return;
|
|
385
|
+
setTheme({ ...activeTheme, darkMode: raw });
|
|
386
|
+
}
|
|
368
387
|
|
|
369
388
|
// src/reset.ts
|
|
370
389
|
var RESET_STYLE_ID = "kbach-reset";
|
|
@@ -455,6 +474,7 @@ export {
|
|
|
455
474
|
setTheme,
|
|
456
475
|
getTheme,
|
|
457
476
|
getThemeJson,
|
|
477
|
+
detectDarkModeStrategyFromDom,
|
|
458
478
|
RESET_STYLE_ID,
|
|
459
479
|
BASE_RESET,
|
|
460
480
|
buildResetCSS
|
|
@@ -87,8 +87,28 @@ interface KbachConfig {
|
|
|
87
87
|
* already-defined-above) is treated as a literal CSS value instead
|
|
88
88
|
* (`accent: 'red'` — REAL CSS `red`, since there's no color named "red"
|
|
89
89
|
* to alias), never an error either way.
|
|
90
|
+
*
|
|
91
|
+
* `dark` is a reserved key, not a color name — a SECOND way to write a
|
|
92
|
+
* mode-aware color, as a group instead of per-color: `{ surface:
|
|
93
|
+
* 'gray-2', card: 'white', dark: { surface: 'gray-11', card: 'gray-9' } }`
|
|
94
|
+
* makes `surface`/`card` mode-aware, while a name never mentioned under
|
|
95
|
+
* `dark` (like a hypothetical always-the-same `brand` here) stays a
|
|
96
|
+
* plain, mode-independent color. Equivalent to writing `surface: {
|
|
97
|
+
* light: 'gray-2', dark: 'gray-11' }` directly — pick whichever reads
|
|
98
|
+
* better for your config: per-color pairs when only a couple of colors
|
|
99
|
+
* are mode-aware, the grouped block when most of them are and writing
|
|
100
|
+
* "light"/"dark" twice per line would be repetitive. A name under
|
|
101
|
+
* `dark` with no matching top-level (light) entry is skipped — mode-
|
|
102
|
+
* aware colors need both sides written out. References inside `dark`
|
|
103
|
+
* resolve against the built-in palette and OTHER entries within `dark`
|
|
104
|
+
* itself (not the light-side custom colors, and not a light-side color
|
|
105
|
+
* already written as a `{ light, dark }` pair) — self-contained, the
|
|
106
|
+
* same way the light side already resolves independently of it.
|
|
90
107
|
*/
|
|
91
|
-
colors?:
|
|
108
|
+
colors?: {
|
|
109
|
+
[colorName: string]: ColorEntry | string | Record<string, string> | undefined;
|
|
110
|
+
dark?: Record<string, string>;
|
|
111
|
+
};
|
|
92
112
|
spacing?: Record<string, number>;
|
|
93
113
|
screens?: Record<string, number>;
|
|
94
114
|
/** Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` — adds to (or overrides one of) `defaultTheme.fontFamily`'s three names. */
|
|
@@ -99,7 +119,18 @@ interface KbachConfig {
|
|
|
99
119
|
}
|
|
100
120
|
/** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
|
|
101
121
|
declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
|
|
102
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point
|
|
124
|
+
* from app code. Returns the resolved theme (e.g. to also hand to the Vite
|
|
125
|
+
* plugin's own `theme` option).
|
|
126
|
+
*
|
|
127
|
+
* Also re-syncs the DOM to the CURRENT dark-mode state right after
|
|
128
|
+
* `setTheme()` — see `resyncDomWithActiveTheme`'s own doc comment for why
|
|
129
|
+
* this is required, not optional: without it, an app that configures
|
|
130
|
+
* `darkMode: 'attribute'`/`'class'` here can silently never apply its
|
|
131
|
+
* `dark:` classes on initial load (even though `useTheme().isDark` reads
|
|
132
|
+
* correctly) until some later, unrelated dark-mode event happens to fire.
|
|
133
|
+
*/
|
|
103
134
|
declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
|
|
104
135
|
|
|
105
136
|
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 };
|
|
@@ -87,8 +87,28 @@ interface KbachConfig {
|
|
|
87
87
|
* already-defined-above) is treated as a literal CSS value instead
|
|
88
88
|
* (`accent: 'red'` — REAL CSS `red`, since there's no color named "red"
|
|
89
89
|
* to alias), never an error either way.
|
|
90
|
+
*
|
|
91
|
+
* `dark` is a reserved key, not a color name — a SECOND way to write a
|
|
92
|
+
* mode-aware color, as a group instead of per-color: `{ surface:
|
|
93
|
+
* 'gray-2', card: 'white', dark: { surface: 'gray-11', card: 'gray-9' } }`
|
|
94
|
+
* makes `surface`/`card` mode-aware, while a name never mentioned under
|
|
95
|
+
* `dark` (like a hypothetical always-the-same `brand` here) stays a
|
|
96
|
+
* plain, mode-independent color. Equivalent to writing `surface: {
|
|
97
|
+
* light: 'gray-2', dark: 'gray-11' }` directly — pick whichever reads
|
|
98
|
+
* better for your config: per-color pairs when only a couple of colors
|
|
99
|
+
* are mode-aware, the grouped block when most of them are and writing
|
|
100
|
+
* "light"/"dark" twice per line would be repetitive. A name under
|
|
101
|
+
* `dark` with no matching top-level (light) entry is skipped — mode-
|
|
102
|
+
* aware colors need both sides written out. References inside `dark`
|
|
103
|
+
* resolve against the built-in palette and OTHER entries within `dark`
|
|
104
|
+
* itself (not the light-side custom colors, and not a light-side color
|
|
105
|
+
* already written as a `{ light, dark }` pair) — self-contained, the
|
|
106
|
+
* same way the light side already resolves independently of it.
|
|
90
107
|
*/
|
|
91
|
-
colors?:
|
|
108
|
+
colors?: {
|
|
109
|
+
[colorName: string]: ColorEntry | string | Record<string, string> | undefined;
|
|
110
|
+
dark?: Record<string, string>;
|
|
111
|
+
};
|
|
92
112
|
spacing?: Record<string, number>;
|
|
93
113
|
screens?: Record<string, number>;
|
|
94
114
|
/** Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` — adds to (or overrides one of) `defaultTheme.fontFamily`'s three names. */
|
|
@@ -99,7 +119,18 @@ interface KbachConfig {
|
|
|
99
119
|
}
|
|
100
120
|
/** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
|
|
101
121
|
declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
|
|
102
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point
|
|
124
|
+
* from app code. Returns the resolved theme (e.g. to also hand to the Vite
|
|
125
|
+
* plugin's own `theme` option).
|
|
126
|
+
*
|
|
127
|
+
* Also re-syncs the DOM to the CURRENT dark-mode state right after
|
|
128
|
+
* `setTheme()` — see `resyncDomWithActiveTheme`'s own doc comment for why
|
|
129
|
+
* this is required, not optional: without it, an app that configures
|
|
130
|
+
* `darkMode: 'attribute'`/`'class'` here can silently never apply its
|
|
131
|
+
* `dark:` classes on initial load (even though `useTheme().isDark` reads
|
|
132
|
+
* correctly) until some later, unrelated dark-mode event happens to fire.
|
|
133
|
+
*/
|
|
103
134
|
declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
|
|
104
135
|
|
|
105
136
|
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 { 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-
|
|
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-N74_lKa0.mjs';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
@@ -120,6 +120,22 @@ type ColorsAPI = {
|
|
|
120
120
|
* `rgb(var(--kb-color-x))` mode-aware references.
|
|
121
121
|
*/
|
|
122
122
|
readonly alpha: (color: string, opacity?: number) => string;
|
|
123
|
+
/**
|
|
124
|
+
* Same lookup dot/bracket access does (`colors.get('brand')` ===
|
|
125
|
+
* `colors.brand`), typed as plain `string` instead of `string |
|
|
126
|
+
* ColorScale` — use this whenever the result goes somewhere that expects
|
|
127
|
+
* a real `string` and hits a type error otherwise. The wider union on
|
|
128
|
+
* every OTHER property here isn't a bug: a flat custom color (`surface`)
|
|
129
|
+
* and a shade-family prefix (`blue`) are served by the exact same
|
|
130
|
+
* object, and TypeScript has no way to statically know which one any
|
|
131
|
+
* given key names — `kbach.config.js`'s colors are plain runtime JS, not
|
|
132
|
+
* a type declaration this package could inspect ahead of time. A name
|
|
133
|
+
* with no matching theme entry falls back to the name itself,
|
|
134
|
+
* unresolved — the same "not a known color name -> literal value"
|
|
135
|
+
* convention `kbach.config.js`'s own color aliasing already uses (see
|
|
136
|
+
* `config.ts`'s `resolveColorEntry`), never an error.
|
|
137
|
+
*/
|
|
138
|
+
readonly get: (name: string) => string;
|
|
123
139
|
};
|
|
124
140
|
/**
|
|
125
141
|
* Resolved theme colors as real values — for anywhere you need an actual
|
|
@@ -141,8 +157,57 @@ type ColorsAPI = {
|
|
|
141
157
|
* colors.blue['6/50']; // same, at 50% opacity
|
|
142
158
|
* colors.surface; // "rgb(var(--kb-color-surface))" if defined as { light, dark }
|
|
143
159
|
* colors.alpha(colors.surface, 50); // "rgba(var(--kb-color-surface), 0.5)"
|
|
160
|
+
* colors.get('surface'); // same value as colors.surface, typed as plain `string`
|
|
144
161
|
* ```
|
|
145
162
|
*/
|
|
146
163
|
declare function useColors(): ColorsAPI;
|
|
147
164
|
|
|
148
|
-
|
|
165
|
+
/**
|
|
166
|
+
* A tiny class-name composer — combine strings, arrays, and objects (with
|
|
167
|
+
* boolean values) into one space-separated className, skipping anything
|
|
168
|
+
* falsy. Matches the widely-used `clsx` npm package's public API and
|
|
169
|
+
* behavior exactly (a from-scratch reimplementation, not a fork or a thin
|
|
170
|
+
* wrapper around it) — this package ships its own copy so using it adds
|
|
171
|
+
* zero runtime dependencies, and so it works identically whether or not
|
|
172
|
+
* `clsx`/`classnames` happens to already be installed.
|
|
173
|
+
*
|
|
174
|
+
* Kbach class strings never NEED this — a Kbach `className` is just a
|
|
175
|
+
* plain string, composable with plain JS (`"bg-" + color + "-6"`,
|
|
176
|
+
* `[base, active && "opacity-100"].join(" ")`, see this package's own
|
|
177
|
+
* README) with no special API required. This exists purely as an
|
|
178
|
+
* ergonomic convenience for the common "several conditional classes on one
|
|
179
|
+
* element" shape, for anyone who'd rather write it declaratively:
|
|
180
|
+
*
|
|
181
|
+
* ```ts
|
|
182
|
+
* clsx("px-4 py-2", isActive && "bg-blue-6", { "opacity-50": disabled });
|
|
183
|
+
* // "px-4 py-2 bg-blue-6" (when isActive is true and disabled is false)
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* The static build-time scanner (vite-plugin/postcss-plugin) already
|
|
187
|
+
* recognizes `clsx(...)` call sites (alongside `cn`/`classnames`/`cx`/
|
|
188
|
+
* `kb`) and extracts every string literal inside them on its own — using
|
|
189
|
+
* this function needs no extra plugin configuration to keep working with
|
|
190
|
+
* the static CSS build.
|
|
191
|
+
*
|
|
192
|
+
* Identical to `@kbach/react-native`'s own `clsx.ts` — deliberately
|
|
193
|
+
* duplicated rather than factored into a shared package, since this is
|
|
194
|
+
* genuinely all there is to it and a whole extra published package for one
|
|
195
|
+
* ~20-line function would be more ceremony than the thing itself.
|
|
196
|
+
*/
|
|
197
|
+
/** Anything `clsx` accepts as one argument — recursively, for arrays. */
|
|
198
|
+
type ClassValue = ClassArray | ClassDictionary | string | number | bigint | boolean | null | undefined;
|
|
199
|
+
/** A key is kept (as a literal class name) only when its value is truthy — `{ "opacity-50": disabled }`. */
|
|
200
|
+
interface ClassDictionary {
|
|
201
|
+
[className: string]: unknown;
|
|
202
|
+
}
|
|
203
|
+
type ClassArray = ClassValue[];
|
|
204
|
+
/**
|
|
205
|
+
* Joins every truthy piece across all arguments with a single space,
|
|
206
|
+
* skipping falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`)
|
|
207
|
+
* entirely — they contribute nothing, not even an extra space. Arrays are
|
|
208
|
+
* flattened (recursively, to any depth); a plain object keeps only the
|
|
209
|
+
* keys whose value is truthy.
|
|
210
|
+
*/
|
|
211
|
+
declare function clsx(...inputs: ClassValue[]): string;
|
|
212
|
+
|
|
213
|
+
export { type ClassArray, type ClassDictionary, type ClassValue, type ColorScale, type ColorsAPI, KbachReset, type ThemeMode, ThemeProvider, type ThemeProviderProps, type ThemeState, clsx, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useColors, useTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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-N74_lKa0.js';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
@@ -120,6 +120,22 @@ type ColorsAPI = {
|
|
|
120
120
|
* `rgb(var(--kb-color-x))` mode-aware references.
|
|
121
121
|
*/
|
|
122
122
|
readonly alpha: (color: string, opacity?: number) => string;
|
|
123
|
+
/**
|
|
124
|
+
* Same lookup dot/bracket access does (`colors.get('brand')` ===
|
|
125
|
+
* `colors.brand`), typed as plain `string` instead of `string |
|
|
126
|
+
* ColorScale` — use this whenever the result goes somewhere that expects
|
|
127
|
+
* a real `string` and hits a type error otherwise. The wider union on
|
|
128
|
+
* every OTHER property here isn't a bug: a flat custom color (`surface`)
|
|
129
|
+
* and a shade-family prefix (`blue`) are served by the exact same
|
|
130
|
+
* object, and TypeScript has no way to statically know which one any
|
|
131
|
+
* given key names — `kbach.config.js`'s colors are plain runtime JS, not
|
|
132
|
+
* a type declaration this package could inspect ahead of time. A name
|
|
133
|
+
* with no matching theme entry falls back to the name itself,
|
|
134
|
+
* unresolved — the same "not a known color name -> literal value"
|
|
135
|
+
* convention `kbach.config.js`'s own color aliasing already uses (see
|
|
136
|
+
* `config.ts`'s `resolveColorEntry`), never an error.
|
|
137
|
+
*/
|
|
138
|
+
readonly get: (name: string) => string;
|
|
123
139
|
};
|
|
124
140
|
/**
|
|
125
141
|
* Resolved theme colors as real values — for anywhere you need an actual
|
|
@@ -141,8 +157,57 @@ type ColorsAPI = {
|
|
|
141
157
|
* colors.blue['6/50']; // same, at 50% opacity
|
|
142
158
|
* colors.surface; // "rgb(var(--kb-color-surface))" if defined as { light, dark }
|
|
143
159
|
* colors.alpha(colors.surface, 50); // "rgba(var(--kb-color-surface), 0.5)"
|
|
160
|
+
* colors.get('surface'); // same value as colors.surface, typed as plain `string`
|
|
144
161
|
* ```
|
|
145
162
|
*/
|
|
146
163
|
declare function useColors(): ColorsAPI;
|
|
147
164
|
|
|
148
|
-
|
|
165
|
+
/**
|
|
166
|
+
* A tiny class-name composer — combine strings, arrays, and objects (with
|
|
167
|
+
* boolean values) into one space-separated className, skipping anything
|
|
168
|
+
* falsy. Matches the widely-used `clsx` npm package's public API and
|
|
169
|
+
* behavior exactly (a from-scratch reimplementation, not a fork or a thin
|
|
170
|
+
* wrapper around it) — this package ships its own copy so using it adds
|
|
171
|
+
* zero runtime dependencies, and so it works identically whether or not
|
|
172
|
+
* `clsx`/`classnames` happens to already be installed.
|
|
173
|
+
*
|
|
174
|
+
* Kbach class strings never NEED this — a Kbach `className` is just a
|
|
175
|
+
* plain string, composable with plain JS (`"bg-" + color + "-6"`,
|
|
176
|
+
* `[base, active && "opacity-100"].join(" ")`, see this package's own
|
|
177
|
+
* README) with no special API required. This exists purely as an
|
|
178
|
+
* ergonomic convenience for the common "several conditional classes on one
|
|
179
|
+
* element" shape, for anyone who'd rather write it declaratively:
|
|
180
|
+
*
|
|
181
|
+
* ```ts
|
|
182
|
+
* clsx("px-4 py-2", isActive && "bg-blue-6", { "opacity-50": disabled });
|
|
183
|
+
* // "px-4 py-2 bg-blue-6" (when isActive is true and disabled is false)
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* The static build-time scanner (vite-plugin/postcss-plugin) already
|
|
187
|
+
* recognizes `clsx(...)` call sites (alongside `cn`/`classnames`/`cx`/
|
|
188
|
+
* `kb`) and extracts every string literal inside them on its own — using
|
|
189
|
+
* this function needs no extra plugin configuration to keep working with
|
|
190
|
+
* the static CSS build.
|
|
191
|
+
*
|
|
192
|
+
* Identical to `@kbach/react-native`'s own `clsx.ts` — deliberately
|
|
193
|
+
* duplicated rather than factored into a shared package, since this is
|
|
194
|
+
* genuinely all there is to it and a whole extra published package for one
|
|
195
|
+
* ~20-line function would be more ceremony than the thing itself.
|
|
196
|
+
*/
|
|
197
|
+
/** Anything `clsx` accepts as one argument — recursively, for arrays. */
|
|
198
|
+
type ClassValue = ClassArray | ClassDictionary | string | number | bigint | boolean | null | undefined;
|
|
199
|
+
/** A key is kept (as a literal class name) only when its value is truthy — `{ "opacity-50": disabled }`. */
|
|
200
|
+
interface ClassDictionary {
|
|
201
|
+
[className: string]: unknown;
|
|
202
|
+
}
|
|
203
|
+
type ClassArray = ClassValue[];
|
|
204
|
+
/**
|
|
205
|
+
* Joins every truthy piece across all arguments with a single space,
|
|
206
|
+
* skipping falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`)
|
|
207
|
+
* entirely — they contribute nothing, not even an extra space. Arrays are
|
|
208
|
+
* flattened (recursively, to any depth); a plain object keeps only the
|
|
209
|
+
* keys whose value is truthy.
|
|
210
|
+
*/
|
|
211
|
+
declare function clsx(...inputs: ClassValue[]): string;
|
|
212
|
+
|
|
213
|
+
export { type ClassArray, type ClassDictionary, type ClassValue, type ColorScale, type ColorsAPI, KbachReset, type ThemeMode, ThemeProvider, type ThemeProviderProps, type ThemeState, clsx, disableRuntimeCSS, getGlobalDarkMode, getGlobalThemeMode, initKbach, isKbachReady, isRuntimeCSSDisabled, kb, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode, useColors, useTheme };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(src_exports, {
|
|
|
33
33
|
KbachReset: () => KbachReset,
|
|
34
34
|
ThemeProvider: () => ThemeProvider,
|
|
35
35
|
applyKbachConfig: () => applyKbachConfig,
|
|
36
|
+
clsx: () => clsx,
|
|
36
37
|
defaultTheme: () => defaultTheme,
|
|
37
38
|
disableRuntimeCSS: () => disableRuntimeCSS,
|
|
38
39
|
getGlobalDarkMode: () => getGlobalDarkMode,
|
|
@@ -70,7 +71,7 @@ function isKbachReady() {
|
|
|
70
71
|
function generateCss(classString, themeJson) {
|
|
71
72
|
if (!ready) {
|
|
72
73
|
throw new Error(
|
|
73
|
-
"[
|
|
74
|
+
"[Kbach] generateCss() was called before initKbach() resolved. Call `await initKbach()` once at startup (e.g. before rendering your app) before using kb()."
|
|
74
75
|
);
|
|
75
76
|
}
|
|
76
77
|
return (0, import_core_engine.generate_css)(classString, themeJson);
|
|
@@ -426,7 +427,19 @@ var defaultTheme = {
|
|
|
426
427
|
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
427
428
|
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
428
429
|
},
|
|
429
|
-
darkMode:
|
|
430
|
+
// Matches real Tailwind's own default (`darkMode: 'media'`): pure
|
|
431
|
+
// `@media (prefers-color-scheme: dark)`, works with zero JS — a project
|
|
432
|
+
// using only static `className` strings gets working `dark:` classes
|
|
433
|
+
// out of the box, with no runtime import required to write `data-theme`/
|
|
434
|
+
// `.dark` onto `<html>` (see darkModeStore.ts's `applyToDom`). The
|
|
435
|
+
// tradeoff: `'media'` can't be overridden by a manual light/dark toggle —
|
|
436
|
+
// `useTheme()`/`toggleGlobalDarkMode()` still track `isDark` correctly
|
|
437
|
+
// for JS consumers, but flipping it has no visible effect on `dark:`
|
|
438
|
+
// classes, since the browser's media query only ever reflects the real
|
|
439
|
+
// OS setting. Opt into `darkMode: 'class'` or `'attribute'` explicitly
|
|
440
|
+
// (via `kbach.config.js` or the `config`/`theme` plugin option) as soon
|
|
441
|
+
// as an app wants a real in-app toggle.
|
|
442
|
+
darkMode: "media",
|
|
430
443
|
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
431
444
|
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
432
445
|
container: {}
|
|
@@ -443,6 +456,13 @@ function getTheme() {
|
|
|
443
456
|
function getThemeJson() {
|
|
444
457
|
return activeThemeJson;
|
|
445
458
|
}
|
|
459
|
+
var DARK_MODE_STRATEGIES = /* @__PURE__ */ new Set(["attribute", "class", "media"]);
|
|
460
|
+
function detectDarkModeStrategyFromDom() {
|
|
461
|
+
if (typeof document === "undefined") return;
|
|
462
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("--kb-dark-mode").trim();
|
|
463
|
+
if (!DARK_MODE_STRATEGIES.has(raw) || raw === activeTheme.darkMode) return;
|
|
464
|
+
setTheme({ ...activeTheme, darkMode: raw });
|
|
465
|
+
}
|
|
446
466
|
|
|
447
467
|
// src/reset.ts
|
|
448
468
|
var RESET_STYLE_ID = "kbach-reset";
|
|
@@ -575,9 +595,23 @@ function ensureResetInjected() {
|
|
|
575
595
|
el.textContent = BASE_RESET;
|
|
576
596
|
document.head.insertBefore(el, document.head.firstChild);
|
|
577
597
|
}
|
|
598
|
+
var KB_CACHE_MAX = 500;
|
|
599
|
+
var kbCache = /* @__PURE__ */ new Map();
|
|
600
|
+
var cachedForThemeJson = null;
|
|
578
601
|
function kb(classString) {
|
|
579
|
-
const
|
|
580
|
-
|
|
602
|
+
const themeJson = getThemeJson();
|
|
603
|
+
if (themeJson !== cachedForThemeJson) {
|
|
604
|
+
kbCache.clear();
|
|
605
|
+
cachedForThemeJson = themeJson;
|
|
606
|
+
}
|
|
607
|
+
let result = kbCache.get(classString);
|
|
608
|
+
if (result === void 0) {
|
|
609
|
+
const json = generateCss(classString, themeJson);
|
|
610
|
+
result = JSON.parse(json);
|
|
611
|
+
if (kbCache.size >= KB_CACHE_MAX) kbCache.clear();
|
|
612
|
+
kbCache.set(classString, result);
|
|
613
|
+
}
|
|
614
|
+
const { className, rules } = result;
|
|
581
615
|
if (!runtimeCSSDisabled) {
|
|
582
616
|
ensureResetInjected();
|
|
583
617
|
for (const { rule, order } of rules) {
|
|
@@ -644,63 +678,6 @@ function ensureColorVariablesInjected(theme) {
|
|
|
644
678
|
${wrapDarkDecls(theme.darkMode, darkDecls.join(" "))}`;
|
|
645
679
|
}
|
|
646
680
|
|
|
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
|
-
|
|
704
681
|
// src/darkModeStore.ts
|
|
705
682
|
var STORAGE_KEY = "kbach-theme";
|
|
706
683
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -728,13 +705,15 @@ function applyToDom(dark) {
|
|
|
728
705
|
if (typeof document === "undefined") return;
|
|
729
706
|
const root = document.documentElement;
|
|
730
707
|
const strategy = getTheme().darkMode;
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
} else if (strategy === "attribute") {
|
|
708
|
+
root.classList.toggle("dark", strategy === "class" && dark);
|
|
709
|
+
root.classList.toggle("light", strategy === "class" && !dark);
|
|
710
|
+
if (strategy === "attribute") {
|
|
735
711
|
root.setAttribute("data-theme", dark ? "dark" : "light");
|
|
712
|
+
} else {
|
|
713
|
+
root.removeAttribute("data-theme");
|
|
736
714
|
}
|
|
737
715
|
}
|
|
716
|
+
detectDarkModeStrategyFromDom();
|
|
738
717
|
var persistedMode = readPersistedMode();
|
|
739
718
|
var hadPersistedMode = persistedMode !== null;
|
|
740
719
|
var mode = persistedMode ?? "system";
|
|
@@ -754,6 +733,9 @@ if (typeof window !== "undefined" && window.matchMedia) {
|
|
|
754
733
|
function notify() {
|
|
755
734
|
for (const listener of listeners) listener();
|
|
756
735
|
}
|
|
736
|
+
function resyncDomWithActiveTheme() {
|
|
737
|
+
applyToDom(isDark);
|
|
738
|
+
}
|
|
757
739
|
function getGlobalDarkMode() {
|
|
758
740
|
return isDark;
|
|
759
741
|
}
|
|
@@ -788,7 +770,81 @@ function seedDefaultMode(fallback) {
|
|
|
788
770
|
notify();
|
|
789
771
|
}
|
|
790
772
|
|
|
773
|
+
// src/config.ts
|
|
774
|
+
function applyOpacityToHex(hex, opacityText) {
|
|
775
|
+
const rgb = parseHexRgb(hex);
|
|
776
|
+
if (!rgb) return hex;
|
|
777
|
+
const a = Number(opacityText) / 100;
|
|
778
|
+
const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
|
|
779
|
+
return `rgba(${rgb.join(",")},${clamped})`;
|
|
780
|
+
}
|
|
781
|
+
function resolveColorEntry(value, lookup) {
|
|
782
|
+
if (typeof value !== "string") return value;
|
|
783
|
+
const slash = value.indexOf("/");
|
|
784
|
+
const name = slash === -1 ? value : value.slice(0, slash);
|
|
785
|
+
const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
|
|
786
|
+
const base = lookup(name);
|
|
787
|
+
if (base === void 0) return value;
|
|
788
|
+
if (opacity === void 0) return base;
|
|
789
|
+
return typeof base === "string" ? applyOpacityToHex(base, opacity) : { light: applyOpacityToHex(base.light, opacity), dark: applyOpacityToHex(base.dark, opacity) };
|
|
790
|
+
}
|
|
791
|
+
function resolveColors(extend, existing) {
|
|
792
|
+
const { dark: darkExtend, ...lightExtend } = extend;
|
|
793
|
+
const resolved = {};
|
|
794
|
+
const lookup = (name) => resolved[name] ?? existing[name];
|
|
795
|
+
for (const [name, value] of Object.entries(lightExtend)) {
|
|
796
|
+
if (typeof value === "object" && value !== null && !("light" in value) && !("dark" in value)) continue;
|
|
797
|
+
resolved[name] = resolveColorEntry(value, lookup);
|
|
798
|
+
}
|
|
799
|
+
if (darkExtend) {
|
|
800
|
+
const resolvedDark = {};
|
|
801
|
+
const lookupDark = (name) => {
|
|
802
|
+
const found = resolvedDark[name] ?? existing[name];
|
|
803
|
+
return typeof found === "string" ? found : void 0;
|
|
804
|
+
};
|
|
805
|
+
for (const [name, value] of Object.entries(darkExtend)) {
|
|
806
|
+
const entry = resolveColorEntry(value, lookupDark);
|
|
807
|
+
resolvedDark[name] = typeof entry === "string" ? entry : entry.dark;
|
|
808
|
+
}
|
|
809
|
+
for (const [name, darkValue] of Object.entries(resolvedDark)) {
|
|
810
|
+
const lightEntry = resolved[name];
|
|
811
|
+
if (lightEntry === void 0 || typeof lightEntry !== "string") continue;
|
|
812
|
+
resolved[name] = { light: lightEntry, dark: darkValue };
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return resolved;
|
|
816
|
+
}
|
|
817
|
+
function resolveKbachConfig(config) {
|
|
818
|
+
let theme = { ...defaultTheme, ...config.theme };
|
|
819
|
+
if (config.extend?.colors) {
|
|
820
|
+
theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
|
|
821
|
+
}
|
|
822
|
+
if (config.extend?.spacing) {
|
|
823
|
+
theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
|
|
824
|
+
}
|
|
825
|
+
if (config.extend?.screens) {
|
|
826
|
+
theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
|
|
827
|
+
}
|
|
828
|
+
if (config.extend?.fontFamily) {
|
|
829
|
+
theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
|
|
830
|
+
}
|
|
831
|
+
if (config.extend?.container) {
|
|
832
|
+
theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
|
|
833
|
+
}
|
|
834
|
+
if (config.darkMode) {
|
|
835
|
+
theme = { ...theme, darkMode: config.darkMode };
|
|
836
|
+
}
|
|
837
|
+
return theme;
|
|
838
|
+
}
|
|
839
|
+
function applyKbachConfig(config) {
|
|
840
|
+
const theme = resolveKbachConfig(config);
|
|
841
|
+
setTheme(theme);
|
|
842
|
+
resyncDomWithActiveTheme();
|
|
843
|
+
return theme;
|
|
844
|
+
}
|
|
845
|
+
|
|
791
846
|
// src/useTheme.ts
|
|
847
|
+
var import_react = require("react");
|
|
792
848
|
function useTheme() {
|
|
793
849
|
const isDark2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
|
|
794
850
|
const mode2 = (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalThemeMode, () => "system");
|
|
@@ -806,10 +862,12 @@ var warnedMultipleProviders = false;
|
|
|
806
862
|
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
807
863
|
(0, import_react2.useEffect)(() => {
|
|
808
864
|
mountedProviderCount++;
|
|
809
|
-
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
865
|
+
if (mountedProviderCount > 1 && !warnedMultipleProviders && process.env.NODE_ENV !== "production") {
|
|
810
866
|
warnedMultipleProviders = true;
|
|
811
867
|
console.warn(
|
|
812
|
-
"[
|
|
868
|
+
"%c[Kbach]%c Multiple <ThemeProvider> instances are mounted at once.\nDark mode is one global store \u2014 useTheme() reads it everywhere, not scoped per provider \u2014 so their defaultMode seeds will fight over the same state.\nMount exactly one <ThemeProvider>, as high in the tree as convenient.",
|
|
869
|
+
"background:#7c3aed;color:#fff;font-weight:600;padding:1px 6px;border-radius:3px",
|
|
870
|
+
"font-weight:400"
|
|
813
871
|
);
|
|
814
872
|
}
|
|
815
873
|
return () => {
|
|
@@ -826,6 +884,7 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
826
884
|
}
|
|
827
885
|
|
|
828
886
|
// src/useColors.ts
|
|
887
|
+
var import_react3 = require("react");
|
|
829
888
|
function applyOpacity(color, opacity) {
|
|
830
889
|
const a = Math.max(0, Math.min(1, opacity / 100));
|
|
831
890
|
const varMatch = /^rgb\(var\((--[\w-]+)\)\)$/.exec(color);
|
|
@@ -870,11 +929,13 @@ function makeColorsProxy(theme, isDark2) {
|
|
|
870
929
|
}
|
|
871
930
|
});
|
|
872
931
|
}
|
|
873
|
-
|
|
932
|
+
const get = (name) => resolveNamed(name) ?? name;
|
|
933
|
+
return new Proxy({ alpha, get }, {
|
|
874
934
|
get(_, prop) {
|
|
875
935
|
const key = String(prop);
|
|
876
936
|
if (key === "then") return void 0;
|
|
877
937
|
if (key === "alpha") return alpha;
|
|
938
|
+
if (key === "get") return get;
|
|
878
939
|
const direct = resolveNamed(key);
|
|
879
940
|
if (direct !== void 0) return direct;
|
|
880
941
|
if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
|
|
@@ -886,13 +947,40 @@ function useColors() {
|
|
|
886
947
|
const { isDark: isDark2 } = useTheme();
|
|
887
948
|
const theme = getTheme();
|
|
888
949
|
ensureColorVariablesInjected(theme);
|
|
889
|
-
return makeColorsProxy(theme, isDark2);
|
|
950
|
+
return (0, import_react3.useMemo)(() => makeColorsProxy(theme, isDark2), [theme, isDark2]);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/clsx.ts
|
|
954
|
+
function clsx(...inputs) {
|
|
955
|
+
let out = "";
|
|
956
|
+
for (const input of inputs) {
|
|
957
|
+
const piece = resolveClassValue(input);
|
|
958
|
+
if (piece) out = out ? `${out} ${piece}` : piece;
|
|
959
|
+
}
|
|
960
|
+
return out;
|
|
961
|
+
}
|
|
962
|
+
function resolveClassValue(value) {
|
|
963
|
+
if (!value) return "";
|
|
964
|
+
const type = typeof value;
|
|
965
|
+
if (type === "string" || type === "number" || type === "bigint") return String(value);
|
|
966
|
+
if (Array.isArray(value)) return clsx(...value);
|
|
967
|
+
if (type === "object") {
|
|
968
|
+
let out = "";
|
|
969
|
+
for (const key in value) {
|
|
970
|
+
if (Object.prototype.hasOwnProperty.call(value, key) && value[key]) {
|
|
971
|
+
out = out ? `${out} ${key}` : key;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return out;
|
|
975
|
+
}
|
|
976
|
+
return "";
|
|
890
977
|
}
|
|
891
978
|
// Annotate the CommonJS export names for ESM import in node:
|
|
892
979
|
0 && (module.exports = {
|
|
893
980
|
KbachReset,
|
|
894
981
|
ThemeProvider,
|
|
895
982
|
applyKbachConfig,
|
|
983
|
+
clsx,
|
|
896
984
|
defaultTheme,
|
|
897
985
|
disableRuntimeCSS,
|
|
898
986
|
getGlobalDarkMode,
|