@kbach/react-native 1.0.0-beta.1 → 1.0.0-beta.10
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 +87 -66
- package/android/build.gradle +36 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/kbachnative/KbachModule.kt +83 -0
- package/android/src/main/java/com/kbachnative/KbachPackage.kt +54 -0
- 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-4N7UEVQL.mjs +14 -0
- package/dist/{chunk-SVMYVXY6.mjs → chunk-DLIXI5OW.mjs} +23 -14
- package/dist/chunk-H5Z24XX5.mjs +192 -0
- package/dist/{chunk-YHF3QLRL.mjs → chunk-HK6PDJJK.mjs} +61 -68
- package/dist/chunk-RBJXE32L.mjs +73 -0
- package/dist/chunk-TDWBXJH5.mjs +186 -0
- package/dist/chunk-XHL653X2.mjs +1211 -0
- package/dist/chunk-YOC3SHF5.mjs +40 -0
- package/dist/index.d.mts +37 -102
- package/dist/index.d.ts +37 -102
- package/dist/index.js +1411 -38
- package/dist/index.mjs +19 -61
- package/dist/index.web.d.mts +15 -0
- package/dist/index.web.d.ts +15 -0
- package/dist/index.web.js +829 -0
- package/dist/index.web.mjs +38 -0
- package/dist/jsx-dev-runtime.d.mts +3 -12
- package/dist/jsx-dev-runtime.d.ts +3 -12
- package/dist/jsx-dev-runtime.js +1314 -47
- package/dist/jsx-dev-runtime.mjs +9 -11
- package/dist/jsx-dev-runtime.web.d.mts +10 -0
- package/dist/jsx-dev-runtime.web.d.ts +10 -0
- package/dist/jsx-dev-runtime.web.js +618 -0
- package/dist/jsx-dev-runtime.web.mjs +17 -0
- package/dist/jsx-runtime.d.mts +3 -23
- package/dist/jsx-runtime.d.ts +3 -23
- package/dist/jsx-runtime.js +1305 -39
- package/dist/jsx-runtime.mjs +7 -3
- package/dist/jsx-runtime.web.d.mts +7 -0
- package/dist/jsx-runtime.web.d.ts +7 -0
- package/dist/jsx-runtime.web.js +602 -0
- package/dist/jsx-runtime.web.mjs +12 -0
- package/dist/useColors-C6jyCLuG.d.mts +239 -0
- package/dist/useColors-C6jyCLuG.d.ts +239 -0
- package/package.json +57 -8
- package/react-native.config.js +29 -0
- package/specs/NativeKbachModule.ts +31 -0
- package/src/babel-plugin.js +74 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A plain hex value (static — same in both modes), or a `{ light, dark }`
|
|
6
|
+
* pair a caller sets manually in their own theme. Fully opt-in per color:
|
|
7
|
+
* nothing here picks a "dark variant" automatically. Only `useColors()`
|
|
8
|
+
* (a JS-value read, not a `className`) understands this shape — utility
|
|
9
|
+
* CLASS resolution (`bg-blue-6`) on native/Expo Go stays plain-string-only,
|
|
10
|
+
* same as it always was (dark mode for a CLASS is the separate `dark:`
|
|
11
|
+
* modifier, gated live at resolve time — see `resolve_style.rs`); a
|
|
12
|
+
* mode-aware entry referenced directly by a class name just doesn't
|
|
13
|
+
* resolve there, mirroring `resolvers/color.rs`'s own `lookup_hex`
|
|
14
|
+
* (`ColorValue::Plain` only, `ModeAware` -> `None`) exactly, same as it
|
|
15
|
+
* would on the web engine too.
|
|
16
|
+
*/
|
|
17
|
+
type ColorEntry = string | {
|
|
18
|
+
light: string;
|
|
19
|
+
dark: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Real Tailwind's `theme.container` config, mirroring
|
|
23
|
+
* `packages/core-engine/src/theme.rs`'s `ContainerConfig` exactly. `center`/
|
|
24
|
+
* `padding` DO reach native now (unlike the max-width breakpoint ladder,
|
|
25
|
+
* which is a `css.rs`-only concept with nothing to generate on native at
|
|
26
|
+
* all) — `resolvers::layout::resolve`'s `"container"` arm is shared by both
|
|
27
|
+
* dispatchers, and `margin`/`padding` are ordinary RN style properties.
|
|
28
|
+
*/
|
|
29
|
+
interface ContainerConfig {
|
|
30
|
+
center?: boolean;
|
|
31
|
+
padding?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ThemeConfig {
|
|
34
|
+
colors: Record<string, ColorEntry>;
|
|
35
|
+
spacing: Record<string, number>;
|
|
36
|
+
screens: Record<string, number>;
|
|
37
|
+
/**
|
|
38
|
+
* e.g. "sans" -> "Inter, sans-serif". Optional, matching the Rust
|
|
39
|
+
* engine's own `#[serde(default)]`. Write it as a real web-shaped
|
|
40
|
+
* fallback stack even on this native-first package — native's own
|
|
41
|
+
* resolver strips it down to a single bare name at resolve time (RN's
|
|
42
|
+
* `fontFamily` prop can't take a fallback list — see
|
|
43
|
+
* `resolvers/mod.rs`'s `first_font_name`), so the SAME config still
|
|
44
|
+
* works unmodified on Expo Web, which needs the full stack.
|
|
45
|
+
*/
|
|
46
|
+
fontFamily?: Record<string, string>;
|
|
47
|
+
darkMode: 'attribute' | 'class' | 'media';
|
|
48
|
+
/** Optional, matching `ContainerConfig`'s own `#[serde(default)]` on the Rust side. */
|
|
49
|
+
container?: ContainerConfig;
|
|
50
|
+
}
|
|
51
|
+
declare const defaultTheme: ThemeConfig;
|
|
52
|
+
declare function setTheme(theme: ThemeConfig): void;
|
|
53
|
+
declare function getTheme(): ThemeConfig;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A `kbach.config.js`-style theme customization — same shape as
|
|
57
|
+
* @kbach/react's `KbachConfig`, minus mode-aware `{ light, dark }` color
|
|
58
|
+
* entries in `extend.colors` specifically: aliasing/opacity math here only
|
|
59
|
+
* ever operates on plain hex (`brand: 'blue-6'`, `brandSoft: 'brand/30'`),
|
|
60
|
+
* matching `jsEngine/resolvers/color.ts`'s `lookupHex`'s own Plain-only
|
|
61
|
+
* convention — a reference to an EXISTING color that happens to be
|
|
62
|
+
* mode-aware (`theme.colors` can hold those now, for `useColors()`; see
|
|
63
|
+
* `theme.ts`'s own `ColorEntry` doc comment) just doesn't resolve as an
|
|
64
|
+
* alias, falling through to "literal CSS value" the same way an unknown
|
|
65
|
+
* name would. A config object goes in, a resolved `ThemeConfig` comes out,
|
|
66
|
+
* via one PURE function (`resolveKbachConfig`) — no plugins, no
|
|
67
|
+
* live-update store, nothing to keep "in sync."
|
|
68
|
+
*/
|
|
69
|
+
interface KbachConfig {
|
|
70
|
+
darkMode?: ThemeConfig['darkMode'];
|
|
71
|
+
/**
|
|
72
|
+
* REPLACES the named section of `defaultTheme` entirely — use `extend` to
|
|
73
|
+
* add instead of replace. `container` isn't here (only under `extend`
|
|
74
|
+
* below) — see @kbach/react's identical `KbachConfig.theme` doc comment
|
|
75
|
+
* for why.
|
|
76
|
+
*/
|
|
77
|
+
theme?: Partial<Pick<ThemeConfig, 'colors' | 'spacing' | 'screens' | 'fontFamily'>>;
|
|
78
|
+
/** Merges INTO `defaultTheme` (or `theme` above, if also supplied) — the common case. */
|
|
79
|
+
extend?: {
|
|
80
|
+
/**
|
|
81
|
+
* A value is either a literal hex color, or a REFERENCE to another
|
|
82
|
+
* color name, optionally with an opacity suffix. Two things can be
|
|
83
|
+
* referenced: a BUILT-IN palette color, always available regardless of
|
|
84
|
+
* ordering (`accent: 'orange-5'`, `accentSoft: 'orange-5/50'`), or a
|
|
85
|
+
* custom color defined EARLIER in this same object (`{ brand:
|
|
86
|
+
* '#ff6b35', brandSoft: 'brand/30' }` — `brandSoft` resolves to
|
|
87
|
+
* `brand`'s own value at 30% opacity). A string that doesn't match any
|
|
88
|
+
* known color name is treated as a literal CSS value instead.
|
|
89
|
+
*/
|
|
90
|
+
colors?: Record<string, string>;
|
|
91
|
+
spacing?: Record<string, number>;
|
|
92
|
+
screens?: Record<string, number>;
|
|
93
|
+
/**
|
|
94
|
+
* Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` —
|
|
95
|
+
* adds to (or overrides one of) `defaultTheme.fontFamily`'s three
|
|
96
|
+
* names. Write it as a real web-shaped fallback stack even though this
|
|
97
|
+
* is the React Native package — native's own resolver strips it down
|
|
98
|
+
* to a single bare name at resolve time (RN's `fontFamily` prop can't
|
|
99
|
+
* take a fallback list), so the SAME config still works unmodified on
|
|
100
|
+
* Expo Web, which needs the full stack.
|
|
101
|
+
*/
|
|
102
|
+
fontFamily?: Record<string, string>;
|
|
103
|
+
/** `{ center: true, padding: '2rem' }` — see `ContainerConfig`'s own doc comment. */
|
|
104
|
+
container?: ContainerConfig;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
|
|
108
|
+
declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
|
|
109
|
+
/** `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point from app code. */
|
|
110
|
+
declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Native port of @kbach/react's darkModeStore.ts — same architecture (a
|
|
114
|
+
* single module-level store is the real source of truth;
|
|
115
|
+
* ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
|
|
116
|
+
* function here works standalone with no provider mounted anywhere), with
|
|
117
|
+
* two real platform differences from the web version:
|
|
118
|
+
*
|
|
119
|
+
* 1. No persistence. There's no React-Native-builtin equivalent of
|
|
120
|
+
* localStorage — adding one would mean forcing a real native dependency
|
|
121
|
+
* (e.g. @react-native-async-storage/async-storage) on every consumer,
|
|
122
|
+
* even ones that don't want it. The mode always starts at 'system' on a
|
|
123
|
+
* fresh app launch. If you want persistence, read your own storage at
|
|
124
|
+
* startup (before your first render) and call setGlobalThemeMode() with
|
|
125
|
+
* the result — same "seed once, no built-in write-back" shape as
|
|
126
|
+
* seedDefaultMode() below, just driven by your own storage instead of
|
|
127
|
+
* one this package picks for you.
|
|
128
|
+
* 2. `applyToDom` below IS needed here, unlike what an earlier version of
|
|
129
|
+
* this file's own comment claimed ("no DOM to apply the resolved mode
|
|
130
|
+
* to") — that reasoning only holds for real native (Android/iOS), where
|
|
131
|
+
* nativeBridge.ts's resolveStyle() reads getGlobalDarkMode() directly on
|
|
132
|
+
* every call, no DOM involved at all. This file is ALSO used by Expo
|
|
133
|
+
* Web, though (no `.web.ts` sibling — Metro has nothing to swap in), and
|
|
134
|
+
* Expo Web resolves `dark:` classes to REAL CSS with `[data-theme=
|
|
135
|
+
* "dark"]`-style selectors (see nativeBridge.web.ts) that only ever
|
|
136
|
+
* match if something writes that attribute to the DOM — nothing did,
|
|
137
|
+
* which is exactly why toggling the mode changed `useTheme()`'s own
|
|
138
|
+
* state (a consumer reading `mode`/`isDark` directly, e.g. for a label,
|
|
139
|
+
* updated correctly) while every `dark:`-prefixed style silently never
|
|
140
|
+
* applied. `typeof document === 'undefined'` is what makes one function
|
|
141
|
+
* safe on both platforms: real native's Hermes has no `document` global
|
|
142
|
+
* at all (not even `undefined` as a defined-but-empty value — genuinely
|
|
143
|
+
* unset, which `typeof` can check without throwing), so this always
|
|
144
|
+
* no-ops there, while Expo Web's react-native-web renders into a real
|
|
145
|
+
* browser DOM where it's needed.
|
|
146
|
+
*/
|
|
147
|
+
type ThemeMode = 'light' | 'dark' | 'system';
|
|
148
|
+
declare function getGlobalDarkMode(): boolean;
|
|
149
|
+
declare function getGlobalThemeMode(): ThemeMode;
|
|
150
|
+
/** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
|
|
151
|
+
declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
152
|
+
declare function toggleGlobalDarkMode(): void;
|
|
153
|
+
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
154
|
+
|
|
155
|
+
interface ThemeState {
|
|
156
|
+
mode: ThemeMode;
|
|
157
|
+
isDark: boolean;
|
|
158
|
+
setMode: (mode: ThemeMode) => void;
|
|
159
|
+
toggle: () => void;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The one hook for reading and writing Kbach's dark-mode state. Reads
|
|
163
|
+
* `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
|
|
164
|
+
* that module-level store, not React Context, is the actual source of
|
|
165
|
+
* truth (every provider used to just mirror it into a Context value), so
|
|
166
|
+
* this works from ANY component, with or without a `<ThemeProvider>`
|
|
167
|
+
* mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
|
|
168
|
+
* as an optional convenience for seeding an app's initial `defaultMode` —
|
|
169
|
+
* see its own doc comment. No server-snapshot argument (unlike
|
|
170
|
+
* @kbach/react's web version) — React Native has no SSR concept at all,
|
|
171
|
+
* so there's no third argument for `useSyncExternalStore` to need.
|
|
172
|
+
*/
|
|
173
|
+
declare function useTheme(): ThemeState;
|
|
174
|
+
|
|
175
|
+
interface ThemeProviderProps {
|
|
176
|
+
children: ReactNode;
|
|
177
|
+
/**
|
|
178
|
+
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
179
|
+
* made an explicit mode choice yet (see
|
|
180
|
+
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
181
|
+
* wins over a component's own default. Unlike @kbach/react's web
|
|
182
|
+
* version, there's no persisted value to check here — see
|
|
183
|
+
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
184
|
+
* the mode across app launches.
|
|
185
|
+
*/
|
|
186
|
+
defaultMode?: ThemeMode;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
|
|
190
|
+
* and works anywhere without this mounted at all. The only thing this
|
|
191
|
+
* component does is seed that store's initial `defaultMode` once, for apps
|
|
192
|
+
* that want to declare a startup default in JSX rather than calling
|
|
193
|
+
* `setGlobalThemeMode()` imperatively before their first render. It
|
|
194
|
+
* renders `children` as-is — no Context.Provider, since there's no value
|
|
195
|
+
* left to distribute; `useTheme()` never reads from Context.
|
|
196
|
+
*
|
|
197
|
+
* Because the store is global, MULTIPLE mounted providers would each try
|
|
198
|
+
* to seed the exact same store rather than getting their own scoped
|
|
199
|
+
* state — mounting more than one is very likely a mistake, so this warns
|
|
200
|
+
* once in dev.
|
|
201
|
+
*/
|
|
202
|
+
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
203
|
+
|
|
204
|
+
interface ColorScale {
|
|
205
|
+
/** `colors.blue[6]` -> raw value */
|
|
206
|
+
readonly [shade: number]: string;
|
|
207
|
+
/** `colors.blue['6/50']` -> shade 6 at 50% opacity */
|
|
208
|
+
readonly [key: string]: string;
|
|
209
|
+
}
|
|
210
|
+
type ColorsAPI = {
|
|
211
|
+
/** `colors.white`, `colors.blue` (a shade family), ... */
|
|
212
|
+
readonly [name: string]: string | ColorScale;
|
|
213
|
+
} & {
|
|
214
|
+
/** Applies an opacity (0–100) to any color string this hook can return. */
|
|
215
|
+
readonly alpha: (color: string, opacity?: number) => string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Resolved theme colors as real values — for anywhere you need an actual
|
|
219
|
+
* color (a chart library prop, a raw SVG `fill`), not a `className`. Ported
|
|
220
|
+
* from old-kbach's `useColors()`, adapted to this engine's flat
|
|
221
|
+
* `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
|
|
222
|
+
*
|
|
223
|
+
* Any color — a shade-family entry like `"blue-6"` or a standalone name —
|
|
224
|
+
* can be defined in the theme as either a plain string (static, same in
|
|
225
|
+
* both modes) or a `{ light, dark }` pair — fully manual, no automatic
|
|
226
|
+
* shade-shifting or other assumption about what a "dark variant" should
|
|
227
|
+
* be; see `resolveEntry`'s own doc comment for how a `{ light, dark }`
|
|
228
|
+
* entry resolves on this platform specifically.
|
|
229
|
+
*
|
|
230
|
+
* ```ts
|
|
231
|
+
* const colors = useColors();
|
|
232
|
+
* colors.blue[6]; // "#2563eb" — static, same value in both modes
|
|
233
|
+
* colors.blue['6/50']; // same, at 50% opacity
|
|
234
|
+
* colors.brand; // resolves per isDark if defined as { light, dark }
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
declare function useColors(): ColorsAPI;
|
|
238
|
+
|
|
239
|
+
export { type ColorEntry as C, type KbachConfig as K, type ThemeConfig as T, type ColorScale as a, type ColorsAPI as b, type ContainerConfig as c, type ThemeMode as d, ThemeProvider as e, type ThemeProviderProps as f, type ThemeState as g, applyKbachConfig as h, defaultTheme as i, getGlobalDarkMode as j, getGlobalThemeMode as k, getTheme as l, setTheme as m, subscribeGlobalDarkMode as n, useTheme as o, resolveKbachConfig as r, setGlobalThemeMode as s, toggleGlobalDarkMode as t, useColors as u };
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A plain hex value (static — same in both modes), or a `{ light, dark }`
|
|
6
|
+
* pair a caller sets manually in their own theme. Fully opt-in per color:
|
|
7
|
+
* nothing here picks a "dark variant" automatically. Only `useColors()`
|
|
8
|
+
* (a JS-value read, not a `className`) understands this shape — utility
|
|
9
|
+
* CLASS resolution (`bg-blue-6`) on native/Expo Go stays plain-string-only,
|
|
10
|
+
* same as it always was (dark mode for a CLASS is the separate `dark:`
|
|
11
|
+
* modifier, gated live at resolve time — see `resolve_style.rs`); a
|
|
12
|
+
* mode-aware entry referenced directly by a class name just doesn't
|
|
13
|
+
* resolve there, mirroring `resolvers/color.rs`'s own `lookup_hex`
|
|
14
|
+
* (`ColorValue::Plain` only, `ModeAware` -> `None`) exactly, same as it
|
|
15
|
+
* would on the web engine too.
|
|
16
|
+
*/
|
|
17
|
+
type ColorEntry = string | {
|
|
18
|
+
light: string;
|
|
19
|
+
dark: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Real Tailwind's `theme.container` config, mirroring
|
|
23
|
+
* `packages/core-engine/src/theme.rs`'s `ContainerConfig` exactly. `center`/
|
|
24
|
+
* `padding` DO reach native now (unlike the max-width breakpoint ladder,
|
|
25
|
+
* which is a `css.rs`-only concept with nothing to generate on native at
|
|
26
|
+
* all) — `resolvers::layout::resolve`'s `"container"` arm is shared by both
|
|
27
|
+
* dispatchers, and `margin`/`padding` are ordinary RN style properties.
|
|
28
|
+
*/
|
|
29
|
+
interface ContainerConfig {
|
|
30
|
+
center?: boolean;
|
|
31
|
+
padding?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ThemeConfig {
|
|
34
|
+
colors: Record<string, ColorEntry>;
|
|
35
|
+
spacing: Record<string, number>;
|
|
36
|
+
screens: Record<string, number>;
|
|
37
|
+
/**
|
|
38
|
+
* e.g. "sans" -> "Inter, sans-serif". Optional, matching the Rust
|
|
39
|
+
* engine's own `#[serde(default)]`. Write it as a real web-shaped
|
|
40
|
+
* fallback stack even on this native-first package — native's own
|
|
41
|
+
* resolver strips it down to a single bare name at resolve time (RN's
|
|
42
|
+
* `fontFamily` prop can't take a fallback list — see
|
|
43
|
+
* `resolvers/mod.rs`'s `first_font_name`), so the SAME config still
|
|
44
|
+
* works unmodified on Expo Web, which needs the full stack.
|
|
45
|
+
*/
|
|
46
|
+
fontFamily?: Record<string, string>;
|
|
47
|
+
darkMode: 'attribute' | 'class' | 'media';
|
|
48
|
+
/** Optional, matching `ContainerConfig`'s own `#[serde(default)]` on the Rust side. */
|
|
49
|
+
container?: ContainerConfig;
|
|
50
|
+
}
|
|
51
|
+
declare const defaultTheme: ThemeConfig;
|
|
52
|
+
declare function setTheme(theme: ThemeConfig): void;
|
|
53
|
+
declare function getTheme(): ThemeConfig;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A `kbach.config.js`-style theme customization — same shape as
|
|
57
|
+
* @kbach/react's `KbachConfig`, minus mode-aware `{ light, dark }` color
|
|
58
|
+
* entries in `extend.colors` specifically: aliasing/opacity math here only
|
|
59
|
+
* ever operates on plain hex (`brand: 'blue-6'`, `brandSoft: 'brand/30'`),
|
|
60
|
+
* matching `jsEngine/resolvers/color.ts`'s `lookupHex`'s own Plain-only
|
|
61
|
+
* convention — a reference to an EXISTING color that happens to be
|
|
62
|
+
* mode-aware (`theme.colors` can hold those now, for `useColors()`; see
|
|
63
|
+
* `theme.ts`'s own `ColorEntry` doc comment) just doesn't resolve as an
|
|
64
|
+
* alias, falling through to "literal CSS value" the same way an unknown
|
|
65
|
+
* name would. A config object goes in, a resolved `ThemeConfig` comes out,
|
|
66
|
+
* via one PURE function (`resolveKbachConfig`) — no plugins, no
|
|
67
|
+
* live-update store, nothing to keep "in sync."
|
|
68
|
+
*/
|
|
69
|
+
interface KbachConfig {
|
|
70
|
+
darkMode?: ThemeConfig['darkMode'];
|
|
71
|
+
/**
|
|
72
|
+
* REPLACES the named section of `defaultTheme` entirely — use `extend` to
|
|
73
|
+
* add instead of replace. `container` isn't here (only under `extend`
|
|
74
|
+
* below) — see @kbach/react's identical `KbachConfig.theme` doc comment
|
|
75
|
+
* for why.
|
|
76
|
+
*/
|
|
77
|
+
theme?: Partial<Pick<ThemeConfig, 'colors' | 'spacing' | 'screens' | 'fontFamily'>>;
|
|
78
|
+
/** Merges INTO `defaultTheme` (or `theme` above, if also supplied) — the common case. */
|
|
79
|
+
extend?: {
|
|
80
|
+
/**
|
|
81
|
+
* A value is either a literal hex color, or a REFERENCE to another
|
|
82
|
+
* color name, optionally with an opacity suffix. Two things can be
|
|
83
|
+
* referenced: a BUILT-IN palette color, always available regardless of
|
|
84
|
+
* ordering (`accent: 'orange-5'`, `accentSoft: 'orange-5/50'`), or a
|
|
85
|
+
* custom color defined EARLIER in this same object (`{ brand:
|
|
86
|
+
* '#ff6b35', brandSoft: 'brand/30' }` — `brandSoft` resolves to
|
|
87
|
+
* `brand`'s own value at 30% opacity). A string that doesn't match any
|
|
88
|
+
* known color name is treated as a literal CSS value instead.
|
|
89
|
+
*/
|
|
90
|
+
colors?: Record<string, string>;
|
|
91
|
+
spacing?: Record<string, number>;
|
|
92
|
+
screens?: Record<string, number>;
|
|
93
|
+
/**
|
|
94
|
+
* Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` —
|
|
95
|
+
* adds to (or overrides one of) `defaultTheme.fontFamily`'s three
|
|
96
|
+
* names. Write it as a real web-shaped fallback stack even though this
|
|
97
|
+
* is the React Native package — native's own resolver strips it down
|
|
98
|
+
* to a single bare name at resolve time (RN's `fontFamily` prop can't
|
|
99
|
+
* take a fallback list), so the SAME config still works unmodified on
|
|
100
|
+
* Expo Web, which needs the full stack.
|
|
101
|
+
*/
|
|
102
|
+
fontFamily?: Record<string, string>;
|
|
103
|
+
/** `{ center: true, padding: '2rem' }` — see `ContainerConfig`'s own doc comment. */
|
|
104
|
+
container?: ContainerConfig;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
|
|
108
|
+
declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
|
|
109
|
+
/** `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point from app code. */
|
|
110
|
+
declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Native port of @kbach/react's darkModeStore.ts — same architecture (a
|
|
114
|
+
* single module-level store is the real source of truth;
|
|
115
|
+
* ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
|
|
116
|
+
* function here works standalone with no provider mounted anywhere), with
|
|
117
|
+
* two real platform differences from the web version:
|
|
118
|
+
*
|
|
119
|
+
* 1. No persistence. There's no React-Native-builtin equivalent of
|
|
120
|
+
* localStorage — adding one would mean forcing a real native dependency
|
|
121
|
+
* (e.g. @react-native-async-storage/async-storage) on every consumer,
|
|
122
|
+
* even ones that don't want it. The mode always starts at 'system' on a
|
|
123
|
+
* fresh app launch. If you want persistence, read your own storage at
|
|
124
|
+
* startup (before your first render) and call setGlobalThemeMode() with
|
|
125
|
+
* the result — same "seed once, no built-in write-back" shape as
|
|
126
|
+
* seedDefaultMode() below, just driven by your own storage instead of
|
|
127
|
+
* one this package picks for you.
|
|
128
|
+
* 2. `applyToDom` below IS needed here, unlike what an earlier version of
|
|
129
|
+
* this file's own comment claimed ("no DOM to apply the resolved mode
|
|
130
|
+
* to") — that reasoning only holds for real native (Android/iOS), where
|
|
131
|
+
* nativeBridge.ts's resolveStyle() reads getGlobalDarkMode() directly on
|
|
132
|
+
* every call, no DOM involved at all. This file is ALSO used by Expo
|
|
133
|
+
* Web, though (no `.web.ts` sibling — Metro has nothing to swap in), and
|
|
134
|
+
* Expo Web resolves `dark:` classes to REAL CSS with `[data-theme=
|
|
135
|
+
* "dark"]`-style selectors (see nativeBridge.web.ts) that only ever
|
|
136
|
+
* match if something writes that attribute to the DOM — nothing did,
|
|
137
|
+
* which is exactly why toggling the mode changed `useTheme()`'s own
|
|
138
|
+
* state (a consumer reading `mode`/`isDark` directly, e.g. for a label,
|
|
139
|
+
* updated correctly) while every `dark:`-prefixed style silently never
|
|
140
|
+
* applied. `typeof document === 'undefined'` is what makes one function
|
|
141
|
+
* safe on both platforms: real native's Hermes has no `document` global
|
|
142
|
+
* at all (not even `undefined` as a defined-but-empty value — genuinely
|
|
143
|
+
* unset, which `typeof` can check without throwing), so this always
|
|
144
|
+
* no-ops there, while Expo Web's react-native-web renders into a real
|
|
145
|
+
* browser DOM where it's needed.
|
|
146
|
+
*/
|
|
147
|
+
type ThemeMode = 'light' | 'dark' | 'system';
|
|
148
|
+
declare function getGlobalDarkMode(): boolean;
|
|
149
|
+
declare function getGlobalThemeMode(): ThemeMode;
|
|
150
|
+
/** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
|
|
151
|
+
declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
152
|
+
declare function toggleGlobalDarkMode(): void;
|
|
153
|
+
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
154
|
+
|
|
155
|
+
interface ThemeState {
|
|
156
|
+
mode: ThemeMode;
|
|
157
|
+
isDark: boolean;
|
|
158
|
+
setMode: (mode: ThemeMode) => void;
|
|
159
|
+
toggle: () => void;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The one hook for reading and writing Kbach's dark-mode state. Reads
|
|
163
|
+
* `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
|
|
164
|
+
* that module-level store, not React Context, is the actual source of
|
|
165
|
+
* truth (every provider used to just mirror it into a Context value), so
|
|
166
|
+
* this works from ANY component, with or without a `<ThemeProvider>`
|
|
167
|
+
* mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
|
|
168
|
+
* as an optional convenience for seeding an app's initial `defaultMode` —
|
|
169
|
+
* see its own doc comment. No server-snapshot argument (unlike
|
|
170
|
+
* @kbach/react's web version) — React Native has no SSR concept at all,
|
|
171
|
+
* so there's no third argument for `useSyncExternalStore` to need.
|
|
172
|
+
*/
|
|
173
|
+
declare function useTheme(): ThemeState;
|
|
174
|
+
|
|
175
|
+
interface ThemeProviderProps {
|
|
176
|
+
children: ReactNode;
|
|
177
|
+
/**
|
|
178
|
+
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
179
|
+
* made an explicit mode choice yet (see
|
|
180
|
+
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
181
|
+
* wins over a component's own default. Unlike @kbach/react's web
|
|
182
|
+
* version, there's no persisted value to check here — see
|
|
183
|
+
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
184
|
+
* the mode across app launches.
|
|
185
|
+
*/
|
|
186
|
+
defaultMode?: ThemeMode;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
|
|
190
|
+
* and works anywhere without this mounted at all. The only thing this
|
|
191
|
+
* component does is seed that store's initial `defaultMode` once, for apps
|
|
192
|
+
* that want to declare a startup default in JSX rather than calling
|
|
193
|
+
* `setGlobalThemeMode()` imperatively before their first render. It
|
|
194
|
+
* renders `children` as-is — no Context.Provider, since there's no value
|
|
195
|
+
* left to distribute; `useTheme()` never reads from Context.
|
|
196
|
+
*
|
|
197
|
+
* Because the store is global, MULTIPLE mounted providers would each try
|
|
198
|
+
* to seed the exact same store rather than getting their own scoped
|
|
199
|
+
* state — mounting more than one is very likely a mistake, so this warns
|
|
200
|
+
* once in dev.
|
|
201
|
+
*/
|
|
202
|
+
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
203
|
+
|
|
204
|
+
interface ColorScale {
|
|
205
|
+
/** `colors.blue[6]` -> raw value */
|
|
206
|
+
readonly [shade: number]: string;
|
|
207
|
+
/** `colors.blue['6/50']` -> shade 6 at 50% opacity */
|
|
208
|
+
readonly [key: string]: string;
|
|
209
|
+
}
|
|
210
|
+
type ColorsAPI = {
|
|
211
|
+
/** `colors.white`, `colors.blue` (a shade family), ... */
|
|
212
|
+
readonly [name: string]: string | ColorScale;
|
|
213
|
+
} & {
|
|
214
|
+
/** Applies an opacity (0–100) to any color string this hook can return. */
|
|
215
|
+
readonly alpha: (color: string, opacity?: number) => string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Resolved theme colors as real values — for anywhere you need an actual
|
|
219
|
+
* color (a chart library prop, a raw SVG `fill`), not a `className`. Ported
|
|
220
|
+
* from old-kbach's `useColors()`, adapted to this engine's flat
|
|
221
|
+
* `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
|
|
222
|
+
*
|
|
223
|
+
* Any color — a shade-family entry like `"blue-6"` or a standalone name —
|
|
224
|
+
* can be defined in the theme as either a plain string (static, same in
|
|
225
|
+
* both modes) or a `{ light, dark }` pair — fully manual, no automatic
|
|
226
|
+
* shade-shifting or other assumption about what a "dark variant" should
|
|
227
|
+
* be; see `resolveEntry`'s own doc comment for how a `{ light, dark }`
|
|
228
|
+
* entry resolves on this platform specifically.
|
|
229
|
+
*
|
|
230
|
+
* ```ts
|
|
231
|
+
* const colors = useColors();
|
|
232
|
+
* colors.blue[6]; // "#2563eb" — static, same value in both modes
|
|
233
|
+
* colors.blue['6/50']; // same, at 50% opacity
|
|
234
|
+
* colors.brand; // resolves per isDark if defined as { light, dark }
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
declare function useColors(): ColorsAPI;
|
|
238
|
+
|
|
239
|
+
export { type ColorEntry as C, type KbachConfig as K, type ThemeConfig as T, type ColorScale as a, type ColorsAPI as b, type ContainerConfig as c, type ThemeMode as d, ThemeProvider as e, type ThemeProviderProps as f, type ThemeState as g, applyKbachConfig as h, defaultTheme as i, getGlobalDarkMode as j, getGlobalThemeMode as k, getTheme as l, setTheme as m, subscribeGlobalDarkMode as n, useTheme as o, resolveKbachConfig as r, setGlobalThemeMode as s, toggleGlobalDarkMode as t, useColors as u };
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kbach/react-native",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "Kbach's utility-class styling engine for React Native — the same class names as @kbach/react, resolved to a StyleSheet-ready style object via a Kotlin/JNI TurboModule.
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
|
+
"description": "Kbach's utility-class styling engine for React Native — the same class names as @kbach/react, resolved to a StyleSheet-ready style object via a Kotlin/JNI TurboModule (native), the same WASM engine @kbach/react uses (Expo Web / react-native-web), or a pure-JS fallback engine (Expo Go). Ships its own native Android module — one npm install, no separate native package. Beta. Native is Android-only (no iOS bridge yet); works with the React Native CLI, an Expo dev client, AND Expo Go (a reduced-coverage pure-JS fallback, since Expo Go can't load any custom native module).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"css",
|
|
7
|
+
"react-native",
|
|
8
|
+
"expo",
|
|
9
|
+
"tailwind",
|
|
10
|
+
"utility-first",
|
|
11
|
+
"styling",
|
|
12
|
+
"android",
|
|
13
|
+
"web",
|
|
14
|
+
"wasm"
|
|
15
|
+
],
|
|
6
16
|
"license": "MIT",
|
|
7
17
|
"author": "semsakadanupol",
|
|
8
18
|
"repository": {
|
|
@@ -17,7 +27,14 @@
|
|
|
17
27
|
"publishConfig": {
|
|
18
28
|
"access": "public"
|
|
19
29
|
},
|
|
20
|
-
"files": [
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src/babel-plugin.js",
|
|
33
|
+
"android/build.gradle",
|
|
34
|
+
"android/src",
|
|
35
|
+
"specs",
|
|
36
|
+
"react-native.config.js"
|
|
37
|
+
],
|
|
21
38
|
"source": "./src/index.ts",
|
|
22
39
|
"main": "./dist/index.js",
|
|
23
40
|
"module": "./dist/index.mjs",
|
|
@@ -26,18 +43,42 @@
|
|
|
26
43
|
".": {
|
|
27
44
|
"types": "./dist/index.d.ts",
|
|
28
45
|
"source": "./src/index.ts",
|
|
46
|
+
"browser": {
|
|
47
|
+
"import": "./dist/index.web.mjs",
|
|
48
|
+
"require": "./dist/index.web.js"
|
|
49
|
+
},
|
|
50
|
+
"node": {
|
|
51
|
+
"import": "./dist/index.web.mjs",
|
|
52
|
+
"require": "./dist/index.web.js"
|
|
53
|
+
},
|
|
29
54
|
"import": "./dist/index.mjs",
|
|
30
55
|
"require": "./dist/index.js"
|
|
31
56
|
},
|
|
32
57
|
"./jsx-runtime": {
|
|
33
58
|
"types": "./dist/jsx-runtime.d.ts",
|
|
34
59
|
"source": "./src/jsx-runtime.tsx",
|
|
60
|
+
"browser": {
|
|
61
|
+
"import": "./dist/jsx-runtime.web.mjs",
|
|
62
|
+
"require": "./dist/jsx-runtime.web.js"
|
|
63
|
+
},
|
|
64
|
+
"node": {
|
|
65
|
+
"import": "./dist/jsx-runtime.web.mjs",
|
|
66
|
+
"require": "./dist/jsx-runtime.web.js"
|
|
67
|
+
},
|
|
35
68
|
"import": "./dist/jsx-runtime.mjs",
|
|
36
69
|
"require": "./dist/jsx-runtime.js"
|
|
37
70
|
},
|
|
38
71
|
"./jsx-dev-runtime": {
|
|
39
72
|
"types": "./dist/jsx-dev-runtime.d.ts",
|
|
40
73
|
"source": "./src/jsx-dev-runtime.tsx",
|
|
74
|
+
"browser": {
|
|
75
|
+
"import": "./dist/jsx-dev-runtime.web.mjs",
|
|
76
|
+
"require": "./dist/jsx-dev-runtime.web.js"
|
|
77
|
+
},
|
|
78
|
+
"node": {
|
|
79
|
+
"import": "./dist/jsx-dev-runtime.web.mjs",
|
|
80
|
+
"require": "./dist/jsx-dev-runtime.web.js"
|
|
81
|
+
},
|
|
41
82
|
"import": "./dist/jsx-dev-runtime.mjs",
|
|
42
83
|
"require": "./dist/jsx-dev-runtime.js"
|
|
43
84
|
},
|
|
@@ -48,14 +89,14 @@
|
|
|
48
89
|
"dev": "tsup --watch",
|
|
49
90
|
"test": "vitest run --passWithNoTests",
|
|
50
91
|
"lint": "tsc --noEmit",
|
|
51
|
-
"generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"@kbach/android": "1.0.0-beta.0"
|
|
92
|
+
"generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts",
|
|
93
|
+
"generate:wasm-base64": "node ../core-engine/scripts/generate-wasm-base64.mjs src/wasmBinary.generated.ts",
|
|
94
|
+
"generate:web-glue": "node ../core-engine/scripts/generate-web-glue.mjs src/wasmGlue.generated.ts"
|
|
55
95
|
},
|
|
56
96
|
"devDependencies": {
|
|
57
97
|
"@types/react": "^19.2.0",
|
|
58
98
|
"@types/react-test-renderer": "^19.1.0",
|
|
99
|
+
"jsdom": "^30.0.1",
|
|
59
100
|
"react-native": "^0.87.0",
|
|
60
101
|
"react-test-renderer": "^19.2.0",
|
|
61
102
|
"tsup": "^8.0.0",
|
|
@@ -65,5 +106,13 @@
|
|
|
65
106
|
"peerDependencies": {
|
|
66
107
|
"react": ">=18.0.0",
|
|
67
108
|
"react-native": ">=0.70.0"
|
|
109
|
+
},
|
|
110
|
+
"codegenConfig": {
|
|
111
|
+
"name": "KbachSpec",
|
|
112
|
+
"type": "modules",
|
|
113
|
+
"jsSrcsDir": "specs",
|
|
114
|
+
"android": {
|
|
115
|
+
"javaPackageName": "com.kbachnative"
|
|
116
|
+
}
|
|
68
117
|
}
|
|
69
118
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit override for React Native autolinking's Android package
|
|
3
|
+
* detection, which otherwise guesses `<packageName>.<PackageClassName>`
|
|
4
|
+
* from either AndroidManifest.xml's `package` attribute or (when absent,
|
|
5
|
+
* our case) build.gradle's `namespace` — see cli-config-android's
|
|
6
|
+
* getPackageName. That guess is wrong here on purpose: KbachModule.kt/
|
|
7
|
+
* KbachPackage.kt declare `package com.kbachnative` (chosen to match the
|
|
8
|
+
* JNI extern symbol names in packages/core-engine/src/jni_bridge.rs), but
|
|
9
|
+
* this module's Gradle `namespace` has to be a DIFFERENT value
|
|
10
|
+
* (com.kbachnative.nativemodule, see android/build.gradle's own comment)
|
|
11
|
+
* since AGP requires unique namespaces per module and the consuming app
|
|
12
|
+
* itself already claims "com.kbachnative" as its own. Without this
|
|
13
|
+
* override, the generated PackageList.java imports the non-existent
|
|
14
|
+
* `com.kbachnative.nativemodule.KbachPackage` and the app fails to
|
|
15
|
+
* compile. (A `package="com.kbachnative"` attribute in AndroidManifest.xml
|
|
16
|
+
* was tried first instead — simpler on paper — but AGP 9+ hard-errors on a
|
|
17
|
+
* manifest package that doesn't match the namespace, so this explicit
|
|
18
|
+
* config override is the only working fix.)
|
|
19
|
+
*/
|
|
20
|
+
module.exports = {
|
|
21
|
+
dependency: {
|
|
22
|
+
platforms: {
|
|
23
|
+
android: {
|
|
24
|
+
packageImportPath: 'import com.kbachnative.KbachPackage;',
|
|
25
|
+
packageInstance: 'new KbachPackage()',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurboModule spec for KbachModule — RN's Codegen convention requires this
|
|
3
|
+
* exact `Native<Name>.ts` filename and a default-exported
|
|
4
|
+
* `TurboModuleRegistry.getEnforcing<Spec>(...)`. Read by Codegen (see this
|
|
5
|
+
* package's own `codegenConfig` in package.json) to generate the native
|
|
6
|
+
* glue that ../android/src/main/java/com/kbachnative/KbachModule.kt
|
|
7
|
+
* implements. Lives in this same package (not the consuming app) alongside
|
|
8
|
+
* the native Android code it describes — Android autolinking only
|
|
9
|
+
* discovers native modules from npm dependencies with their own `android/`
|
|
10
|
+
* folder, not from native code declared directly inside an app, which is
|
|
11
|
+
* why `@kbach/react-native` ships its own `android/` + `specs/` at all
|
|
12
|
+
* (see Phase 13) rather than relying on the consuming app to provide one.
|
|
13
|
+
*
|
|
14
|
+
* Method names have no `Sync` suffix (unlike the pre-Phase-13 bridge's
|
|
15
|
+
* `generateCssSync`/`resolveStyleSync`) — every TurboModule call is
|
|
16
|
+
* synchronous by nature via JSI, so the suffix's whole point (flagging
|
|
17
|
+
* "this one blocks, unlike normal bridge methods") no longer applies.
|
|
18
|
+
*/
|
|
19
|
+
import type { TurboModule } from 'react-native';
|
|
20
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
21
|
+
|
|
22
|
+
export interface Spec extends TurboModule {
|
|
23
|
+
generateCss(classString: string, themeJson: string): string;
|
|
24
|
+
// `width`: a plain `number` (not the `Int32`/`Float` CodegenTypes
|
|
25
|
+
// aliases) is Codegen's own default float-numeric mapping — it already
|
|
26
|
+
// compiles to Kotlin's `Double` on the native side, matching
|
|
27
|
+
// KbachModule.kt's `width: Double` parameter with no extra type alias.
|
|
28
|
+
resolveStyle(classString: string, themeJson: string, colorScheme: string, pressed: boolean, width: number): string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('KbachModule');
|