@kbach/react-native 1.0.0-beta.8 → 1.0.0-beta.9
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 +67 -235
- package/android/src/main/jniLibs/arm64-v8a/libkbach_core_engine.so +0 -0
- package/android/src/main/jniLibs/x86_64/libkbach_core_engine.so +0 -0
- package/dist/{chunk-KV3ZIXSH.mjs → chunk-DOWW44F7.mjs} +1 -1
- package/dist/chunk-H5Z24XX5.mjs +192 -0
- package/dist/{chunk-4BXM3QUV.mjs → chunk-HK6PDJJK.mjs} +21 -6
- package/dist/{chunk-JR3BLUAV.mjs → chunk-SY5OAY2C.mjs} +11 -4
- package/dist/chunk-X4UQCEOE.mjs +186 -0
- package/dist/{chunk-OIW7OQWO.mjs → chunk-YOC3SHF5.mjs} +1 -1
- package/dist/index.d.mts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +176 -39
- package/dist/index.mjs +9 -5
- package/dist/index.web.d.mts +1 -1
- package/dist/index.web.d.ts +1 -1
- package/dist/index.web.js +171 -39
- package/dist/index.web.mjs +9 -5
- package/dist/jsx-dev-runtime.js +31 -9
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-dev-runtime.web.js +22 -7
- package/dist/jsx-dev-runtime.web.mjs +3 -3
- package/dist/jsx-runtime.js +31 -9
- package/dist/jsx-runtime.mjs +3 -3
- package/dist/jsx-runtime.web.js +22 -7
- package/dist/jsx-runtime.web.mjs +3 -3
- package/dist/useColors-CeP5_HvM.d.mts +230 -0
- package/dist/useColors-CeP5_HvM.d.ts +230 -0
- package/package.json +1 -1
- package/src/babel-plugin.js +74 -2
- package/dist/ThemeContext-Dc5AC-m0.d.mts +0 -98
- package/dist/ThemeContext-Dc5AC-m0.d.ts +0 -98
- package/dist/chunk-CGU7UM2F.mjs +0 -66
- package/dist/chunk-JS5KIGAB.mjs +0 -192
package/README.md
CHANGED
|
@@ -2,37 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Kbach's utility-class styling engine for React Native — the same class
|
|
4
4
|
names as [`@kbach/react`](https://www.npmjs.com/package/@kbach/react),
|
|
5
|
-
resolved to a
|
|
5
|
+
resolved to a `StyleSheet`-ready style object instead of injected CSS.
|
|
6
6
|
|
|
7
|
-
>
|
|
8
|
-
>
|
|
9
|
-
> react-native-web resolves through the same Rust engine compiled to WASM,
|
|
10
|
-
> but to real generated CSS instead (full modifier support, including
|
|
11
|
-
> `hover:`/`focus:`/etc. — see "Expo Web" below); Expo Go — which can load
|
|
12
|
-
> neither a TurboModule nor WASM — resolves through a third, pure-JS
|
|
13
|
-
> fallback engine with reduced utility coverage (see "Expo Go" below). The
|
|
14
|
-
> native module ships prebuilt (see Setup below) — no Rust toolchain or
|
|
15
|
-
> manual native wiring required on any platform.
|
|
7
|
+
> Beta. Native is Android-only (no iOS bridge yet). Ships a prebuilt native
|
|
8
|
+
> module — no Rust toolchain or manual native wiring required.
|
|
16
9
|
|
|
17
|
-
##
|
|
10
|
+
## Install
|
|
18
11
|
|
|
19
12
|
```sh
|
|
20
13
|
npm install @kbach/react-native
|
|
21
14
|
```
|
|
22
15
|
|
|
23
|
-
One package, nothing else to install — the Kotlin TurboModule and its
|
|
24
|
-
prebuilt native `.so` ship inside this same package's own `android/`
|
|
25
|
-
folder. Android's autolinking discovers it automatically the moment it's
|
|
26
|
-
in `node_modules`; no manual `MainApplication.kt` edit needed. Works the
|
|
27
|
-
same way whether your app was created with the React Native CLI or with
|
|
28
|
-
Expo, **including Expo Go** — see its own section below for the scope
|
|
29
|
-
tradeoff that involves.
|
|
30
|
-
|
|
31
16
|
### React Native CLI
|
|
32
17
|
|
|
33
|
-
Add the Babel plugin so your app's JSX routes through Kbach's JSX runtime
|
|
34
|
-
instead of `react/jsx-runtime`:
|
|
35
|
-
|
|
36
18
|
```js
|
|
37
19
|
// babel.config.js
|
|
38
20
|
module.exports = {
|
|
@@ -41,254 +23,104 @@ module.exports = {
|
|
|
41
23
|
};
|
|
42
24
|
```
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
you're re-running one that's already built) so Gradle picks up the new
|
|
46
|
-
native module. See `apps/native-sandbox` in the
|
|
47
|
-
[repo](https://github.com/semsakadanupol/kbach) for a full working
|
|
48
|
-
reference project, including the Metro config a monorepo setup like this
|
|
49
|
-
one needs (a plain single-app install doesn't need any of that — it's
|
|
50
|
-
only relevant if you're linking packages locally instead of installing
|
|
51
|
-
from npm).
|
|
26
|
+
Rebuild the Android app afterward so Gradle picks up the native module.
|
|
52
27
|
|
|
53
28
|
### Expo Go
|
|
54
29
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
same class-resolution logic, ported by hand from
|
|
66
|
-
[`resolve_style.rs`](https://github.com/semsakadanupol/kbach/blob/main/packages/core-engine/src/resolve_style.rs)
|
|
67
|
-
and its resolvers (see
|
|
68
|
-
[`src/jsEngine`](https://github.com/semsakadanupol/kbach/blob/main/packages/react-native/src/jsEngine)
|
|
69
|
-
in the repo) — no WASM (Hermes has no `WebAssembly` API at all) and no
|
|
70
|
-
native code, so it can run anywhere plain JS runs, Expo Go included. A
|
|
71
|
-
real dev-client or CLI build always has the TurboModule registered, so
|
|
72
|
-
this fallback activates ONLY inside Expo Go itself; every other
|
|
73
|
-
environment is completely unaffected.
|
|
74
|
-
|
|
75
|
-
The tradeoff: the JS engine covers layout, spacing, border/radius,
|
|
76
|
-
plain-hex colors (`bg-*`/`text-*`, no opacity composition), font-weight,
|
|
77
|
-
text-transform (`uppercase`/`lowercase`/`capitalize`), text-decoration-line
|
|
78
|
-
(`underline`/`line-through`/`no-underline`), `italic`/`not-italic`, numeric
|
|
79
|
-
line-height and arbitrary letter-spacing, and `shadow-*` — the same
|
|
80
|
-
native-representable subset
|
|
81
|
-
`resolve_utility_native` (the Rust engine's own native dispatcher) covers,
|
|
82
|
-
minus a WASM/JNI dependency. It does **not** cover anything that subset
|
|
83
|
-
already excludes on native (grid, transforms, filters, gradients, DOM-only
|
|
84
|
-
typography like `truncate`/`decoration-*`/`indent-*`, interactivity/scroll
|
|
85
|
-
utilities) — those were never available outside a dev client/CLI build,
|
|
86
|
-
Expo Go doesn't change that. `dark:`/`active:`/`sm:`-`2xl:` modifiers work
|
|
87
|
-
identically to the native/WASM engines (same gating logic, just inlined
|
|
88
|
-
rather than routed through a shared registry). If you hit a class that
|
|
89
|
-
silently doesn't apply only in Expo Go, check whether it resolves under a
|
|
90
|
-
dev client first — if it does, it's outside this fallback's ported scope,
|
|
91
|
-
not a bug in the class itself.
|
|
92
|
-
|
|
93
|
-
### Expo (dev client / prebuild — Android)
|
|
94
|
-
|
|
95
|
-
A dev client gets you the *full* native engine (the same one the CLI path
|
|
96
|
-
above uses) instead of the JS fallback above — worth it once you need
|
|
97
|
-
something outside that fallback's scope. Built once via `expo prebuild`/
|
|
98
|
-
`expo run:android`, functionally the same "real native project" as the CLI
|
|
99
|
-
path above:
|
|
30
|
+
Works out of the box — no prebuild, no dev client, nothing to configure.
|
|
31
|
+
Expo Go can't load third-party native code, so this falls back to a pure-JS
|
|
32
|
+
engine automatically. Covers layout, spacing, border/radius, plain colors,
|
|
33
|
+
font-weight, text-transform/decoration, line-height, letter-spacing, and
|
|
34
|
+
shadows. Doesn't cover grid, transforms, filters, gradients, or DOM-only
|
|
35
|
+
typography — same scope native itself excludes.
|
|
36
|
+
|
|
37
|
+
### Expo (dev client / prebuild)
|
|
38
|
+
|
|
39
|
+
Gets you the full native engine instead of the JS fallback:
|
|
100
40
|
|
|
101
41
|
```js
|
|
102
42
|
// babel.config.js
|
|
103
43
|
module.exports = {
|
|
104
|
-
presets: ['babel-preset-expo'],
|
|
44
|
+
presets: ['babel-preset-expo'],
|
|
105
45
|
plugins: ['@kbach/react-native/babel-plugin'],
|
|
106
46
|
};
|
|
107
47
|
```
|
|
108
48
|
|
|
109
49
|
```sh
|
|
110
|
-
npx expo prebuild
|
|
111
|
-
npx expo run:android
|
|
50
|
+
npx expo prebuild
|
|
51
|
+
npx expo run:android
|
|
112
52
|
```
|
|
113
53
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Architecture off, turn it on in `app.json`
|
|
118
|
-
(`"newArchEnabled": true` under `expo`) before prebuilding.
|
|
119
|
-
|
|
120
|
-
EAS Build works the same way — it runs the same `prebuild` step, so no
|
|
121
|
-
extra configuration beyond the two steps above.
|
|
122
|
-
|
|
123
|
-
See `apps/expo-sandbox` in the [repo](https://github.com/semsakadanupol/kbach)
|
|
124
|
-
for a full working reference project covering both this section and Expo
|
|
125
|
-
Web below — including the `metro.config.js` a monorepo setup like this one
|
|
126
|
-
needs (a plain single-app install doesn't need it).
|
|
54
|
+
Requires the New Architecture enabled (`"newArchEnabled": true` in
|
|
55
|
+
`app.json`, on by default in current Expo SDKs). EAS Build works the same
|
|
56
|
+
way, no extra config.
|
|
127
57
|
|
|
128
58
|
### Expo Web
|
|
129
59
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
the
|
|
138
|
-
|
|
139
|
-
SSR bundles — a genuinely separate resolution axis from `platform: 'web'`,
|
|
140
|
-
see `nativeBridge.web.ts`'s own doc comment) export conditions; your app
|
|
141
|
-
code and JSX never change. The WASM module initializes itself eagerly
|
|
142
|
-
(synchronously, at import time — no `await` needed before your first
|
|
143
|
-
render, unlike a from-scratch WASM setup).
|
|
144
|
-
|
|
145
|
-
**Full modifier support, including `hover:`/`focus:`/`group-hover:`/
|
|
146
|
-
`has-[...]`/container queries/etc.** — every modifier
|
|
147
|
-
[`registry.rs`](https://github.com/semsakadanupol/kbach/blob/main/packages/core-engine/src/registry.rs)
|
|
148
|
-
knows, not just the `dark:`/`active:`/responsive subset native/Expo Go
|
|
149
|
-
gate via a live JS parameter. This is possible specifically *because* Expo
|
|
150
|
-
Web is real DOM: real CSS selectors (`:hover`, `:focus`, `@media`,
|
|
151
|
-
`@container`, ...) work exactly the way they do for `@kbach/react`, no
|
|
152
|
-
polyfilling needed. The one wrinkle: `react-native-web`'s `View`/`Text`/
|
|
153
|
-
`Pressable` don't forward a `className` prop to the DOM at all (checked
|
|
154
|
-
against its own `forwardedProps` whitelist), so this can't render a
|
|
155
|
-
literal `className` the way `@kbach/react` does on plain DOM. What it uses
|
|
156
|
-
instead: RN's `dataSet` prop (which RNW DOES forward, as real `data-*`
|
|
157
|
-
attributes) carries the resolved class text into a `data-kb="..."`
|
|
158
|
-
attribute, and the generated CSS targets it with `[data-kb~="..."]`
|
|
159
|
-
attribute selectors instead of `.class` selectors — functionally
|
|
160
|
-
identical to a real `className`, just reached through RN's own prop
|
|
161
|
-
system instead. This also means `active:` is now REAL CSS `:active` (the
|
|
162
|
-
browser's own press-state detection), not a `Pressable`-specific style-
|
|
163
|
-
function simulation the way native's `active:` still is — one fewer
|
|
164
|
-
special case, and closer to actual Tailwind's own semantics.
|
|
165
|
-
|
|
166
|
-
Everything else about this matches `@kbach/react`'s own web engine
|
|
167
|
-
exactly — same
|
|
168
|
-
[`resolve_utility`](https://github.com/semsakadanupol/kbach/blob/main/packages/core-engine/src/resolvers/mod.rs)
|
|
169
|
-
dispatch (not the reduced `resolve_utility_native` subset), so grid,
|
|
170
|
-
transforms, filters, gradients, and DOM-only typography (`truncate`,
|
|
171
|
-
`decoration-*`, etc.) all resolve on Expo Web even though they don't on
|
|
172
|
-
native or Expo Go — see this README's "What this is" section for what's
|
|
173
|
-
covered elsewhere.
|
|
174
|
-
|
|
175
|
-
#### Expo Router static rendering: `dark:`/responsive on the FIRST paint
|
|
176
|
-
|
|
177
|
-
If you use [static rendering](https://docs.expo.dev/router/reference/static-rendering/)
|
|
178
|
-
(`web.output: "static"` — Expo Router's SSR/pre-render feature), any
|
|
179
|
-
`dark:`/`sm:`/etc. class on content that's part of the **initial,
|
|
180
|
-
pre-rendered HTML** resolves against whatever the **server** (Node.js, no
|
|
181
|
-
real display/viewport) reports — not the visiting browser's actual OS
|
|
182
|
-
preference or window size. React's hydration then adopts that
|
|
183
|
-
server-rendered markup as-is rather than silently overwriting it with the
|
|
184
|
-
client's own (correct) values, because a `style`-attribute-only difference
|
|
185
|
-
doesn't fail hydration the way a text-content mismatch does. Concretely: a
|
|
186
|
-
user with their OS in dark mode can see a `dark:` class resolve to its
|
|
187
|
-
*light* value on first paint, with no error or warning anywhere.
|
|
188
|
-
|
|
189
|
-
**This isn't a Kbach bug** — it's the same fundamental constraint every
|
|
190
|
-
OS-preference-based styling system has under SSR (Next.js's own dark-mode
|
|
191
|
-
docs describe the identical problem and fix). The standard fix applies
|
|
192
|
-
here unchanged: don't resolve preference-dependent classes on
|
|
193
|
-
initially-server-rendered content at all; defer them to after your app has
|
|
194
|
-
mounted client-side, e.g.:
|
|
60
|
+
Works out of the box — resolves through the same real-CSS engine
|
|
61
|
+
`@kbach/react` uses on the DOM, with full modifier support (`hover:`,
|
|
62
|
+
`focus:`, `group-hover:`, container queries, etc.), not just the
|
|
63
|
+
`dark:`/`active:`/responsive subset native supports.
|
|
64
|
+
|
|
65
|
+
**Static rendering (SSR) caveat:** `dark:`/responsive classes on content
|
|
66
|
+
that's part of the initial pre-rendered HTML resolve against the *server*,
|
|
67
|
+
not the visiting browser — so a user in dark mode can briefly see the light
|
|
68
|
+
version on first paint. Defer preference-dependent classes until mounted:
|
|
195
69
|
|
|
196
70
|
```tsx
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
function ThemedScreen() {
|
|
201
|
-
const [mounted, setMounted] = useState(false);
|
|
202
|
-
useEffect(() => setMounted(true), []);
|
|
203
|
-
|
|
204
|
-
// Neutral on the server AND on the client's first paint (matches exactly,
|
|
205
|
-
// no hydration mismatch) — only becomes preference-aware once mounted.
|
|
206
|
-
return (
|
|
207
|
-
<View className={mounted ? 'bg-gray-1 dark:bg-gray-12' : 'bg-gray-1'}>
|
|
208
|
-
<Text>Hello</Text>
|
|
209
|
-
</View>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
```
|
|
71
|
+
const [mounted, setMounted] = useState(false);
|
|
72
|
+
useEffect(() => setMounted(true), []);
|
|
213
73
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
before your first `useEffect` runs.
|
|
217
|
-
|
|
218
|
-
## What this is
|
|
219
|
-
|
|
220
|
-
- A JSX runtime (`@kbach/react-native/jsx-runtime`) that intercepts
|
|
221
|
-
`className`/`kb` props on every element and calls into the native, WASM,
|
|
222
|
-
or pure-JS bridge (whichever platform you're on — see the Setup section
|
|
223
|
-
above), synchronously, on every render. On native and Expo Go this
|
|
224
|
-
resolves to an inline `style` object; on Expo Web it resolves to real
|
|
225
|
-
injected CSS plus a `dataSet={{ kb: '...' }}` prop instead — see "Expo
|
|
226
|
-
Web" below for why the two differ.
|
|
227
|
-
- A Babel plugin (`@kbach/react-native/babel-plugin`) that routes your
|
|
228
|
-
app's own files (never `node_modules`) through that JSX runtime via a
|
|
229
|
-
per-file `@jsxImportSource` pragma.
|
|
230
|
-
- `setTheme()`/`getTheme()` for customizing the color palette and spacing
|
|
231
|
-
scale — the same `ThemeConfig` shape `@kbach/react` uses, kept as an
|
|
232
|
-
independent type here so a native-only project isn't forced to depend on
|
|
233
|
-
a web-oriented package.
|
|
234
|
-
- `shadow-*` resolves to real native shadows — RN's discrete
|
|
235
|
-
`shadowColor`/`shadowOffset`/`shadowOpacity`/`shadowRadius` props plus
|
|
236
|
-
Android's `elevation` (both emitted together; `elevation` is what
|
|
237
|
-
actually renders a shadow on Android — the `shadow*` props are iOS-only
|
|
238
|
-
in RN itself, ready for whenever this package gets an iOS bridge),
|
|
239
|
-
derived from the same named tiers (`sm`/`DEFAULT`/`md`/`lg`/`xl`/`2xl`)
|
|
240
|
-
`@kbach/react`'s CSS `box-shadow` scale uses. `shadow-inner` and
|
|
241
|
-
arbitrary `shadow-[...]` values aren't supported — RN's native shadow
|
|
242
|
-
props can't express an inset shadow, and an arbitrary value is a raw CSS
|
|
243
|
-
string with no mechanical translation into discrete native props.
|
|
244
|
-
- Responsive breakpoints (`sm:`/`md:`/`lg:`/`xl:`/`2xl:`) — same
|
|
245
|
-
mobile-first "min-width and up" semantics as web, freely combinable with
|
|
246
|
-
`dark:`/`active:` (e.g. `dark:md:bg-blue-8`). On native/Expo Go, gated
|
|
247
|
-
live against the current window width (`Dimensions.get('window').width`)
|
|
248
|
-
— call RN's own `useWindowDimensions()` somewhere in your tree to
|
|
249
|
-
re-render on rotation/resize, same role `useColorScheme()` plays for
|
|
250
|
-
`dark:` below. On Expo Web this is real `@media (min-width: ...)` CSS
|
|
251
|
-
instead (see "Expo Web" above) — the browser evaluates it continuously
|
|
252
|
-
on its own, so no `useWindowDimensions()` call is needed there at all.
|
|
74
|
+
<View className={mounted ? 'bg-gray-1 dark:bg-gray-12' : 'bg-gray-1'} />;
|
|
75
|
+
```
|
|
253
76
|
|
|
254
77
|
## Dark mode
|
|
255
78
|
|
|
256
|
-
Works standalone, with zero provider — same architecture as `@kbach/react`:
|
|
257
|
-
|
|
258
79
|
```tsx
|
|
259
|
-
import {
|
|
80
|
+
import { useTheme } from '@kbach/react-native';
|
|
260
81
|
|
|
261
82
|
function ThemeToggle() {
|
|
262
|
-
const isDark =
|
|
263
|
-
return <Pressable onPress={
|
|
83
|
+
const { mode, isDark, setMode, toggle } = useTheme();
|
|
84
|
+
return <Pressable onPress={toggle}><Text>{mode}</Text></Pressable>;
|
|
264
85
|
}
|
|
265
86
|
```
|
|
266
87
|
|
|
267
|
-
|
|
268
|
-
|
|
88
|
+
Works anywhere, no provider needed. `<ThemeProvider defaultMode="dark">` is
|
|
89
|
+
optional, for seeding a startup default. Outside React, use
|
|
90
|
+
`getGlobalDarkMode()`/`toggleGlobalDarkMode()` instead.
|
|
91
|
+
|
|
92
|
+
Follows the OS setting live until overridden, and isn't persisted across
|
|
93
|
+
app launches (no built-in storage dependency). To persist, read your own
|
|
94
|
+
storage at startup and call `setGlobalThemeMode()` before your first render.
|
|
95
|
+
|
|
96
|
+
## Reading colors as values
|
|
97
|
+
|
|
98
|
+
For anywhere you need a real color, not a `className` — a chart prop, a
|
|
99
|
+
native shadow color:
|
|
269
100
|
|
|
270
101
|
```tsx
|
|
271
|
-
import {
|
|
102
|
+
import { useColors } from '@kbach/react-native';
|
|
272
103
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
104
|
+
const colors = useColors();
|
|
105
|
+
colors.blue[6]; // '#2563eb' — static, same value in both modes
|
|
106
|
+
colors.blue['6/50']; // same, at 50% opacity
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Any color can be defined in the theme as a plain string (static) or a
|
|
110
|
+
`{ light, dark }` pair — fully manual, resolved per the current dark-mode
|
|
111
|
+
state:
|
|
277
112
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
113
|
+
```ts
|
|
114
|
+
setTheme({
|
|
115
|
+
...defaultTheme,
|
|
116
|
+
colors: { ...defaultTheme.colors, brand: { light: '#2563eb', dark: '#93c5fd' } },
|
|
117
|
+
});
|
|
281
118
|
```
|
|
282
119
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
`@react-native-async-storage/async-storage`) on every consumer, even ones
|
|
288
|
-
that don't want it. If you want persistence, read your own storage at
|
|
289
|
-
startup and call `setGlobalThemeMode()` with the result before your first
|
|
290
|
-
render — the same "seed once" shape `<ThemeProvider defaultMode>` itself
|
|
291
|
-
uses internally.
|
|
120
|
+
A shade-family lookup (`colors.blue[n]`) auto-mirrors to shade `13 - n` in
|
|
121
|
+
dark mode — this palette's own convention is 1 = lightest, 12 = darkest, so
|
|
122
|
+
that's the same visual weight relative to its own background, no manual
|
|
123
|
+
`colors.blue[isDark ? 7 : 6]` needed.
|
|
292
124
|
|
|
293
125
|
## License
|
|
294
126
|
|
|
Binary file
|
|
Binary file
|