@kbach/react 0.6.3 → 0.6.4

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 CHANGED
@@ -20,9 +20,11 @@ npm install @kbach/react
20
20
 
21
21
  ## Setup
22
22
 
23
+ Two steps, and you're done — styles work immediately via runtime CSS injection, no build plugin required:
24
+
23
25
  ### 1. Configure the JSX runtime
24
26
 
25
- **tsconfig.json**
27
+ **tsconfig.json** (works for Vite, Next.js, React Router, and most other tooling — it's the one setting that everything reads):
26
28
 
27
29
  ```json
28
30
  {
@@ -33,121 +35,85 @@ npm install @kbach/react
33
35
  }
34
36
  ```
35
37
 
36
- Or per-file (no config change needed):
38
+ Don't also pass `jsxImportSource` to `@vitejs/plugin-react` (or any other bundler plugin) unless you have a specific reason to — the tsconfig setting alone is enough, and pointing two different places at the JSX runtime is a common source of "it's not applying" confusion (or, worse, plugin conflicts — see the React Router note below).
37
39
 
38
- ```js
39
- /** @jsxImportSource @kbach/react */
40
- ```
40
+ Alternatives if you can't use tsconfig: a per-file `/** @jsxImportSource @kbach/react */` pragma comment, or `['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }]` in `babel.config.js` for non-Vite/non-SWC toolchains.
41
41
 
42
- **Babel (non-Vite)**
42
+ ### 2. Wrap your app
43
43
 
44
- ```js
45
- // babel.config.js
46
- module.exports = {
47
- presets: [
48
- ['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }],
49
- ],
50
- };
44
+ ```jsx
45
+ import { ThemeProvider } from '@kbach/react';
46
+
47
+ export default function Root() {
48
+ return (
49
+ <ThemeProvider defaultMode="system">
50
+ <App />
51
+ </ThemeProvider>
52
+ );
53
+ }
51
54
  ```
52
55
 
53
- ### 2. Add the Vite plugin
56
+ That's the whole setup. `@kbach/react` ships its own `"use client"` directive, so this works directly in a Next.js App Router Server Component tree too — no manual client wrapper needed.
54
57
 
55
- `@kbach/react` doesn't bundle a React plugin for Vite — if your project doesn't already have one, install it first:
58
+ ### Optional: static CSS for production (Vite only)
56
59
 
57
- ```
58
- npm install -D vite @vitejs/plugin-react
59
- ```
60
+ By default, styles are generated and injected into a `<style>` tag by client-side JS at runtime — simple, but it means server-rendered HTML has no matching CSS until hydration runs (a brief flash of unstyled content on first paint). If you're on a Vite-based project and want zero-runtime-cost CSS instead, add the `@kbach/react/vite` plugin:
60
61
 
61
62
  ```ts
62
63
  // vite.config.ts
63
64
  import { defineConfig } from 'vite';
64
- import react from '@vitejs/plugin-react';
65
65
  import { kbach } from '@kbach/react/vite';
66
66
 
67
67
  export default defineConfig({
68
68
  plugins: [
69
- react({ jsxImportSource: '@kbach/react' }),
69
+ // ...your framework's own plugin(s) — see the framework notes below
70
+ // before adding @vitejs/plugin-react yourself.
70
71
  kbach(),
71
72
  ],
72
73
  });
73
74
  ```
74
75
 
75
- The plugin scans your source files and writes generated CSS between `/* kbach:start */` / `/* kbach:end */` markers inside any `kbach.css` file it finds in your project. On HMR only the changed file is rescanned — unchanged tokens reuse cached CSS.
76
-
77
- ### 3. Create and import kbach.css
78
-
79
- Create `kbach.css` anywhere in your project (e.g. `src/kbach.css`):
76
+ Then create an empty `kbach.css` anywhere in your project and import it once in your app entry:
80
77
 
81
78
  ```css
79
+ /* src/kbach.css */
82
80
  /* kbach:start */
83
81
  /* kbach:end */
84
82
  ```
85
83
 
86
- Import it in your app entry:
87
-
88
84
  ```ts
89
85
  // main.tsx
90
86
  import './kbach.css';
91
87
  ```
92
88
 
93
- Importing `kbach.css` automatically disables runtime style injection — all styles come from the CSS file instead.
94
-
95
- ### 4. Wrap your app
96
-
97
- ```jsx
98
- import { ThemeProvider } from '@kbach/react';
99
-
100
- export default function Root() {
101
- return (
102
- <ThemeProvider defaultMode="system">
103
- <App />
104
- </ThemeProvider>
105
- );
106
- }
107
- ```
89
+ The plugin scans your source files and writes generated CSS between the `/* kbach:start */`/`/* kbach:end */` markers. Importing `kbach.css` automatically disables runtime injection — all styles come from the file instead. On HMR, only the changed file is rescanned; unchanged tokens reuse cached CSS.
108
90
 
109
91
  ## Next.js
110
92
 
111
- Steps 1 and 4 above apply as-is — the JSX runtime setup works via `jsxImportSource` in `tsconfig.json` (Next.js's SWC compiler reads it the same way Vite does), and `ThemeProvider` wraps your app the same way.
112
-
113
- Two things differ from the Vite setup:
93
+ Step 1 (tsconfig `jsxImportSource`) and step 2 (`ThemeProvider`) above apply as-is — Next.js's SWC compiler reads `jsxImportSource` from `tsconfig.json` the same way Vite does, and `@kbach/react` shipping its own `"use client"` directive means `ThemeProvider` works directly in a Server Component tree (e.g. the App Router root `layout.tsx`) with no wrapper of your own needed.
114
94
 
115
- **No Vite plugin, no static `kbach.css`.** Next.js builds with webpack/Turbopack, not Vite, so step 2/3 (the `@kbach/react/vite` plugin) don't apply — there's currently no static-CSS build step for Next.js. Styles fall back to runtime injection (the same mechanism used automatically when no `kbach.css` is imported). This works, but since the injection only runs in the browser (there's no `document` during the server render), server-rendered HTML has classes with no matching CSS yet — expect a brief flash of unstyled content on first paint before hydration completes and injects the rules. This is a known limitation, not a bug to work around per-project.
95
+ The "Optional: static CSS" step above doesn't apply — Next.js builds with webpack/Turbopack, not Vite, so there's currently no static-CSS build step for it. Styles fall back to runtime injection, which means the same first-paint FOUC note above applies: expect a brief flash of unstyled content before hydration completes. This is a known limitation, not a per-project bug to work around.
116
96
 
117
- **`ThemeProvider` needs a `'use client'` boundary.** `@kbach/react` doesn't ship the `'use client'` directive in its build output, so importing `ThemeProvider` directly into a Server Component (e.g. the App Router root `layout.tsx`, which is a Server Component by default) will fail. Wrap it in your own client component once:
97
+ ## React Router
118
98
 
119
- ```tsx
120
- // app/providers.tsx
121
- 'use client';
122
- import { ThemeProvider } from '@kbach/react';
99
+ **Framework mode (v7+, SSR by default):** `@react-router/dev`'s own `reactRouter()` Vite plugin already includes its own React JSX transform and Fast Refresh integration — **do not also add `@vitejs/plugin-react`.** Having both active makes each one inject its own Fast Refresh preamble into the same module, which crashes the page at runtime (`Identifier 'RefreshRuntime' has already been declared`) before React ever hydrates — every class on the page will silently fail to style because the app never actually mounts. The correct setup is just:
123
100
 
124
- export function Providers({ children }: { children: React.ReactNode }) {
125
- return <ThemeProvider defaultMode="system">{children}</ThemeProvider>;
126
- }
127
- ```
128
-
129
- ```tsx
130
- // app/layout.tsx (Server Component — no 'use client' needed here)
131
- import { Providers } from './providers';
101
+ ```ts
102
+ // vite.config.ts
103
+ import { reactRouter } from '@react-router/dev/vite';
104
+ import { defineConfig } from 'vite';
105
+ import { kbach } from '@kbach/react/vite'; // omit if you're not using static CSS
132
106
 
133
- export default function RootLayout({ children }: { children: React.ReactNode }) {
134
- return (
135
- <html lang="en">
136
- <body>
137
- <Providers>{children}</Providers>
138
- </body>
139
- </html>
140
- );
141
- }
107
+ export default defineConfig({
108
+ plugins: [kbach(), reactRouter()],
109
+ });
142
110
  ```
143
111
 
144
- This is the standard pattern for any hook-using npm package that predates the App Router — the same workaround is needed for many other UI libraries.
145
-
146
- ## React Router
112
+ `jsxImportSource` in `tsconfig.json` (step 1 above) is all that's needed for the JSX runtime — React Router's Vite plugin reads it the same way plain Vite does. `include`'s default scan dirs (`['src', 'app', 'pages', 'components', 'views', 'layouts']`) already cover React Router's `app/` routes convention if you're using the static-CSS plugin.
147
113
 
148
- **Framework mode (v7, SSR by default):** works with the same Vite-based setup as above — framework mode builds with Vite under the hood, so the `@kbach/react/vite` plugin runs normally. `include` defaults to `['src', 'app', 'pages', 'components', 'views', 'layouts']`, which already covers React Router's `app/` routes convention. Because the static `kbach.css` approach ships real CSS (not runtime JS injection), there's no FOUC concern for server-rendered routes the way there is with Next.js.
114
+ If you do add `kbach()` to `vite.config.ts` and it detects both plugins active together, it prints a warning explaining exactly this at dev-server startup — so this doesn't have to be a silent, hard-to-diagnose blank page.
149
115
 
150
- **Library mode (client-only, `createBrowserRouter`/`<BrowserRouter>`):** no SSR involved — set it up exactly like any other Vite + React app per the steps above.
116
+ **Library mode (client-only, `createBrowserRouter`/`<BrowserRouter>`):** no SSR involved — set it up exactly like any other Vite + React app. Since there's no meta-framework Vite plugin providing JSX handling here, you likely do need `@vitejs/plugin-react({ jsxImportSource: '@kbach/react' })` (or rely on tsconfig alone — Vite's default esbuild-based transform reads it too).
151
117
 
152
118
  ## API
153
119
 
@@ -23,7 +23,7 @@ import {
23
23
  transformToWebProps,
24
24
  useConditionalWidth,
25
25
  useGlobalDarkMode
26
- } from "./chunk-ZZWJK33J.mjs";
26
+ } from "./chunk-SVPKCLOI.mjs";
27
27
 
28
28
  // src/jsx-runtime.tsx
29
29
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -2001,6 +2001,33 @@ var RESOLVERS = {
2001
2001
  const color = resolveColor(value, colors, isArbitrary);
2002
2002
  return color ? { accentColor: color } : null;
2003
2003
  },
2004
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2005
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2006
+ // take stroke/fill as component PROPS, not style entries, so there's no
2007
+ // reliable way to apply a resolved color through the `style` prop there.
2008
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2009
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2010
+ stroke: ({ value, isArbitrary }, { colors }) => {
2011
+ if (!getEffectiveIsWeb()) return null;
2012
+ if (!value) return null;
2013
+ if (value === "none") return { stroke: "none" };
2014
+ if (isArbitrary) {
2015
+ const w = toNativeValue(value);
2016
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2017
+ }
2018
+ const color = resolveColor(value, colors, false);
2019
+ if (color) return { stroke: color };
2020
+ const n = parseFloat(value);
2021
+ return isNaN(n) ? null : { strokeWidth: n };
2022
+ },
2023
+ fill: ({ value, isArbitrary }, { colors }) => {
2024
+ if (!getEffectiveIsWeb()) return null;
2025
+ if (!value) return null;
2026
+ if (value === "none") return { fill: "none" };
2027
+ if (isArbitrary) return { fill: value };
2028
+ const color = resolveColor(value, colors, false);
2029
+ return color ? { fill: color } : null;
2030
+ },
2004
2031
  // ── Flex ───────────────────────────────────────────────────────────────────
2005
2032
  flex: ({ value, isArbitrary }, { flex }) => {
2006
2033
  if (!value) return { display: "flex" };
package/dist/index.js CHANGED
@@ -2120,6 +2120,33 @@ var RESOLVERS = {
2120
2120
  const color = resolveColor(value, colors, isArbitrary);
2121
2121
  return color ? { accentColor: color } : null;
2122
2122
  },
2123
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2124
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2125
+ // take stroke/fill as component PROPS, not style entries, so there's no
2126
+ // reliable way to apply a resolved color through the `style` prop there.
2127
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2128
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2129
+ stroke: ({ value, isArbitrary }, { colors }) => {
2130
+ if (!getEffectiveIsWeb()) return null;
2131
+ if (!value) return null;
2132
+ if (value === "none") return { stroke: "none" };
2133
+ if (isArbitrary) {
2134
+ const w = toNativeValue(value);
2135
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2136
+ }
2137
+ const color = resolveColor(value, colors, false);
2138
+ if (color) return { stroke: color };
2139
+ const n = parseFloat(value);
2140
+ return isNaN(n) ? null : { strokeWidth: n };
2141
+ },
2142
+ fill: ({ value, isArbitrary }, { colors }) => {
2143
+ if (!getEffectiveIsWeb()) return null;
2144
+ if (!value) return null;
2145
+ if (value === "none") return { fill: "none" };
2146
+ if (isArbitrary) return { fill: value };
2147
+ const color = resolveColor(value, colors, false);
2148
+ return color ? { fill: color } : null;
2149
+ },
2123
2150
  // ── Flex ───────────────────────────────────────────────────────────────────
2124
2151
  flex: ({ value, isArbitrary }, { flex }) => {
2125
2152
  if (!value) return { display: "flex" };
package/dist/index.mjs CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  useGlobalDarkMode,
37
37
  useGlobalWidth,
38
38
  useSyncExternalStore
39
- } from "./chunk-ZZWJK33J.mjs";
39
+ } from "./chunk-SVPKCLOI.mjs";
40
40
 
41
41
  // src/context.tsx
42
42
  import { createContext, useContext } from "react";
@@ -2079,6 +2079,33 @@ var RESOLVERS = {
2079
2079
  const color = resolveColor(value, colors, isArbitrary);
2080
2080
  return color ? { accentColor: color } : null;
2081
2081
  },
2082
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2083
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2084
+ // take stroke/fill as component PROPS, not style entries, so there's no
2085
+ // reliable way to apply a resolved color through the `style` prop there.
2086
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2087
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2088
+ stroke: ({ value, isArbitrary }, { colors }) => {
2089
+ if (!getEffectiveIsWeb()) return null;
2090
+ if (!value) return null;
2091
+ if (value === "none") return { stroke: "none" };
2092
+ if (isArbitrary) {
2093
+ const w = toNativeValue(value);
2094
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2095
+ }
2096
+ const color = resolveColor(value, colors, false);
2097
+ if (color) return { stroke: color };
2098
+ const n = parseFloat(value);
2099
+ return isNaN(n) ? null : { strokeWidth: n };
2100
+ },
2101
+ fill: ({ value, isArbitrary }, { colors }) => {
2102
+ if (!getEffectiveIsWeb()) return null;
2103
+ if (!value) return null;
2104
+ if (value === "none") return { fill: "none" };
2105
+ if (isArbitrary) return { fill: value };
2106
+ const color = resolveColor(value, colors, false);
2107
+ return color ? { fill: color } : null;
2108
+ },
2082
2109
  // ── Flex ───────────────────────────────────────────────────────────────────
2083
2110
  flex: ({ value, isArbitrary }, { flex }) => {
2084
2111
  if (!value) return { display: "flex" };
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-4A24NQ5X.mjs";
7
- import "./chunk-ZZWJK33J.mjs";
6
+ } from "./chunk-EU7RK3K4.mjs";
7
+ import "./chunk-SVPKCLOI.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -2080,6 +2080,33 @@ var RESOLVERS = {
2080
2080
  const color = resolveColor(value, colors, isArbitrary);
2081
2081
  return color ? { accentColor: color } : null;
2082
2082
  },
2083
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2084
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2085
+ // take stroke/fill as component PROPS, not style entries, so there's no
2086
+ // reliable way to apply a resolved color through the `style` prop there.
2087
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2088
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2089
+ stroke: ({ value, isArbitrary }, { colors }) => {
2090
+ if (!getEffectiveIsWeb()) return null;
2091
+ if (!value) return null;
2092
+ if (value === "none") return { stroke: "none" };
2093
+ if (isArbitrary) {
2094
+ const w = toNativeValue(value);
2095
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2096
+ }
2097
+ const color = resolveColor(value, colors, false);
2098
+ if (color) return { stroke: color };
2099
+ const n = parseFloat(value);
2100
+ return isNaN(n) ? null : { strokeWidth: n };
2101
+ },
2102
+ fill: ({ value, isArbitrary }, { colors }) => {
2103
+ if (!getEffectiveIsWeb()) return null;
2104
+ if (!value) return null;
2105
+ if (value === "none") return { fill: "none" };
2106
+ if (isArbitrary) return { fill: value };
2107
+ const color = resolveColor(value, colors, false);
2108
+ return color ? { fill: color } : null;
2109
+ },
2083
2110
  // ── Flex ───────────────────────────────────────────────────────────────────
2084
2111
  flex: ({ value, isArbitrary }, { flex }) => {
2085
2112
  if (!value) return { display: "flex" };
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-4A24NQ5X.mjs";
7
+ } from "./chunk-EU7RK3K4.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-ZZWJK33J.mjs";
10
+ } from "./chunk-SVPKCLOI.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,
@@ -1939,6 +1939,33 @@ var RESOLVERS = {
1939
1939
  const color = resolveColor(value, colors, isArbitrary);
1940
1940
  return color ? { accentColor: color } : null;
1941
1941
  },
1942
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
1943
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
1944
+ // take stroke/fill as component PROPS, not style entries, so there's no
1945
+ // reliable way to apply a resolved color through the `style` prop there.
1946
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
1947
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
1948
+ stroke: ({ value, isArbitrary }, { colors }) => {
1949
+ if (!getEffectiveIsWeb()) return null;
1950
+ if (!value) return null;
1951
+ if (value === "none") return { stroke: "none" };
1952
+ if (isArbitrary) {
1953
+ const w = toNativeValue(value);
1954
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
1955
+ }
1956
+ const color = resolveColor(value, colors, false);
1957
+ if (color) return { stroke: color };
1958
+ const n = parseFloat(value);
1959
+ return isNaN(n) ? null : { strokeWidth: n };
1960
+ },
1961
+ fill: ({ value, isArbitrary }, { colors }) => {
1962
+ if (!getEffectiveIsWeb()) return null;
1963
+ if (!value) return null;
1964
+ if (value === "none") return { fill: "none" };
1965
+ if (isArbitrary) return { fill: value };
1966
+ const color = resolveColor(value, colors, false);
1967
+ return color ? { fill: color } : null;
1968
+ },
1942
1969
  // ── Flex ───────────────────────────────────────────────────────────────────
1943
1970
  flex: ({ value, isArbitrary }, { flex }) => {
1944
1971
  if (!value) return { display: "flex" };
@@ -3224,6 +3251,12 @@ function kbach(userConfigOrOptions) {
3224
3251
  enforce: "pre",
3225
3252
  configResolved(resolved) {
3226
3253
  root = resolved.root;
3254
+ const names = new Set(resolved.plugins.map((p) => p.name));
3255
+ if (names.has("react-router") && names.has("vite:react-refresh")) {
3256
+ console.warn(
3257
+ `[kbach] Detected both @vitejs/plugin-react and @react-router/dev's reactRouter() plugin active together. reactRouter() already includes its own JSX transform and Fast Refresh integration \u2014 having both crashes the page at runtime with "Identifier 'RefreshRuntime' has already been declared" before React hydrates, which looks like every class silently failing to style. Remove the react() plugin from vite.config.ts; jsxImportSource in tsconfig.json is enough on its own.`
3258
+ );
3259
+ }
3227
3260
  },
3228
3261
  // When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
3229
3262
  // so styles come solely from the static stylesheet — no duplicate injection.
@@ -1905,6 +1905,33 @@ var RESOLVERS = {
1905
1905
  const color = resolveColor(value, colors, isArbitrary);
1906
1906
  return color ? { accentColor: color } : null;
1907
1907
  },
1908
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
1909
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
1910
+ // take stroke/fill as component PROPS, not style entries, so there's no
1911
+ // reliable way to apply a resolved color through the `style` prop there.
1912
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
1913
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
1914
+ stroke: ({ value, isArbitrary }, { colors }) => {
1915
+ if (!getEffectiveIsWeb()) return null;
1916
+ if (!value) return null;
1917
+ if (value === "none") return { stroke: "none" };
1918
+ if (isArbitrary) {
1919
+ const w = toNativeValue(value);
1920
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
1921
+ }
1922
+ const color = resolveColor(value, colors, false);
1923
+ if (color) return { stroke: color };
1924
+ const n = parseFloat(value);
1925
+ return isNaN(n) ? null : { strokeWidth: n };
1926
+ },
1927
+ fill: ({ value, isArbitrary }, { colors }) => {
1928
+ if (!getEffectiveIsWeb()) return null;
1929
+ if (!value) return null;
1930
+ if (value === "none") return { fill: "none" };
1931
+ if (isArbitrary) return { fill: value };
1932
+ const color = resolveColor(value, colors, false);
1933
+ return color ? { fill: color } : null;
1934
+ },
1908
1935
  // ── Flex ───────────────────────────────────────────────────────────────────
1909
1936
  flex: ({ value, isArbitrary }, { flex }) => {
1910
1937
  if (!value) return { display: "flex" };
@@ -3190,6 +3217,12 @@ function kbach(userConfigOrOptions) {
3190
3217
  enforce: "pre",
3191
3218
  configResolved(resolved) {
3192
3219
  root = resolved.root;
3220
+ const names = new Set(resolved.plugins.map((p) => p.name));
3221
+ if (names.has("react-router") && names.has("vite:react-refresh")) {
3222
+ console.warn(
3223
+ `[kbach] Detected both @vitejs/plugin-react and @react-router/dev's reactRouter() plugin active together. reactRouter() already includes its own JSX transform and Fast Refresh integration \u2014 having both crashes the page at runtime with "Identifier 'RefreshRuntime' has already been declared" before React hydrates, which looks like every class silently failing to style. Remove the react() plugin from vite.config.ts; jsxImportSource in tsconfig.json is enough on its own.`
3224
+ );
3225
+ }
3193
3226
  },
3194
3227
  // When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
3195
3228
  // so styles come solely from the static stylesheet — no duplicate injection.
package/kbach-react.md CHANGED
@@ -13,7 +13,7 @@ Package: `@kbach/react`
13
13
  { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/react" } }
14
14
  ```
15
15
 
16
- ### vite.config.ts
16
+ ### vite.config.ts (plain Vite only — skip @vitejs/plugin-react if a meta-framework already provides its own Vite/React plugin, e.g. React Router's reactRouter(); see below)
17
17
  Requires `vite` and `@vitejs/plugin-react` as dev dependencies — not installed by `@kbach/react` itself: `npm install -D vite @vitejs/plugin-react`.
18
18
  ```ts
19
19
  import { defineConfig } from 'vite';
@@ -50,7 +50,13 @@ import { ThemeProvider } from '@kbach/react';
50
50
  tsconfig `jsxImportSource` setup above applies as-is. No Vite plugin for Next.js (webpack/Turbopack) — falls back to runtime CSS injection, which only runs client-side, so expect a brief flash of unstyled content on first paint before hydration. `@kbach/react`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, and `<ThemeProvider>` directly, with no manual client wrapper needed.
51
51
 
52
52
  ### React Router
53
- Framework mode (v7, SSR) builds with Vite, so the setup above works unchanged — default scan dirs include `app/`. Library mode (client-only) needs no special handling beyond the standard Vite setup.
53
+ Framework mode (v7+, SSR): do NOT add `@vitejs/plugin-react` — `@react-router/dev`'s `reactRouter()` Vite plugin already includes its own JSX transform + Fast Refresh integration. Adding both makes each inject its own Fast Refresh preamble into the same module, crashing the page (`Identifier 'RefreshRuntime' has already been declared`) before React hydrates — every class on the page silently fails to style because the app never mounts. tsconfig `jsxImportSource` alone is enough:
54
+ ```ts
55
+ import { reactRouter } from '@react-router/dev/vite';
56
+ import { kbach } from '@kbach/react/vite'; // omit if not using static CSS
57
+ export default { plugins: [kbach(), reactRouter()] };
58
+ ```
59
+ Default scan dirs include `app/`. Library mode (client-only, no meta-framework Vite plugin involved) needs no special handling beyond the standard Vite setup above.
54
60
 
55
61
  ---
56
62
 
@@ -630,6 +636,8 @@ aspect-auto / aspect-square / aspect-video / aspect-[4/3]
630
636
  columns-{n} / columns-auto / columns-{size}
631
637
  caret-{color} / caret-auto / caret-transparent
632
638
  accent-{color} / accent-auto
639
+ stroke-{color} / stroke-{n} / stroke-none (web only, SVG)
640
+ fill-{color} / fill-none (web only, SVG)
633
641
  mix-blend-{mode}
634
642
  bg-blend-{mode}
635
643
  will-change-auto/scroll/contents/transform
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",