@kbach/react-native 1.0.0-beta.20 → 1.0.0-beta.22

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
@@ -58,9 +58,10 @@ way, no extra config.
58
58
  ### Expo Web
59
59
 
60
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.
61
+ `@kbach/react` uses on the DOM, with the FULL modifier set (`group-hover:`,
62
+ `peer-hover:`, container queries, `has-[...]:`, etc.) — everything real
63
+ Tailwind supports. Native supports a smaller, explicitly-scoped subset; see
64
+ "Modifiers on native" below for exactly what that covers.
64
65
 
65
66
  **Static rendering (SSR) caveat:** `dark:`/responsive classes on content
66
67
  that's part of the initial pre-rendered HTML resolve against the *server*,
@@ -124,6 +125,38 @@ module.exports = config; // (React Native CLI: mergeConfig(getDefaultConfig(proj
124
125
  A single, non-monorepo `npm install` never hits this — only relevant if your
125
126
  app and `@kbach/react-native` share a workspace root.
126
127
 
128
+ ## Modifiers on native
129
+
130
+ Native has no CSS selector/cascade engine, so only a fixed, explicitly
131
+ supported set of modifiers actually applies — everything else (`group-*:`,
132
+ `peer-*:`, `has-[...]:`, `aria-*:`, `data-*:`, container queries, ...)
133
+ parses without error but never takes effect. Supported today:
134
+
135
+ - **`dark:`** — reacts to the OS/app color scheme, see "Dark mode" below.
136
+ - **`sm:`/`md:`/`lg:`/`xl:`/`2xl:`** — reacts to `useWindowDimensions()`
137
+ crossing the matching `theme.screens` breakpoint (rotation/resize/fold).
138
+ - **`active:`** — `Pressable`'s own press state, via its style-callback API.
139
+ - **`hover:`** — tracked via `onHoverIn`/`onHoverOut`, which RN only ever
140
+ fires on a platform with real pointer input (iOS 13+/Android/macOS/
141
+ Windows via a mouse/trackpad/Apple Pencil hover) — inert (never fires) on
142
+ a touch-only device, the same way real Tailwind's `hover:` already
143
+ degrades on a touch-only *browser*. Composing it onto any host type is
144
+ harmless even where it never fires.
145
+ - **`focus:`** — tracked via `onFocus`/`onBlur` (keyboard/external-keyboard/
146
+ TV-remote/tab navigation, and `TextInput` focus).
147
+ - **`disabled:`** — reads the element's own `disabled` prop directly; no
148
+ event tracking needed, since a prop change already re-renders normally.
149
+ - Any combination of the above chained on one class (`sm:dark:bg-blue-8`,
150
+ `hover:focus:opacity-100`) requires every named condition to hold at once
151
+ — chain order never matters (`dark:sm:` and `sm:dark:` are equivalent).
152
+
153
+ `group-hover:`/`peer-hover:` and friends are NOT implemented on native —
154
+ doing so needs a React Context bridge (propagating an ancestor's hover/
155
+ press/focus state down to descendants, since there's no `.group`/`.peer`
156
+ selector relationship to lean on) that doesn't exist today. If you need
157
+ group/peer-style coordination on native right now, lift the shared state
158
+ into a parent component yourself and pass it down as an explicit prop.
159
+
127
160
  ## Dark mode
128
161
 
129
162
  ```tsx
@@ -140,8 +173,26 @@ optional, for seeding a startup default. Outside React, use
140
173
  `getGlobalDarkMode()`/`toggleGlobalDarkMode()` instead.
141
174
 
142
175
  Follows the OS setting live until overridden, and isn't persisted across
143
- app launches (no built-in storage dependency). To persist, read your own
144
- storage at startup and call `setGlobalThemeMode()` before your first render.
176
+ app launches by default (no forced storage dependency). To persist, pass
177
+ any async key-value store exposing `getItem`/`setItem` as `<ThemeProvider
178
+ persist>` — `@react-native-async-storage/async-storage`'s default export
179
+ already matches this shape exactly:
180
+
181
+ ```tsx
182
+ import AsyncStorage from '@react-native-async-storage/async-storage';
183
+ import { ThemeProvider } from '@kbach/react-native';
184
+
185
+ <ThemeProvider persist={AsyncStorage}>
186
+ <App />
187
+ </ThemeProvider>;
188
+ ```
189
+
190
+ The persisted mode (if any) is read once on mount and wins over
191
+ `defaultMode`; every later explicit choice is written back automatically.
192
+ `false` by default — this package still never imports a storage library
193
+ itself, so nothing changes for apps that don't opt in. Prefer wiring your
194
+ own storage manually instead? `setGlobalThemeMode()` before your first
195
+ render still works exactly as before.
145
196
 
146
197
  ## Reading colors as values
147
198
 
@@ -189,12 +240,15 @@ way — no such unit exists on native at all. It already works fine on web
189
240
  (real CSS); on native the declaration is dropped and a `console.warn`
190
241
  explains why (dev builds only).
191
242
 
192
- **Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)`)
193
- IS supported, differently: since native has no way to know "100% of what"
194
- ahead of time, this measures the element's own real layout instead —
243
+ **Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)`,
244
+ `min(50%,20rem)`, `max(1rem,5%)`, `clamp(16rem,50%,32rem)`) IS supported,
245
+ differently: since native has no way to know "100% of what" ahead of time,
246
+ this measures the element's own real layout instead —
195
247
 
196
248
  ```tsx
197
249
  <View className="w-[calc(100%_-_3rem)]" />
250
+ <View className="w-[min(50%,20rem)]" /> // half the parent, capped at a fixed size
251
+ <View className="w-[clamp(16rem,50%,32rem)]" />
198
252
  ```
199
253
 
200
254
  Renders once at a plain `100%` (a real RN percentage, resolved correctly by
@@ -203,9 +257,14 @@ event, then immediately corrects to the actual computed number once
203
257
  measured — a genuine one-frame measure-then-snap, not instant math like the
204
258
  constant-only case above, and it keeps re-measuring on every layout (screen
205
259
  rotation, parent resize, ...) rather than freezing after the first value.
206
- Scoped to exactly one percentage term plus one constant term on `width`/
207
- `height` only — `calc(50%-50%)`, other properties, or anything with `*`/`/`
208
- falls through to the same drop-and-warn behavior as before.
260
+ Scoped narrowly, same "reduce fully or bail" philosophy as the constant-only
261
+ case: `calc()` takes exactly one percentage term plus one constant term;
262
+ `min()`/`max()` take two or more comma-separated arguments, each EITHER a
263
+ plain percentage or a constant px/rem expression (not both mixed in one
264
+ argument — `min(50%-1rem,20rem)` is out of scope); `clamp()` takes exactly
265
+ three. `width`/`height` only in every case — other properties, or anything
266
+ that doesn't reduce fully, falls through to the same drop-and-warn behavior
267
+ as before.
209
268
 
210
269
  **Arbitrary values can't contain literal spaces** — this is a pre-existing
211
270
  rule (real Tailwind has the same one), not specific to `calc()`: a space
@@ -264,8 +323,8 @@ On web, `setDynamicToken` writes the real CSS custom property to the DOM —
264
323
  every element using it repaints instantly via the browser's own CSS engine,
265
324
  no React re-render involved. On native, the className is substituted with
266
325
  the token's current value before every resolve, and an element referencing
267
- a token re-renders/repaints on its own when that specific token changes
268
- (same `key`-remount mechanism `dark:` and `sm:`/`md:`/... already use — see
326
+ a token re-renders/repaints on its own when that token changes (same
327
+ subscription mechanism `dark:` and `sm:`/`md:`/... already use — see
269
328
  `jsxRuntimeCore.ts`'s own doc comments). An unregistered token name falls
270
329
  through to the same native invalid-value warning above.
271
330