@kbach/react-native 1.0.0-beta.7 → 1.0.0-beta.8
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 +108 -39
- package/dist/ThemeContext-Dc5AC-m0.d.mts +98 -0
- package/dist/ThemeContext-Dc5AC-m0.d.ts +98 -0
- package/dist/{chunk-AEVOM2NB.mjs → chunk-4BXM3QUV.mjs} +38 -57
- package/dist/{chunk-4KCCEMEN.mjs → chunk-CGU7UM2F.mjs} +1 -1
- package/dist/chunk-JR3BLUAV.mjs +1204 -0
- package/dist/chunk-JS5KIGAB.mjs +192 -0
- package/dist/{chunk-OJQAJLYT.mjs → chunk-KV3ZIXSH.mjs} +1 -1
- package/dist/chunk-OIW7OQWO.mjs +40 -0
- package/dist/chunk-Z664AKW7.mjs +54 -0
- package/dist/index.d.mts +21 -98
- package/dist/index.d.ts +21 -98
- package/dist/index.js +1219 -5
- package/dist/index.mjs +8 -6
- package/dist/index.web.d.mts +12 -4
- package/dist/index.web.d.ts +12 -4
- package/dist/index.web.js +101 -36
- package/dist/index.web.mjs +11 -9
- package/dist/jsx-dev-runtime.js +1223 -5
- package/dist/jsx-dev-runtime.mjs +4 -3
- package/dist/jsx-dev-runtime.web.js +108 -71
- package/dist/jsx-dev-runtime.web.mjs +4 -4
- package/dist/jsx-runtime.js +1223 -5
- package/dist/jsx-runtime.mjs +4 -3
- package/dist/jsx-runtime.web.js +93 -61
- package/dist/jsx-runtime.web.mjs +4 -6
- package/package.json +22 -4
- package/dist/chunk-5WK55Z56.mjs +0 -14
- package/dist/chunk-FG5E4NLJ.mjs +0 -18
- package/dist/chunk-T3HPTH62.mjs +0 -161
package/README.md
CHANGED
|
@@ -5,9 +5,12 @@ names as [`@kbach/react`](https://www.npmjs.com/package/@kbach/react),
|
|
|
5
5
|
resolved to a real `StyleSheet`-ready style object instead of injected CSS.
|
|
6
6
|
|
|
7
7
|
> **Beta.** Native (Android-only, no iOS bridge yet) resolves through a
|
|
8
|
-
> Kotlin/JNI TurboModule; Expo Web /
|
|
9
|
-
> same Rust engine compiled to WASM
|
|
10
|
-
>
|
|
8
|
+
> Kotlin/JNI TurboModule to an inline style object; Expo Web /
|
|
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
|
|
11
14
|
> native module ships prebuilt (see Setup below) — no Rust toolchain or
|
|
12
15
|
> manual native wiring required on any platform.
|
|
13
16
|
|
|
@@ -22,8 +25,8 @@ prebuilt native `.so` ship inside this same package's own `android/`
|
|
|
22
25
|
folder. Android's autolinking discovers it automatically the moment it's
|
|
23
26
|
in `node_modules`; no manual `MainApplication.kt` edit needed. Works the
|
|
24
27
|
same way whether your app was created with the React Native CLI or with
|
|
25
|
-
Expo
|
|
26
|
-
|
|
28
|
+
Expo, **including Expo Go** — see its own section below for the scope
|
|
29
|
+
tradeoff that involves.
|
|
27
30
|
|
|
28
31
|
### React Native CLI
|
|
29
32
|
|
|
@@ -47,18 +50,53 @@ one needs (a plain single-app install doesn't need any of that — it's
|
|
|
47
50
|
only relevant if you're linking packages locally instead of installing
|
|
48
51
|
from npm).
|
|
49
52
|
|
|
50
|
-
### Expo
|
|
51
|
-
|
|
52
|
-
**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
### Expo Go
|
|
54
|
+
|
|
55
|
+
**Works — no prebuild, no dev client, no extra setup.** Expo Go is a
|
|
56
|
+
single fixed binary Expo pre-builds and ships through the app stores,
|
|
57
|
+
containing only the native code Expo bundled into it at their own build
|
|
58
|
+
time — there is no mechanism for *any* third-party package to add native
|
|
59
|
+
code to it at runtime, whether that's a raw TurboModule (what this
|
|
60
|
+
package's Android/Expo-dev-client path uses) or even Expo's own Modules
|
|
61
|
+
API. So instead of trying to load one, this package detects that the
|
|
62
|
+
TurboModule isn't registered (`TurboModuleRegistry.get('KbachModule')`
|
|
63
|
+
returns `null` rather than throwing) and transparently falls back to a
|
|
64
|
+
**third resolution engine**: a pure-JavaScript reimplementation of the
|
|
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:
|
|
62
100
|
|
|
63
101
|
```js
|
|
64
102
|
// babel.config.js
|
|
@@ -92,22 +130,47 @@ needs (a plain single-app install doesn't need it).
|
|
|
92
130
|
**Works — no extra setup, no separate package, no init call.** `expo start
|
|
93
131
|
--web` (and EAS's web builds) render through `react-native-web` —
|
|
94
132
|
`View`/`Text`/etc. become real DOM elements with real CSS, not a
|
|
95
|
-
Kotlin/JNI bridge
|
|
96
|
-
same
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
133
|
+
Kotlin/JNI bridge — so on web this package resolves classes through the
|
|
134
|
+
same real-CSS engine
|
|
135
|
+
[`@kbach/react`](https://www.npmjs.com/package/@kbach/react) uses on the
|
|
136
|
+
DOM, not the inline-style-object engine native/Expo Go use. Metro picks
|
|
137
|
+
the right implementation per platform on its own via this package's
|
|
138
|
+
`"browser"` (client bundles) and `"node"` (Expo Router's static-rendering/
|
|
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
|
|
103
142
|
(synchronously, at import time — no `await` needed before your first
|
|
104
143
|
render, unlike a from-scratch WASM setup).
|
|
105
144
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
[`
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
111
174
|
|
|
112
175
|
#### Expo Router static rendering: `dark:`/responsive on the FIRST paint
|
|
113
176
|
|
|
@@ -155,9 +218,12 @@ before your first `useEffect` runs.
|
|
|
155
218
|
## What this is
|
|
156
219
|
|
|
157
220
|
- A JSX runtime (`@kbach/react-native/jsx-runtime`) that intercepts
|
|
158
|
-
`className`/`kb` props on every element and calls into the native
|
|
159
|
-
bridge (whichever platform you're on
|
|
160
|
-
synchronously, on every render.
|
|
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.
|
|
161
227
|
- A Babel plugin (`@kbach/react-native/babel-plugin`) that routes your
|
|
162
228
|
app's own files (never `node_modules`) through that JSX runtime via a
|
|
163
229
|
per-file `@jsxImportSource` pragma.
|
|
@@ -175,12 +241,15 @@ before your first `useEffect` runs.
|
|
|
175
241
|
arbitrary `shadow-[...]` values aren't supported — RN's native shadow
|
|
176
242
|
props can't express an inset shadow, and an arbitrary value is a raw CSS
|
|
177
243
|
string with no mechanical translation into discrete native props.
|
|
178
|
-
- Responsive breakpoints (`sm:`/`md:`/`lg:`/`xl:`/`2xl:`) —
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
own `useWindowDimensions()` somewhere in your tree to
|
|
183
|
-
rotation/resize
|
|
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.
|
|
184
253
|
|
|
185
254
|
## Dark mode
|
|
186
255
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ThemeConfig {
|
|
5
|
+
/** Plain hex only, this pass — e.g. "blue-6" -> "#2563eb" */
|
|
6
|
+
colors: Record<string, string>;
|
|
7
|
+
spacing: Record<string, number>;
|
|
8
|
+
screens: Record<string, number>;
|
|
9
|
+
darkMode: 'attribute' | 'class' | 'media';
|
|
10
|
+
}
|
|
11
|
+
declare const defaultTheme: ThemeConfig;
|
|
12
|
+
declare function setTheme(theme: ThemeConfig): void;
|
|
13
|
+
declare function getTheme(): ThemeConfig;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Native port of @kbach/react's darkModeStore.ts — same architecture (a
|
|
17
|
+
* single module-level store is the real source of truth;
|
|
18
|
+
* ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
|
|
19
|
+
* function here works standalone with no provider mounted anywhere), with
|
|
20
|
+
* two real platform differences from the web version:
|
|
21
|
+
*
|
|
22
|
+
* 1. No persistence. There's no React-Native-builtin equivalent of
|
|
23
|
+
* localStorage — adding one would mean forcing a real native dependency
|
|
24
|
+
* (e.g. @react-native-async-storage/async-storage) on every consumer,
|
|
25
|
+
* even ones that don't want it. The mode always starts at 'system' on a
|
|
26
|
+
* fresh app launch. If you want persistence, read your own storage at
|
|
27
|
+
* startup (before your first render) and call setGlobalThemeMode() with
|
|
28
|
+
* the result — same "seed once, no built-in write-back" shape as
|
|
29
|
+
* seedDefaultMode() below, just driven by your own storage instead of
|
|
30
|
+
* one this package picks for you.
|
|
31
|
+
* 2. No DOM to "apply" the resolved mode to. On web, applyToDom() flips a
|
|
32
|
+
* class/attribute the generated CSS's `dark:` selectors match against.
|
|
33
|
+
* On native there's no CSS at all — nativeBridge.ts's resolveStyle()
|
|
34
|
+
* reads getGlobalDarkMode() directly on every call instead of querying
|
|
35
|
+
* Appearance.getColorScheme() itself, which is what actually makes an
|
|
36
|
+
* explicit override affect rendering (previously that function always
|
|
37
|
+
* read the raw OS setting, so it had no way to reflect a user choice —
|
|
38
|
+
* see nativeBridge.ts's own comment on this for why that's the one
|
|
39
|
+
* genuinely fixed bug this change introduces, not just a new feature).
|
|
40
|
+
*/
|
|
41
|
+
type ThemeMode = 'light' | 'dark' | 'system';
|
|
42
|
+
declare function getGlobalDarkMode(): boolean;
|
|
43
|
+
declare function getGlobalThemeMode(): ThemeMode;
|
|
44
|
+
/** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
|
|
45
|
+
declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
46
|
+
declare function toggleGlobalDarkMode(): void;
|
|
47
|
+
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reactive read of the global dark-mode store — works with or without a
|
|
51
|
+
* `<ThemeProvider>` mounted anywhere in the tree, since it subscribes to
|
|
52
|
+
* `darkModeStore.ts` directly. No server-snapshot argument (unlike
|
|
53
|
+
* @kbach/react's web version) — React Native has no SSR concept at all,
|
|
54
|
+
* so there's no third argument for `useSyncExternalStore` to need.
|
|
55
|
+
*/
|
|
56
|
+
declare function useGlobalDarkMode(): boolean;
|
|
57
|
+
|
|
58
|
+
interface ThemeProviderProps {
|
|
59
|
+
children: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
62
|
+
* made an explicit mode choice yet (see
|
|
63
|
+
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
64
|
+
* wins over a component's own default. Unlike @kbach/react's web
|
|
65
|
+
* version, there's no persisted value to check here — see
|
|
66
|
+
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
67
|
+
* the mode across app launches.
|
|
68
|
+
*/
|
|
69
|
+
defaultMode?: ThemeMode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Native port of @kbach/react's `ThemeProvider` — a thin, OPTIONAL React
|
|
73
|
+
* Context wrapper around `darkModeStore.ts`'s single global store, not the
|
|
74
|
+
* source of truth itself. Because the store is global, MULTIPLE mounted
|
|
75
|
+
* providers share the exact same `mode`/`isDark` state rather than each
|
|
76
|
+
* getting their own scoped value — mounting more than one is very likely a
|
|
77
|
+
* mistake (each fighting over the same state via a different
|
|
78
|
+
* `defaultMode`), so this warns once in dev, matching the web version's
|
|
79
|
+
* identical guard.
|
|
80
|
+
*/
|
|
81
|
+
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
82
|
+
|
|
83
|
+
interface ThemeContextValue {
|
|
84
|
+
mode: ThemeMode;
|
|
85
|
+
isDark: boolean;
|
|
86
|
+
setMode: (mode: ThemeMode) => void;
|
|
87
|
+
toggle: () => void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Requires a `<ThemeProvider>` ancestor — throws otherwise, since a
|
|
91
|
+
* component that destructures `{ mode, setMode }` genuinely needs Context
|
|
92
|
+
* (unlike a component that just wants the dark/light boolean, which should
|
|
93
|
+
* reach for `useGlobalDarkMode()` instead and never needs a provider at
|
|
94
|
+
* all).
|
|
95
|
+
*/
|
|
96
|
+
declare function useTheme(): ThemeContextValue;
|
|
97
|
+
|
|
98
|
+
export { type ThemeConfig as T, type ThemeContextValue as a, type ThemeMode as b, ThemeProvider as c, type ThemeProviderProps as d, defaultTheme as e, getGlobalThemeMode as f, getGlobalDarkMode as g, getTheme as h, setTheme as i, subscribeGlobalDarkMode as j, useTheme as k, setGlobalThemeMode as s, toggleGlobalDarkMode as t, useGlobalDarkMode as u };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ThemeConfig {
|
|
5
|
+
/** Plain hex only, this pass — e.g. "blue-6" -> "#2563eb" */
|
|
6
|
+
colors: Record<string, string>;
|
|
7
|
+
spacing: Record<string, number>;
|
|
8
|
+
screens: Record<string, number>;
|
|
9
|
+
darkMode: 'attribute' | 'class' | 'media';
|
|
10
|
+
}
|
|
11
|
+
declare const defaultTheme: ThemeConfig;
|
|
12
|
+
declare function setTheme(theme: ThemeConfig): void;
|
|
13
|
+
declare function getTheme(): ThemeConfig;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Native port of @kbach/react's darkModeStore.ts — same architecture (a
|
|
17
|
+
* single module-level store is the real source of truth;
|
|
18
|
+
* ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
|
|
19
|
+
* function here works standalone with no provider mounted anywhere), with
|
|
20
|
+
* two real platform differences from the web version:
|
|
21
|
+
*
|
|
22
|
+
* 1. No persistence. There's no React-Native-builtin equivalent of
|
|
23
|
+
* localStorage — adding one would mean forcing a real native dependency
|
|
24
|
+
* (e.g. @react-native-async-storage/async-storage) on every consumer,
|
|
25
|
+
* even ones that don't want it. The mode always starts at 'system' on a
|
|
26
|
+
* fresh app launch. If you want persistence, read your own storage at
|
|
27
|
+
* startup (before your first render) and call setGlobalThemeMode() with
|
|
28
|
+
* the result — same "seed once, no built-in write-back" shape as
|
|
29
|
+
* seedDefaultMode() below, just driven by your own storage instead of
|
|
30
|
+
* one this package picks for you.
|
|
31
|
+
* 2. No DOM to "apply" the resolved mode to. On web, applyToDom() flips a
|
|
32
|
+
* class/attribute the generated CSS's `dark:` selectors match against.
|
|
33
|
+
* On native there's no CSS at all — nativeBridge.ts's resolveStyle()
|
|
34
|
+
* reads getGlobalDarkMode() directly on every call instead of querying
|
|
35
|
+
* Appearance.getColorScheme() itself, which is what actually makes an
|
|
36
|
+
* explicit override affect rendering (previously that function always
|
|
37
|
+
* read the raw OS setting, so it had no way to reflect a user choice —
|
|
38
|
+
* see nativeBridge.ts's own comment on this for why that's the one
|
|
39
|
+
* genuinely fixed bug this change introduces, not just a new feature).
|
|
40
|
+
*/
|
|
41
|
+
type ThemeMode = 'light' | 'dark' | 'system';
|
|
42
|
+
declare function getGlobalDarkMode(): boolean;
|
|
43
|
+
declare function getGlobalThemeMode(): ThemeMode;
|
|
44
|
+
/** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
|
|
45
|
+
declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
46
|
+
declare function toggleGlobalDarkMode(): void;
|
|
47
|
+
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reactive read of the global dark-mode store — works with or without a
|
|
51
|
+
* `<ThemeProvider>` mounted anywhere in the tree, since it subscribes to
|
|
52
|
+
* `darkModeStore.ts` directly. No server-snapshot argument (unlike
|
|
53
|
+
* @kbach/react's web version) — React Native has no SSR concept at all,
|
|
54
|
+
* so there's no third argument for `useSyncExternalStore` to need.
|
|
55
|
+
*/
|
|
56
|
+
declare function useGlobalDarkMode(): boolean;
|
|
57
|
+
|
|
58
|
+
interface ThemeProviderProps {
|
|
59
|
+
children: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
62
|
+
* made an explicit mode choice yet (see
|
|
63
|
+
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
64
|
+
* wins over a component's own default. Unlike @kbach/react's web
|
|
65
|
+
* version, there's no persisted value to check here — see
|
|
66
|
+
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
67
|
+
* the mode across app launches.
|
|
68
|
+
*/
|
|
69
|
+
defaultMode?: ThemeMode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Native port of @kbach/react's `ThemeProvider` — a thin, OPTIONAL React
|
|
73
|
+
* Context wrapper around `darkModeStore.ts`'s single global store, not the
|
|
74
|
+
* source of truth itself. Because the store is global, MULTIPLE mounted
|
|
75
|
+
* providers share the exact same `mode`/`isDark` state rather than each
|
|
76
|
+
* getting their own scoped value — mounting more than one is very likely a
|
|
77
|
+
* mistake (each fighting over the same state via a different
|
|
78
|
+
* `defaultMode`), so this warns once in dev, matching the web version's
|
|
79
|
+
* identical guard.
|
|
80
|
+
*/
|
|
81
|
+
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
82
|
+
|
|
83
|
+
interface ThemeContextValue {
|
|
84
|
+
mode: ThemeMode;
|
|
85
|
+
isDark: boolean;
|
|
86
|
+
setMode: (mode: ThemeMode) => void;
|
|
87
|
+
toggle: () => void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Requires a `<ThemeProvider>` ancestor — throws otherwise, since a
|
|
91
|
+
* component that destructures `{ mode, setMode }` genuinely needs Context
|
|
92
|
+
* (unlike a component that just wants the dark/light boolean, which should
|
|
93
|
+
* reach for `useGlobalDarkMode()` instead and never needs a provider at
|
|
94
|
+
* all).
|
|
95
|
+
*/
|
|
96
|
+
declare function useTheme(): ThemeContextValue;
|
|
97
|
+
|
|
98
|
+
export { type ThemeConfig as T, type ThemeContextValue as a, type ThemeMode as b, ThemeProvider as c, type ThemeProviderProps as d, defaultTheme as e, getGlobalThemeMode as f, getGlobalDarkMode as g, getTheme as h, setTheme as i, subscribeGlobalDarkMode as j, useTheme as k, setGlobalThemeMode as s, toggleGlobalDarkMode as t, useGlobalDarkMode as u };
|
|
@@ -278,16 +278,49 @@ var PALETTE = {
|
|
|
278
278
|
// src/theme.ts
|
|
279
279
|
var defaultTheme = {
|
|
280
280
|
colors: PALETTE,
|
|
281
|
+
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
282
|
+
// `n * 4px`, including the fractional `.5` steps (`1.5` -> 6px, `2.5` ->
|
|
283
|
+
// 10px, `3.5` -> 14px) and the "px" literal (a bare 1px). Previously
|
|
284
|
+
// this only had {0,1,2,4,6,8} — a handful of spot-checked values, not
|
|
285
|
+
// the real scale — so anything else (`p-3`, `w-5`, `border-2`,
|
|
286
|
+
// `gap-10`, ...) silently failed to resolve. Mirrors @kbach/react's
|
|
287
|
+
// theme.ts exactly.
|
|
281
288
|
spacing: {
|
|
282
|
-
// "0" is always a hardcoded zero in real Tailwind's own scale too,
|
|
283
|
-
// regardless of the base spacing unit — needed for `p-0`/`m-0`/etc.
|
|
284
|
-
// Mirrors the identical fix in @kbach/react's theme.ts.
|
|
285
289
|
"0": 0,
|
|
290
|
+
"px": 1,
|
|
291
|
+
"0.5": 2,
|
|
286
292
|
"1": 4,
|
|
293
|
+
"1.5": 6,
|
|
287
294
|
"2": 8,
|
|
295
|
+
"2.5": 10,
|
|
296
|
+
"3": 12,
|
|
297
|
+
"3.5": 14,
|
|
288
298
|
"4": 16,
|
|
299
|
+
"5": 20,
|
|
289
300
|
"6": 24,
|
|
290
|
-
"
|
|
301
|
+
"7": 28,
|
|
302
|
+
"8": 32,
|
|
303
|
+
"9": 36,
|
|
304
|
+
"10": 40,
|
|
305
|
+
"11": 44,
|
|
306
|
+
"12": 48,
|
|
307
|
+
"14": 56,
|
|
308
|
+
"16": 64,
|
|
309
|
+
"20": 80,
|
|
310
|
+
"24": 96,
|
|
311
|
+
"28": 112,
|
|
312
|
+
"32": 128,
|
|
313
|
+
"36": 144,
|
|
314
|
+
"40": 160,
|
|
315
|
+
"44": 176,
|
|
316
|
+
"48": 192,
|
|
317
|
+
"52": 208,
|
|
318
|
+
"56": 224,
|
|
319
|
+
"60": 240,
|
|
320
|
+
"64": 256,
|
|
321
|
+
"72": 288,
|
|
322
|
+
"80": 320,
|
|
323
|
+
"96": 384
|
|
291
324
|
},
|
|
292
325
|
screens: {
|
|
293
326
|
sm: 640,
|
|
@@ -311,61 +344,9 @@ function getThemeJson() {
|
|
|
311
344
|
return activeThemeJson;
|
|
312
345
|
}
|
|
313
346
|
|
|
314
|
-
// src/darkModeStore.ts
|
|
315
|
-
import { Appearance } from "react-native";
|
|
316
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
317
|
-
function systemPrefersDark() {
|
|
318
|
-
return Appearance.getColorScheme() === "dark";
|
|
319
|
-
}
|
|
320
|
-
function resolveIsDark(m) {
|
|
321
|
-
return m === "system" ? systemPrefersDark() : m === "dark";
|
|
322
|
-
}
|
|
323
|
-
var mode = "system";
|
|
324
|
-
var isDark = resolveIsDark(mode);
|
|
325
|
-
var hasExplicitChoice = false;
|
|
326
|
-
var subscription = Appearance.addChangeListener(({ colorScheme }) => {
|
|
327
|
-
if (mode !== "system") return;
|
|
328
|
-
isDark = colorScheme === "dark";
|
|
329
|
-
notify();
|
|
330
|
-
});
|
|
331
|
-
function notify() {
|
|
332
|
-
for (const listener of listeners) listener();
|
|
333
|
-
}
|
|
334
|
-
function getGlobalDarkMode() {
|
|
335
|
-
return isDark;
|
|
336
|
-
}
|
|
337
|
-
function getGlobalThemeMode() {
|
|
338
|
-
return mode;
|
|
339
|
-
}
|
|
340
|
-
function setGlobalThemeMode(nextMode) {
|
|
341
|
-
mode = nextMode;
|
|
342
|
-
isDark = resolveIsDark(nextMode);
|
|
343
|
-
hasExplicitChoice = true;
|
|
344
|
-
notify();
|
|
345
|
-
}
|
|
346
|
-
function toggleGlobalDarkMode() {
|
|
347
|
-
setGlobalThemeMode(isDark ? "light" : "dark");
|
|
348
|
-
}
|
|
349
|
-
function subscribeGlobalDarkMode(listener) {
|
|
350
|
-
listeners.add(listener);
|
|
351
|
-
return () => listeners.delete(listener);
|
|
352
|
-
}
|
|
353
|
-
function seedDefaultMode(fallback) {
|
|
354
|
-
if (hasExplicitChoice || mode === fallback) return;
|
|
355
|
-
mode = fallback;
|
|
356
|
-
isDark = resolveIsDark(fallback);
|
|
357
|
-
notify();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
347
|
export {
|
|
361
348
|
defaultTheme,
|
|
362
349
|
setTheme,
|
|
363
350
|
getTheme,
|
|
364
|
-
getThemeJson
|
|
365
|
-
getGlobalDarkMode,
|
|
366
|
-
getGlobalThemeMode,
|
|
367
|
-
setGlobalThemeMode,
|
|
368
|
-
toggleGlobalDarkMode,
|
|
369
|
-
subscribeGlobalDarkMode,
|
|
370
|
-
seedDefaultMode
|
|
351
|
+
getThemeJson
|
|
371
352
|
};
|