@kbach/react 0.6.2 → 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 +39 -73
- package/dist/{chunk-NWW57Q5Q.mjs → chunk-EU7RK3K4.mjs} +1 -1
- package/dist/{chunk-GRZ6MVIL.mjs → chunk-SVPKCLOI.mjs} +74 -19
- package/dist/index.js +74 -19
- package/dist/index.mjs +1 -1
- package/dist/jsx-dev-runtime.js +74 -19
- package/dist/jsx-dev-runtime.mjs +2 -2
- package/dist/jsx-runtime.js +74 -19
- package/dist/jsx-runtime.mjs +2 -2
- package/dist/vite-plugin.js +80 -19
- package/dist/vite-plugin.mjs +80 -19
- package/kbach-react.md +20 -6
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
### 2. Wrap your app
|
|
43
43
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
## React Router
|
|
118
98
|
|
|
119
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -361,7 +361,10 @@ var defaultTheme = {
|
|
|
361
361
|
"2/3": "66.666667%",
|
|
362
362
|
"1/4": "25%",
|
|
363
363
|
"3/4": "75%",
|
|
364
|
-
screen
|
|
364
|
+
// Dynamic viewport unit — see the comment on the 'screen' standalone utilities
|
|
365
|
+
// in utilities.ts for why dvh beats vh on mobile. Used by h-screen (w-screen
|
|
366
|
+
// has its own standalone entry so it never reaches this spacing lookup).
|
|
367
|
+
screen: "100dvh",
|
|
365
368
|
min: "min-content",
|
|
366
369
|
max: "max-content",
|
|
367
370
|
fit: "fit-content"
|
|
@@ -810,15 +813,19 @@ function buildStandalone() {
|
|
|
810
813
|
// Display
|
|
811
814
|
// React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
|
|
812
815
|
// a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
|
|
813
|
-
// `grid`, `
|
|
816
|
+
// `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
|
|
817
|
+
// The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
|
|
818
|
+
// to 'flex' on native instead — like plain `flex`, this is a no-op most of the
|
|
819
|
+
// time, but without it these utilities couldn't re-show a `hidden` element on
|
|
820
|
+
// native either (null → no style applied → still display:'none').
|
|
814
821
|
flex: { display: "flex" },
|
|
815
822
|
block: web ? { display: "block" } : null,
|
|
816
|
-
"inline-block":
|
|
817
|
-
inline: web ?
|
|
823
|
+
"inline-block": { display: web ? "inline-block" : "flex" },
|
|
824
|
+
inline: { display: web ? "inline" : "flex" },
|
|
818
825
|
grid: web ? { display: "grid" } : null,
|
|
819
826
|
grd: web ? { display: "grid" } : null,
|
|
820
827
|
"inline-flex": { display: web ? "inline-flex" : "flex" },
|
|
821
|
-
"inline-grid":
|
|
828
|
+
"inline-grid": { display: web ? "inline-grid" : "flex" },
|
|
822
829
|
hidden: { display: "none" },
|
|
823
830
|
contents: web ? { display: "contents" } : null,
|
|
824
831
|
"flow-root": web ? { display: "flow-root" } : null,
|
|
@@ -1077,12 +1084,17 @@ function buildStandalone() {
|
|
|
1077
1084
|
"text-nowrap": web ? { textWrap: "nowrap" } : null,
|
|
1078
1085
|
"text-balance": web ? { textWrap: "balance" } : null,
|
|
1079
1086
|
"text-pretty": web ? { textWrap: "pretty" } : null,
|
|
1080
|
-
// Screen sizing
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1087
|
+
// Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
|
|
1088
|
+
// browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
|
|
1089
|
+
// so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
|
|
1090
|
+
// the actual visible viewport as browser chrome shows/hides. Desktop behavior
|
|
1091
|
+
// is unchanged since there's no dynamic chrome to account for.
|
|
1092
|
+
// (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
|
|
1093
|
+
"w-screen": web ? { width: "100dvw" } : null,
|
|
1094
|
+
"min-h-screen": { minHeight: "100dvh" },
|
|
1095
|
+
"max-h-screen": { maxHeight: "100dvh" },
|
|
1096
|
+
"min-w-screen": web ? { minWidth: "100dvw" } : null,
|
|
1097
|
+
"max-w-screen": web ? { maxWidth: "100dvw" } : null,
|
|
1086
1098
|
// max-w named container sizes (mirrors Tailwind's container scale)
|
|
1087
1099
|
"max-w-none": { maxWidth: "none" },
|
|
1088
1100
|
"max-w-xs": { maxWidth: 320 },
|
|
@@ -1989,6 +2001,33 @@ var RESOLVERS = {
|
|
|
1989
2001
|
const color = resolveColor(value, colors, isArbitrary);
|
|
1990
2002
|
return color ? { accentColor: color } : null;
|
|
1991
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
|
+
},
|
|
1992
2031
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
1993
2032
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
1994
2033
|
if (!value) return { display: "flex" };
|
|
@@ -2208,34 +2247,50 @@ var RESOLVERS = {
|
|
|
2208
2247
|
const ms = isArbitrary ? value : `${value}ms`;
|
|
2209
2248
|
return { transitionDelay: ms };
|
|
2210
2249
|
},
|
|
2211
|
-
// ── Ring
|
|
2250
|
+
// ── Ring ───────────────────────────────────────────────────────────────────
|
|
2251
|
+
// Web: box-shadow outline ring (doesn't affect layout).
|
|
2252
|
+
// Native: React Native has no box-shadow, so this falls back to borderWidth/
|
|
2253
|
+
// borderColor — the closest visual approximation (used by other RN Tailwind-
|
|
2254
|
+
// likes for the same reason). Unlike the web ring, this DOES affect layout,
|
|
2255
|
+
// and it shares its properties with the `border` utility — combining
|
|
2256
|
+
// `border-*` and `ring-*` on the same native element means whichever class
|
|
2257
|
+
// comes later wins, since both ultimately set borderWidth/borderColor.
|
|
2212
2258
|
ring: ({ value, isArbitrary }, { colors }) => {
|
|
2213
|
-
|
|
2259
|
+
const onWeb = getEffectiveIsWeb();
|
|
2214
2260
|
const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
|
|
2215
2261
|
const DEFAULT_WIDTH = 3;
|
|
2262
|
+
const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
|
|
2216
2263
|
if (!value) {
|
|
2217
|
-
return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
|
|
2264
|
+
return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
|
|
2218
2265
|
}
|
|
2219
2266
|
if (value === "inset") {
|
|
2220
|
-
return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
|
|
2267
|
+
return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
|
|
2221
2268
|
}
|
|
2222
2269
|
const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
|
|
2223
2270
|
if (!isArbitrary && value in widthTokens) {
|
|
2224
2271
|
const w = widthTokens[value];
|
|
2225
|
-
return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
|
|
2272
|
+
return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
|
|
2226
2273
|
}
|
|
2227
2274
|
if (isArbitrary) {
|
|
2228
2275
|
const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
|
|
2229
2276
|
if (numMatch) {
|
|
2230
2277
|
const unit = numMatch[2] ?? "px";
|
|
2231
|
-
return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
|
|
2278
|
+
if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
|
|
2279
|
+
return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
|
|
2232
2280
|
}
|
|
2233
|
-
return { boxShadow: value.replace(/_/g, " ") };
|
|
2281
|
+
return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
|
|
2234
2282
|
}
|
|
2235
2283
|
const color = resolveColor(value, colors, false);
|
|
2236
|
-
if (color)
|
|
2284
|
+
if (color) {
|
|
2285
|
+
return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
|
|
2286
|
+
}
|
|
2237
2287
|
return null;
|
|
2238
2288
|
},
|
|
2289
|
+
// Web-only, unlike `ring` above: this stacks a second box-shadow layer to
|
|
2290
|
+
// create a gap between the element and the ring. There's no native
|
|
2291
|
+
// equivalent to approximate that with (a border can't create a gap outside
|
|
2292
|
+
// its own element without an extra wrapper view), so this stays a no-op
|
|
2293
|
+
// on native rather than rendering a misleading half-translation.
|
|
2239
2294
|
"ring-offset": ({ value, isArbitrary }, _theme) => {
|
|
2240
2295
|
if (!getEffectiveIsWeb()) return null;
|
|
2241
2296
|
const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
|
package/dist/index.js
CHANGED
|
@@ -445,7 +445,10 @@ var defaultTheme = {
|
|
|
445
445
|
"2/3": "66.666667%",
|
|
446
446
|
"1/4": "25%",
|
|
447
447
|
"3/4": "75%",
|
|
448
|
-
screen
|
|
448
|
+
// Dynamic viewport unit — see the comment on the 'screen' standalone utilities
|
|
449
|
+
// in utilities.ts for why dvh beats vh on mobile. Used by h-screen (w-screen
|
|
450
|
+
// has its own standalone entry so it never reaches this spacing lookup).
|
|
451
|
+
screen: "100dvh",
|
|
449
452
|
min: "min-content",
|
|
450
453
|
max: "max-content",
|
|
451
454
|
fit: "fit-content"
|
|
@@ -929,15 +932,19 @@ function buildStandalone() {
|
|
|
929
932
|
// Display
|
|
930
933
|
// React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
|
|
931
934
|
// a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
|
|
932
|
-
// `grid`, `
|
|
935
|
+
// `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
|
|
936
|
+
// The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
|
|
937
|
+
// to 'flex' on native instead — like plain `flex`, this is a no-op most of the
|
|
938
|
+
// time, but without it these utilities couldn't re-show a `hidden` element on
|
|
939
|
+
// native either (null → no style applied → still display:'none').
|
|
933
940
|
flex: { display: "flex" },
|
|
934
941
|
block: web ? { display: "block" } : null,
|
|
935
|
-
"inline-block":
|
|
936
|
-
inline: web ?
|
|
942
|
+
"inline-block": { display: web ? "inline-block" : "flex" },
|
|
943
|
+
inline: { display: web ? "inline" : "flex" },
|
|
937
944
|
grid: web ? { display: "grid" } : null,
|
|
938
945
|
grd: web ? { display: "grid" } : null,
|
|
939
946
|
"inline-flex": { display: web ? "inline-flex" : "flex" },
|
|
940
|
-
"inline-grid":
|
|
947
|
+
"inline-grid": { display: web ? "inline-grid" : "flex" },
|
|
941
948
|
hidden: { display: "none" },
|
|
942
949
|
contents: web ? { display: "contents" } : null,
|
|
943
950
|
"flow-root": web ? { display: "flow-root" } : null,
|
|
@@ -1196,12 +1203,17 @@ function buildStandalone() {
|
|
|
1196
1203
|
"text-nowrap": web ? { textWrap: "nowrap" } : null,
|
|
1197
1204
|
"text-balance": web ? { textWrap: "balance" } : null,
|
|
1198
1205
|
"text-pretty": web ? { textWrap: "pretty" } : null,
|
|
1199
|
-
// Screen sizing
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1206
|
+
// Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
|
|
1207
|
+
// browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
|
|
1208
|
+
// so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
|
|
1209
|
+
// the actual visible viewport as browser chrome shows/hides. Desktop behavior
|
|
1210
|
+
// is unchanged since there's no dynamic chrome to account for.
|
|
1211
|
+
// (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
|
|
1212
|
+
"w-screen": web ? { width: "100dvw" } : null,
|
|
1213
|
+
"min-h-screen": { minHeight: "100dvh" },
|
|
1214
|
+
"max-h-screen": { maxHeight: "100dvh" },
|
|
1215
|
+
"min-w-screen": web ? { minWidth: "100dvw" } : null,
|
|
1216
|
+
"max-w-screen": web ? { maxWidth: "100dvw" } : null,
|
|
1205
1217
|
// max-w named container sizes (mirrors Tailwind's container scale)
|
|
1206
1218
|
"max-w-none": { maxWidth: "none" },
|
|
1207
1219
|
"max-w-xs": { maxWidth: 320 },
|
|
@@ -2108,6 +2120,33 @@ var RESOLVERS = {
|
|
|
2108
2120
|
const color = resolveColor(value, colors, isArbitrary);
|
|
2109
2121
|
return color ? { accentColor: color } : null;
|
|
2110
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
|
+
},
|
|
2111
2150
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
2112
2151
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
2113
2152
|
if (!value) return { display: "flex" };
|
|
@@ -2327,34 +2366,50 @@ var RESOLVERS = {
|
|
|
2327
2366
|
const ms = isArbitrary ? value : `${value}ms`;
|
|
2328
2367
|
return { transitionDelay: ms };
|
|
2329
2368
|
},
|
|
2330
|
-
// ── Ring
|
|
2369
|
+
// ── Ring ───────────────────────────────────────────────────────────────────
|
|
2370
|
+
// Web: box-shadow outline ring (doesn't affect layout).
|
|
2371
|
+
// Native: React Native has no box-shadow, so this falls back to borderWidth/
|
|
2372
|
+
// borderColor — the closest visual approximation (used by other RN Tailwind-
|
|
2373
|
+
// likes for the same reason). Unlike the web ring, this DOES affect layout,
|
|
2374
|
+
// and it shares its properties with the `border` utility — combining
|
|
2375
|
+
// `border-*` and `ring-*` on the same native element means whichever class
|
|
2376
|
+
// comes later wins, since both ultimately set borderWidth/borderColor.
|
|
2331
2377
|
ring: ({ value, isArbitrary }, { colors }) => {
|
|
2332
|
-
|
|
2378
|
+
const onWeb = getEffectiveIsWeb();
|
|
2333
2379
|
const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
|
|
2334
2380
|
const DEFAULT_WIDTH = 3;
|
|
2381
|
+
const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
|
|
2335
2382
|
if (!value) {
|
|
2336
|
-
return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
|
|
2383
|
+
return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
|
|
2337
2384
|
}
|
|
2338
2385
|
if (value === "inset") {
|
|
2339
|
-
return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
|
|
2386
|
+
return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
|
|
2340
2387
|
}
|
|
2341
2388
|
const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
|
|
2342
2389
|
if (!isArbitrary && value in widthTokens) {
|
|
2343
2390
|
const w = widthTokens[value];
|
|
2344
|
-
return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
|
|
2391
|
+
return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
|
|
2345
2392
|
}
|
|
2346
2393
|
if (isArbitrary) {
|
|
2347
2394
|
const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
|
|
2348
2395
|
if (numMatch) {
|
|
2349
2396
|
const unit = numMatch[2] ?? "px";
|
|
2350
|
-
return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
|
|
2397
|
+
if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
|
|
2398
|
+
return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
|
|
2351
2399
|
}
|
|
2352
|
-
return { boxShadow: value.replace(/_/g, " ") };
|
|
2400
|
+
return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
|
|
2353
2401
|
}
|
|
2354
2402
|
const color = resolveColor(value, colors, false);
|
|
2355
|
-
if (color)
|
|
2403
|
+
if (color) {
|
|
2404
|
+
return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
|
|
2405
|
+
}
|
|
2356
2406
|
return null;
|
|
2357
2407
|
},
|
|
2408
|
+
// Web-only, unlike `ring` above: this stacks a second box-shadow layer to
|
|
2409
|
+
// create a gap between the element and the ring. There's no native
|
|
2410
|
+
// equivalent to approximate that with (a border can't create a gap outside
|
|
2411
|
+
// its own element without an extra wrapper view), so this stays a no-op
|
|
2412
|
+
// on native rather than rendering a misleading half-translation.
|
|
2358
2413
|
"ring-offset": ({ value, isArbitrary }, _theme) => {
|
|
2359
2414
|
if (!getEffectiveIsWeb()) return null;
|
|
2360
2415
|
const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
|