@kbach/react-native 1.0.0-beta.46 → 1.0.0-beta.48
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/AGENTS.md +325 -0
- package/README.md +73 -13
- 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-3KLOVONL.mjs +564 -0
- package/dist/{chunk-UFSVFF3V.mjs → chunk-SC3L5I7H.mjs} +1 -1
- package/dist/{chunk-KT6TQLWI.mjs → chunk-XOH25F3E.mjs} +1 -1
- package/dist/dynamicTokens.d.ts +18 -1
- package/dist/index.js +23 -2
- package/dist/index.web.js +1 -1
- package/dist/index.web.mjs +2 -2
- package/dist/jsx-dev-runtime.js +23 -2
- package/dist/jsx-dev-runtime.web.js +1 -1
- package/dist/jsx-dev-runtime.web.mjs +3 -3
- package/dist/jsx-runtime.js +23 -2
- package/dist/jsx-runtime.web.js +1 -1
- package/dist/jsx-runtime.web.mjs +2 -2
- package/package.json +3 -2
- package/dist/chunk-DKZM4NUU.mjs +0 -564
package/AGENTS.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# `@kbach/react-native` — reference for AI assistants
|
|
2
|
+
|
|
3
|
+
The React Native binding of [Kbach](https://github.com/semsakadanupol/kbach)
|
|
4
|
+
— the same Tailwind-v4-shaped class-name vocabulary as `@kbach/react`,
|
|
5
|
+
resolved to a **`StyleSheet`-ready style object at render time** via a
|
|
6
|
+
shared Rust core (JNI on Android, WASM on Expo Web, a pure-JS fallback on
|
|
7
|
+
Expo Go and iOS). This file covers `@kbach/react-native` only; for the
|
|
8
|
+
cross-package overview, `kbach.config.js` reference, the color palette,
|
|
9
|
+
and how the underlying engine is built, see the repo root's
|
|
10
|
+
[AGENTS.md](https://github.com/semsakadanupol/kbach/blob/main/AGENTS.md).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Install + setup
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install @kbach/react-native@beta
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The babel plugin is **required on every target, including Expo Go** — it
|
|
21
|
+
injects the per-file `@jsxImportSource @kbach/react-native` pragma
|
|
22
|
+
(babel-plugin.js) that routes `className` through this package's
|
|
23
|
+
jsx-runtime instead of `react/jsx-runtime`; nothing else makes
|
|
24
|
+
interception happen. Confirmed as a real, reported doc bug: the table
|
|
25
|
+
below used to say "None" for Expo Go/Expo Web, and a from-scratch repro
|
|
26
|
+
(fresh Expo project, README's own example, zero babel config, Expo Go on
|
|
27
|
+
a real emulator) showed `className` doing nothing at all — no styling, no
|
|
28
|
+
warning, no error — exactly as this mechanism predicts. What differs per
|
|
29
|
+
target is what happens *after* the plugin is in place:
|
|
30
|
+
|
|
31
|
+
| Target | Setup |
|
|
32
|
+
| :-- | :-- |
|
|
33
|
+
| **Expo Go** | Babel plugin. A pure-JS fallback engine loads automatically after that (reduced utility coverage) — no other setup. |
|
|
34
|
+
| **Expo Web** / `react-native-web` | Babel plugin. Uses the same WASM CSS engine as `@kbach/react` after that — no other setup. |
|
|
35
|
+
| **Expo dev client / prebuild / EAS** | Babel plugin, then `npx expo prebuild && npx expo run:android`. Full native engine. |
|
|
36
|
+
| **React Native CLI** | Babel plugin, then restart Metro with `--reset-cache` and rebuild the Android app. |
|
|
37
|
+
|
|
38
|
+
Babel plugin — added to the existing `babel.config.js` (keep the function
|
|
39
|
+
form Expo generates; only add the `plugins` entry):
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
module.exports = function (api) {
|
|
43
|
+
api.cache(true);
|
|
44
|
+
return {
|
|
45
|
+
presets: ['babel-preset-expo'],
|
|
46
|
+
plugins: ['@kbach/react-native/babel-plugin'],
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
React Native CLI uses `presets: ['module:@react-native/babel-preset']` and
|
|
52
|
+
a plain object export.
|
|
53
|
+
|
|
54
|
+
**Native is Android-only** (no iOS bridge yet) — on iOS, every build
|
|
55
|
+
(dev client, CLI, EAS) runs the same reduced-coverage JS-fallback engine
|
|
56
|
+
as Expo Go, not the full native engine (see §3's engine table).
|
|
57
|
+
|
|
58
|
+
## 2. What the babel plugin does
|
|
59
|
+
|
|
60
|
+
Per user source file (never `node_modules`, never Metro/Expo virtual
|
|
61
|
+
modules, never `kbach.config.js` itself):
|
|
62
|
+
|
|
63
|
+
1. Prepends a `// @jsxImportSource @kbach/react-native` comment so the JSX
|
|
64
|
+
transform routes `className` through this package's jsx-runtime.
|
|
65
|
+
2. Prepends `require('@kbach/react-native').applyKbachConfig(require('<abs
|
|
66
|
+
path>/kbach.config.js'))` if that file exists at the project root — so
|
|
67
|
+
the config is applied before the app's first render with **no import or
|
|
68
|
+
call in app code**.
|
|
69
|
+
|
|
70
|
+
A dev-server restart is required after changing `kbach.config.js` or the
|
|
71
|
+
babel config — for the babel config specifically, a plain restart isn't
|
|
72
|
+
reliably enough (Metro's transform cache doesn't always invalidate on a
|
|
73
|
+
babel config change on its own); use `--reset-cache` (`npx react-native
|
|
74
|
+
start --reset-cache` / `npx expo start --clear`).
|
|
75
|
+
|
|
76
|
+
## 3. How resolution works
|
|
77
|
+
|
|
78
|
+
`className` on a host element (`View`, `Text`, `Pressable`, …) is
|
|
79
|
+
intercepted by the jsx-runtime and resolved to a style object. One of three
|
|
80
|
+
engines does the work, chosen automatically:
|
|
81
|
+
|
|
82
|
+
| Runtime | Engine | Mechanism |
|
|
83
|
+
| :-- | :-- | :-- |
|
|
84
|
+
| Android dev-client / CLI build | **Rust via JNI** (`KbachModule` TurboModule, prebuilt `.so`) | `resolveStyle(classString, themeJson, colorScheme, pressed, width)` → JSON style object. |
|
|
85
|
+
| Expo Web / `react-native-web` | **Rust via WASM** (`generate_css_attr`) | Emits real CSS rules into a `<style>` tag; element gets `dataSet={{ kb }}` and rules target `[data-kb~="…"]`. Full modifier set. |
|
|
86
|
+
| Expo Go | **Pure-JS fallback** (`jsEngine/`) | A partial TS port of the native resolver. No custom native module can load in Expo Go. |
|
|
87
|
+
| iOS — ANY build (dev-client, CLI, EAS) | **Pure-JS fallback** (`jsEngine/`) | Same reduced-coverage path as Expo Go, since there's no iOS native module at all yet (`TurboModuleRegistry.get('KbachModule')` returns `null` on iOS, so `resolveStyle` falls through to the JS engine — see nativeBridge.ts). Not specific to Expo Go; a real iOS dev-client/CLI build is on this row too. |
|
|
88
|
+
|
|
89
|
+
Resolution is memoized per `(classString, pressed, colorScheme, width,
|
|
90
|
+
theme)`.
|
|
91
|
+
|
|
92
|
+
## 4. Modifiers on native
|
|
93
|
+
|
|
94
|
+
Native has no selector/cascade engine. Two groups:
|
|
95
|
+
|
|
96
|
+
**Resolved in the engine from live parameters:**
|
|
97
|
+
- `dark:` — current OS/app color scheme.
|
|
98
|
+
- `sm:` `md:` `lg:` `xl:` `2xl:` — window width vs `theme.screens`.
|
|
99
|
+
- `min-[500px]:` / `max-[30rem]:` — arbitrary width breakpoints (constant
|
|
100
|
+
px/rem only).
|
|
101
|
+
- `active:` — `Pressable` press state (via its style-callback).
|
|
102
|
+
|
|
103
|
+
**Resolved in `jsxRuntimeCore.ts` from React props, before the engine sees
|
|
104
|
+
the string:**
|
|
105
|
+
- `hover:` — `onHoverIn`/`onHoverOut` — only fires for pointer-capable
|
|
106
|
+
input (mouse/trackpad); inert on pure touch.
|
|
107
|
+
- `focus:` — `onFocus`/`onBlur`.
|
|
108
|
+
- `disabled:` — the element's own `disabled` prop.
|
|
109
|
+
- `data-[key=value]:` / `data-[key]:` / `aria-[…]:` / static `aria-*`
|
|
110
|
+
shortcuts — the element's own `data-*` / `aria-*` props.
|
|
111
|
+
|
|
112
|
+
A satisfied prop-based modifier is rewritten to an internal `_kbon` marker
|
|
113
|
+
(one per satisfied modifier) so it still contributes to specificity.
|
|
114
|
+
|
|
115
|
+
**Prop-based modifiers never inherit from an ancestor.** `disabled:` /
|
|
116
|
+
`aria-[…]:` / `data-[…]:` read `hostRest` off THAT exact element's own
|
|
117
|
+
props (`propBasedModifierState` in jsxRuntimeCore.ts) — there is no
|
|
118
|
+
`group-disabled:`-style cascade on native to inherit one through. Confirmed
|
|
119
|
+
as a real footgun (reported after two independent hits building a demo
|
|
120
|
+
app): `<Pressable disabled><Text className="disabled:text-gray-5">` does
|
|
121
|
+
nothing at all, silently — `Text` never received a `disabled` prop, so
|
|
122
|
+
`propBasedModifierState` returns `null` for it (no opinion), the modifier
|
|
123
|
+
is treated as never-satisfied, and nothing warns. The prop has to be
|
|
124
|
+
repeated on whichever element actually carries the modifier:
|
|
125
|
+
`<Pressable disabled><Text disabled className="disabled:text-gray-5">`.
|
|
126
|
+
|
|
127
|
+
**Not supported** (parse without error, never apply): `group-*`, `peer-*`,
|
|
128
|
+
`has-[…]`, container queries, `*:` / `**:`, `before:` / `after:` and other
|
|
129
|
+
pseudo-elements.
|
|
130
|
+
|
|
131
|
+
**Specificity / class order.** When two classes set the same property, the
|
|
132
|
+
one with more satisfied modifiers wins **regardless of source order** —
|
|
133
|
+
`dark:bg-black bg-white` and `bg-white dark:bg-black` both resolve to black
|
|
134
|
+
in dark mode, matching web. Equal specificity falls back to last-token-wins.
|
|
135
|
+
`transform-none` is an exception: it clears all transforms above it in
|
|
136
|
+
source order regardless of specificity.
|
|
137
|
+
|
|
138
|
+
Chaining composes and is order-independent (`dark:sm:` ≡ `sm:dark:`); every
|
|
139
|
+
named condition must hold.
|
|
140
|
+
|
|
141
|
+
## 5. Expo Go coverage gap
|
|
142
|
+
|
|
143
|
+
The JS fallback covers: layout, spacing, border/radius, plain colors,
|
|
144
|
+
font-weight, text-transform/decoration, line-height, letter-spacing,
|
|
145
|
+
shadows, and the RN-representable transforms (`translate-x/y`,
|
|
146
|
+
`scale`/`scale-x/y`, `rotate`/`rotate-x/y/z`, `skew-x/y`, `transform-none`,
|
|
147
|
+
`backface-*`).
|
|
148
|
+
|
|
149
|
+
Not covered: grid, filters, gradients, DOM-only typography, and transforms
|
|
150
|
+
with no RN equivalent (`translate-z`, `scale-z`, `perspective*`, `origin`,
|
|
151
|
+
`transform-gpu/cpu`). Same scope native itself excludes. Typo warnings are
|
|
152
|
+
also unavailable in Expo Go (no full-vocabulary resolver to check against).
|
|
153
|
+
|
|
154
|
+
This is also exactly what iOS gets in every build type (§3) — the coverage
|
|
155
|
+
gap isn't Expo-Go-specific, it's "whatever the JS-fallback engine covers."
|
|
156
|
+
|
|
157
|
+
## 6. Dark mode
|
|
158
|
+
|
|
159
|
+
Zero setup on all targets. On native `dark:` is resolved from a live
|
|
160
|
+
parameter every render — no build step to keep in sync. Expo Web defaults
|
|
161
|
+
to the `'attribute'` strategy.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
import { useTheme } from '@kbach/react-native';
|
|
165
|
+
const { mode, isDark, setMode, toggle } = useTheme();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
No provider required. `<ThemeProvider>` optionally seeds a startup
|
|
169
|
+
`defaultMode` and enables persistence:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
173
|
+
<ThemeProvider persist={AsyncStorage}><App /></ThemeProvider>;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Mount at most one — dark mode is one global store, not scoped per
|
|
177
|
+
provider, so a second mounted instance logs a dev-only warning (they'd
|
|
178
|
+
fight over the same `defaultMode`/`persist` seed).
|
|
179
|
+
|
|
180
|
+
`persist` takes any `{ getItem, setItem }` async store. The persisted mode
|
|
181
|
+
is read once on mount and wins over `defaultMode`; later changes are
|
|
182
|
+
written back. This package never imports a storage library itself.
|
|
183
|
+
|
|
184
|
+
Outside React: `getGlobalDarkMode()` / `toggleGlobalDarkMode()` /
|
|
185
|
+
`setGlobalThemeMode()`.
|
|
186
|
+
|
|
187
|
+
## 7. `calc()` / `min()` / `max()` / `clamp()`
|
|
188
|
+
|
|
189
|
+
- **Constant** px/rem/bare-number expressions are reduced at resolve time:
|
|
190
|
+
`p-[calc(16px+8px)]` → `padding: 24`. No spaces or underscores are
|
|
191
|
+
required around operators; `calc(16px+8px)` is fine.
|
|
192
|
+
- **Viewport-relative** (`calc(10vw+8px)`): no such unit on native — the
|
|
193
|
+
declaration is dropped and a dev-only `console.warn` explains why.
|
|
194
|
+
- **Percentage-relative `width`/`height`** (`w-[calc(100%-3rem)]`,
|
|
195
|
+
`w-[min(50%,20rem)]`, `w-[clamp(16rem,50%,32rem)]`): supported via a
|
|
196
|
+
one-frame measure-then-snap — renders at `100%`, measures via
|
|
197
|
+
`onLayout`, then corrects to the computed number; re-measures on layout
|
|
198
|
+
changes. Narrow shapes only: `calc()` = one percent term + one constant
|
|
199
|
+
term; `min`/`max` args each a plain percent OR a constant expression (not
|
|
200
|
+
mixed); `clamp` exactly three args; `width`/`height` only.
|
|
201
|
+
- **Literal spaces are illegal inside `[...]`** on any Tailwind-family
|
|
202
|
+
engine (a space splits the class token). Use underscores if you want them
|
|
203
|
+
for readability: `w-[calc(100%_-_3rem)]`.
|
|
204
|
+
|
|
205
|
+
## 8. Dynamic tokens (native counterpart to CSS custom properties)
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { setDynamicToken, useDynamicToken } from '@kbach/react-native';
|
|
209
|
+
|
|
210
|
+
setDynamicToken('sidebar-width', '240px');
|
|
211
|
+
<View className="w-[var(--sidebar-width)]" />;
|
|
212
|
+
const w = useDynamicToken('sidebar-width'); // '240px' | undefined, reactive
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
On web `setDynamicToken` writes a real CSS custom property (browser
|
|
216
|
+
repaints, no re-render). On native the token value is substituted into the
|
|
217
|
+
class string before each resolve, and subscribed elements re-render on
|
|
218
|
+
change. `var(--x)` in a `className` already works unaided on Expo Web.
|
|
219
|
+
|
|
220
|
+
**Call `setDynamicToken` synchronously from a render body, not from module
|
|
221
|
+
top-level.** It calls `notify()` synchronously (dynamicTokens.ts), which
|
|
222
|
+
synchronously re-renders every already-mounted `ReactiveElement` subscribed
|
|
223
|
+
via `useSyncExternalStore` — safe when nothing is subscribed yet, a real
|
|
224
|
+
hazard once something is. Module-top-level code runs whenever Metro's
|
|
225
|
+
inline-requires first evaluates that module, which is NOT a fixed point in
|
|
226
|
+
the app's lifecycle — it can land in the middle of React rendering some
|
|
227
|
+
unrelated component elsewhere in the tree, which is exactly the "Cannot
|
|
228
|
+
update a component while rendering a different component" class of bug:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
// Bad — races Metro inline-requires; can fire mid-render of an unrelated component.
|
|
232
|
+
setDynamicToken('accent', '#8b5cf6');
|
|
233
|
+
|
|
234
|
+
// Good — call synchronously in your ROOT component's own render, before any
|
|
235
|
+
// consumer mounts; guard so it only ever fires once (unguarded, every
|
|
236
|
+
// re-render would call notify() again, wastefully re-rendering every
|
|
237
|
+
// subscribed element for no value change).
|
|
238
|
+
function RootLayout({ children }) {
|
|
239
|
+
if (getDynamicToken('accent') === undefined) {
|
|
240
|
+
setDynamicToken('accent', '#8b5cf6');
|
|
241
|
+
}
|
|
242
|
+
return <ThemeProvider>{children}</ThemeProvider>;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 9. Reading colors as values
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import { useColors } from '@kbach/react-native';
|
|
250
|
+
|
|
251
|
+
const colors = useColors();
|
|
252
|
+
colors.blue[6]; // '#2563eb'
|
|
253
|
+
colors.blue['6/50']; // 50% opacity
|
|
254
|
+
colors.alpha(colors.blue[6], 30);
|
|
255
|
+
colors.get('brand'); // a custom color, typed as plain `string`
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`colors.blue[n]` auto-mirrors to shade `13 - n` in dark mode (palette
|
|
259
|
+
convention: 1 = lightest, 12 = darkest). `colors.brand` (a custom flat
|
|
260
|
+
color) is typed `string | ColorScale` because the same object also serves
|
|
261
|
+
shade families and TS can't tell them apart statically — use
|
|
262
|
+
`colors.get(name)` when you need a guaranteed `string`.
|
|
263
|
+
|
|
264
|
+
## 10. Unknown-class warnings
|
|
265
|
+
|
|
266
|
+
On a native dev-client/CLI build, a class matching no Kbach utility on any
|
|
267
|
+
platform emits a one-time `console.warn` (dev only). A real utility native
|
|
268
|
+
just doesn't support yet (`grid-cols-3`, `blur`, …) stays silent — that's a
|
|
269
|
+
documented platform gap, not a typo. Not available in Expo Go.
|
|
270
|
+
|
|
271
|
+
A malformed or misused value fails the same safe way — a deduped, dev-only
|
|
272
|
+
`console.warn` naming the offending class and the fix, never a crash or
|
|
273
|
+
silently-wrong output. Confirmed across CSS-injection attempts in an
|
|
274
|
+
arbitrary value, invalid `calc()` operands, and double-mounted providers.
|
|
275
|
+
|
|
276
|
+
## 11. Monorepo / workspace installs
|
|
277
|
+
|
|
278
|
+
If `@kbach/react-native` is a workspace/hoisted dependency, Metro can end
|
|
279
|
+
up with **two physical copies of `react-native` / `react`** — different
|
|
280
|
+
files resolve to different module instances, and a dark-mode toggle can
|
|
281
|
+
silently do nothing. Force a single instance in your app's
|
|
282
|
+
`metro.config.js` via `resolver.resolveRequest`, pointing `react-native`
|
|
283
|
+
and `react` at `projectRoot/node_modules/...`. A plain non-monorepo
|
|
284
|
+
`npm install` never hits this.
|
|
285
|
+
|
|
286
|
+
## 12. Exports
|
|
287
|
+
|
|
288
|
+
`setTheme`, `getTheme`, `defaultTheme`, `resolveStyle`, `resolveKbachConfig`,
|
|
289
|
+
`applyKbachConfig`, `useTheme`, `ThemeProvider`, `useColors`,
|
|
290
|
+
`setDynamicToken`, `getDynamicToken`, `deleteDynamicToken`,
|
|
291
|
+
`subscribeDynamicTokens`, `useDynamicToken`, `clsx`, `getGlobalDarkMode`,
|
|
292
|
+
`getGlobalThemeMode`, `setGlobalThemeMode`, `toggleGlobalDarkMode`,
|
|
293
|
+
`subscribeGlobalDarkMode`. Types: `ThemeConfig`, `ColorEntry`,
|
|
294
|
+
`ContainerConfig`, `StyleObject`, `KbachConfig`, `ThemeState`,
|
|
295
|
+
`ThemeProviderProps`, `ThemeStorageAdapter`, `ThemeMode`, `ColorsAPI`,
|
|
296
|
+
`ColorScale`, `ClassValue`.
|
|
297
|
+
|
|
298
|
+
Subpath exports: `@kbach/react-native/babel-plugin`,
|
|
299
|
+
`@kbach/react-native/jsx-runtime`, `@kbach/react-native/jsx-dev-runtime`.
|
|
300
|
+
|
|
301
|
+
## 13. Common failure modes
|
|
302
|
+
|
|
303
|
+
- **`bg-<custom>` renders nothing** — the color is mode-aware
|
|
304
|
+
(`{light,dark}` / `dark:` block) and the app is on a stale
|
|
305
|
+
`@kbach/react-native` (`< 1.0.0-beta.36`). Update.
|
|
306
|
+
- **A `dark:` variant loses to a plain class** — stale
|
|
307
|
+
`@kbach/react-native` (`< 1.0.0-beta.37`); class-order independence
|
|
308
|
+
landed there. Update.
|
|
309
|
+
- **`useColors().brand` type error** — assign via `colors.get('brand')`
|
|
310
|
+
(`< beta.36` has no `.get`).
|
|
311
|
+
- **Dark toggle does nothing in a monorepo** — duplicate `react-native` /
|
|
312
|
+
`react` instances; add the `metro.config.js` `resolveRequest` override
|
|
313
|
+
(§11).
|
|
314
|
+
- **Rust change not reflected on a real Android build** — stale prebuilt
|
|
315
|
+
`.so`; the maintainer must re-run `build:android` and republish.
|
|
316
|
+
- **`kbach.config.js` "does nothing" / Metro bundling error** — an
|
|
317
|
+
unquoted hyphenated key (`surface-dim:` instead of `'surface-dim':`).
|
|
318
|
+
Quote it.
|
|
319
|
+
- **TypeScript error on `className`** (`Property 'className' does not
|
|
320
|
+
exist on type ... ViewProps ...`) — fixed in `>= 1.0.0-beta.46`; update.
|
|
321
|
+
- **`className` does absolutely nothing — no styling, no warning, no
|
|
322
|
+
error, on any target including Expo Go** — the babel plugin (§1) is
|
|
323
|
+
missing. It's required on every target, not just Android/dev-client
|
|
324
|
+
builds; without it nothing routes `className` through this package's
|
|
325
|
+
jsx-runtime at all, so RN just silently ignores the unrecognized prop.
|
package/README.md
CHANGED
|
@@ -4,8 +4,10 @@ Utility-class styling for React Native — the same class names as
|
|
|
4
4
|
[`@kbach/react`](https://www.npmjs.com/package/@kbach/react), resolved to a
|
|
5
5
|
`StyleSheet`-ready style object instead of CSS.
|
|
6
6
|
|
|
7
|
-
> **Beta.** Native is Android-only (no iOS bridge yet)
|
|
8
|
-
>
|
|
7
|
+
> **Beta.** Native is Android-only (no iOS bridge yet) — on iOS, every
|
|
8
|
+
> build (dev client, CLI, EAS) runs the same reduced-coverage JS-fallback
|
|
9
|
+
> engine as Expo Go, not the full native engine. Ships a prebuilt native
|
|
10
|
+
> module — no Rust toolchain or manual linking.
|
|
9
11
|
|
|
10
12
|
## Install
|
|
11
13
|
|
|
@@ -15,12 +17,18 @@ npm install @kbach/react-native@beta
|
|
|
15
17
|
|
|
16
18
|
## Setup
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
The babel plugin (below) is **required on every target, including Expo
|
|
21
|
+
Go** — it's the only thing that makes `className` get intercepted at all;
|
|
22
|
+
without it, `className` is just an unrecognized prop RN silently ignores
|
|
23
|
+
(no styling, no warning). What differs per target is what happens *after*
|
|
24
|
+
that:
|
|
25
|
+
|
|
26
|
+
| Target | Setup |
|
|
19
27
|
| :-- | :-- |
|
|
20
|
-
| **Expo Go** |
|
|
21
|
-
| **Expo Web** / `react-native-web` |
|
|
28
|
+
| **Expo Go** | Add the babel plugin (below). A pure-JS fallback engine loads automatically after that — no other setup. |
|
|
29
|
+
| **Expo Web** / `react-native-web` | Add the babel plugin (below). Resolves through the same CSS engine `@kbach/react` uses after that — no other setup. |
|
|
22
30
|
| **Expo dev client / prebuild / EAS** | Add the babel plugin (below), then `npx expo prebuild && npx expo run:android`. |
|
|
23
|
-
| **React Native CLI** | Add the babel plugin (below), then rebuild the app. |
|
|
31
|
+
| **React Native CLI** | Add the babel plugin (below), then restart Metro with `--reset-cache` and rebuild the app. |
|
|
24
32
|
|
|
25
33
|
The babel plugin, added to your existing `babel.config.js`:
|
|
26
34
|
|
|
@@ -43,6 +51,11 @@ module.exports = {
|
|
|
43
51
|
};
|
|
44
52
|
```
|
|
45
53
|
|
|
54
|
+
A plain JS reload isn't enough after adding the plugin or editing
|
|
55
|
+
`kbach.config.js` — Metro's transform cache doesn't reliably pick up a
|
|
56
|
+
babel config change on its own. Restart with `--reset-cache` (`npx
|
|
57
|
+
react-native start --reset-cache` / `npx expo start --clear`).
|
|
58
|
+
|
|
46
59
|
Then write classes:
|
|
47
60
|
|
|
48
61
|
```tsx
|
|
@@ -90,6 +103,27 @@ const { mode, isDark, setMode, toggle } = useTheme();
|
|
|
90
103
|
|
|
91
104
|
Works anywhere, no provider. `<ThemeProvider>` is optional — for seeding a
|
|
92
105
|
startup default and opt-in persistence (`<ThemeProvider persist={AsyncStorage}>`).
|
|
106
|
+
Mount at most one — a second instance logs a dev warning, since dark mode
|
|
107
|
+
is one global store, not scoped per provider.
|
|
108
|
+
|
|
109
|
+
## Dynamic values
|
|
110
|
+
|
|
111
|
+
For a value that changes at runtime without touching component state —
|
|
112
|
+
a live accent color, a user-picked theme:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { setDynamicToken, useDynamicToken } from '@kbach/react-native';
|
|
116
|
+
|
|
117
|
+
setDynamicToken('accent', '#8b5cf6');
|
|
118
|
+
|
|
119
|
+
<View className="bg-[var(--accent)]" />; // updates instantly, everywhere it's used
|
|
120
|
+
const accent = useDynamicToken('accent'); // '#8b5cf6' | undefined, reactive
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Call `setDynamicToken` from a component's own render (guarded to fire
|
|
124
|
+
once), not from module top-level — see
|
|
125
|
+
[AGENTS.md §8](https://github.com/semsakadanupol/kbach/blob/main/packages/react-native/AGENTS.md)
|
|
126
|
+
for why and the exact pattern.
|
|
93
127
|
|
|
94
128
|
## Colors as values
|
|
95
129
|
|
|
@@ -113,13 +147,39 @@ container queries parse without error but do not apply. When two classes
|
|
|
113
147
|
set the same property the more-specific one wins regardless of source
|
|
114
148
|
order (`dark:bg-black bg-white` → black in dark mode).
|
|
115
149
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
150
|
+
`hover:` only fires for pointer-capable input (mouse/trackpad) — it's
|
|
151
|
+
inert on pure touch, so don't expect it to do anything on a phone/tablet
|
|
152
|
+
with no attached pointer.
|
|
153
|
+
|
|
154
|
+
`disabled:`/`aria-[…]:`/`data-[…]:` read that exact element's own props —
|
|
155
|
+
never a parent's, since there's no `group-disabled:`-style cascade to
|
|
156
|
+
inherit through. `<Pressable disabled><Text className="disabled:text-gray-5">`
|
|
157
|
+
does nothing to the `Text`, silently, because `Text` never received a
|
|
158
|
+
`disabled` prop of its own; the prop has to be repeated on whichever
|
|
159
|
+
element the modifier is written on:
|
|
160
|
+
`<Pressable disabled><Text disabled className="disabled:text-gray-5">`.
|
|
161
|
+
|
|
162
|
+
Expo Go, and iOS in ANY build type (no native engine there yet — see the
|
|
163
|
+
callout at the top), cover the same documented subset of utilities (no
|
|
164
|
+
grid, filters, gradients, or DOM-only typography). Expo Web and a real
|
|
165
|
+
**Android** dev-client/CLI build cover the full set.
|
|
166
|
+
|
|
167
|
+
A malformed or misused value fails safely — a deduped, dev-only
|
|
168
|
+
`console.warn` naming the offending class and the fix, never a crash or
|
|
169
|
+
silently-wrong output. Confirmed across CSS-injection attempts, invalid
|
|
170
|
+
`calc()` operands, and double-mounting.
|
|
171
|
+
|
|
172
|
+
`clsx()` — this package's own bundled copy, `import { clsx } from
|
|
173
|
+
'@kbach/react-native'` — composes conditional class strings the same way
|
|
174
|
+
the standalone `clsx` package does. You don't need it for `dark:`/`sm:`/
|
|
175
|
+
etc. (those work as plain classes), but it's there for `isActive &&
|
|
176
|
+
'bg-blue-6'`-style composition.
|
|
177
|
+
|
|
178
|
+
See [this package's own AGENTS.md](https://github.com/semsakadanupol/kbach/blob/main/packages/react-native/AGENTS.md)
|
|
179
|
+
for the complete reference — modifier list, `calc()`/`min()`/`max()`/
|
|
180
|
+
`clamp()` support, dynamic tokens, monorepo setup, and failure modes; see
|
|
181
|
+
the [root AGENTS.md](https://github.com/semsakadanupol/kbach/blob/main/AGENTS.md)
|
|
182
|
+
for `kbach.config.js` and how the underlying engine is built.
|
|
123
183
|
|
|
124
184
|
## License
|
|
125
185
|
|
|
Binary file
|
|
Binary file
|