@kbach/react-native 1.0.0-beta.2 → 1.0.0-beta.3

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
@@ -16,13 +16,13 @@ resolved to a real `StyleSheet`-ready style object instead of injected CSS.
16
16
  npm install @kbach/react-native
17
17
  ```
18
18
 
19
- That pulls in [`@kbach/android`](https://www.npmjs.com/package/@kbach/android)
20
- too — the actual native module — as a real dependency. Android's
21
- autolinking discovers it automatically the moment it's in `node_modules`;
22
- no manual `MainApplication.kt` edit needed. Works the same way whether
23
- your app was created with the React Native CLI or with Expo — **except
24
- Expo Go**, which can never load this (or any) custom native module; see
25
- below.
19
+ One package, nothing else to install — the Kotlin TurboModule and its
20
+ prebuilt native `.so` ship inside this same package's own `android/`
21
+ folder. Android's autolinking discovers it automatically the moment it's
22
+ in `node_modules`; no manual `MainApplication.kt` edit needed. Works the
23
+ same way whether your app was created with the React Native CLI or with
24
+ Expo — **except Expo Go**, which can never load this (or any) custom
25
+ native module; see below.
26
26
 
27
27
  ### React Native CLI
28
28
 
@@ -49,8 +49,8 @@ from npm).
49
49
  ### Expo
50
50
 
51
51
  **Expo Go doesn't work** — it's a fixed pre-built binary with no way to
52
- load a custom native module like `@kbach/android`. You need a **dev
53
- client** instead (a custom build of your app with this module baked in) —
52
+ load a custom native module like this one. You need a **dev client**
53
+ instead (a custom build of your app with this module baked in) —
54
54
  functionally the same "real native project" setup as the CLI path above,
55
55
  just generated and driven through Expo's own tooling:
56
56
 
@@ -68,7 +68,7 @@ npx expo run:android # builds and installs a dev client with the native module i
68
68
  ```
69
69
 
70
70
  Also requires the New Architecture enabled (Expo's default in current SDK
71
- versions) — `@kbach/android` is a TurboModule, which doesn't exist under
71
+ versions) — the native module is a TurboModule, which doesn't exist under
72
72
  the legacy bridge at all. If you're on an older SDK/config with the New
73
73
  Architecture off, turn it on in `app.json`
74
74
  (`"newArchEnabled": true` under `expo`) before prebuilding.
@@ -88,14 +88,22 @@ extra configuration beyond the two steps above.
88
88
  scale — the same `ThemeConfig` shape `@kbach/react` uses, kept as an
89
89
  independent type here so a native-only project isn't forced to depend on
90
90
  a web-oriented package.
91
- - `shadow-*` resolves to real native shadows — iOS's discrete
92
- `shadowColor`/`shadowOffset`/`shadowOpacity`/`shadowRadius` and Android's
93
- `elevation`, both derived from the same named tiers (`sm`/`DEFAULT`/`md`/
94
- `lg`/`xl`/`2xl`) `@kbach/react`'s CSS `box-shadow` scale uses — one class
95
- produces a real shadow on both platforms. `shadow-inner` and arbitrary
96
- `shadow-[...]` values aren't supported — neither platform's native shadow
97
- API can express an inset shadow, and an arbitrary value is a raw CSS
91
+ - `shadow-*` resolves to real native shadows — RN's discrete
92
+ `shadowColor`/`shadowOffset`/`shadowOpacity`/`shadowRadius` props plus
93
+ Android's `elevation` (both emitted together; `elevation` is what
94
+ actually renders a shadow on Android — the `shadow*` props are iOS-only
95
+ in RN itself, ready for whenever this package gets an iOS bridge),
96
+ derived from the same named tiers (`sm`/`DEFAULT`/`md`/`lg`/`xl`/`2xl`)
97
+ `@kbach/react`'s CSS `box-shadow` scale uses. `shadow-inner` and
98
+ arbitrary `shadow-[...]` values aren't supported — RN's native shadow
99
+ props can't express an inset shadow, and an arbitrary value is a raw CSS
98
100
  string with no mechanical translation into discrete native props.
101
+ - Responsive breakpoints (`sm:`/`md:`/`lg:`/`xl:`/`2xl:`) — gated live
102
+ against the current window width (`Dimensions.get('window').width`),
103
+ same mobile-first "min-width and up" semantics as web, and freely
104
+ combinable with `dark:`/`active:` (e.g. `dark:md:bg-blue-8`). Call RN's
105
+ own `useWindowDimensions()` somewhere in your tree to re-render on
106
+ rotation/resize — same role `useColorScheme()` plays for `dark:` below.
99
107
 
100
108
  ## Dark mode
101
109
 
@@ -0,0 +1,36 @@
1
+ apply plugin: "com.android.library"
2
+ apply plugin: "org.jetbrains.kotlin.android"
3
+ apply plugin: "com.facebook.react"
4
+
5
+ def getExtOrDefault(name) {
6
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : null
7
+ }
8
+
9
+ android {
10
+ // NOT "com.kbachnative" — that's the app module's own namespace (Phase 5
11
+ // deliberately made the app's package match KbachModule's Kotlin package
12
+ // for simpler JNI symbol names), and AGP requires unique namespaces per
13
+ // module. This is purely a Gradle build-identity value, separate from
14
+ // the actual `package com.kbachnative` declared in the .kt files below —
15
+ // JNI symbol resolution depends on that Kotlin package, not this.
16
+ namespace "com.kbachnative.nativemodule"
17
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
18
+
19
+ defaultConfig {
20
+ minSdkVersion getExtOrDefault("minSdkVersion")
21
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
22
+ }
23
+ }
24
+
25
+ repositories {
26
+ google()
27
+ maven {
28
+ // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm.
29
+ url "$rootDir/../node_modules/react-native/android"
30
+ }
31
+ mavenCentral()
32
+ }
33
+
34
+ dependencies {
35
+ implementation "com.facebook.react:react-native:+"
36
+ }
@@ -0,0 +1,14 @@
1
+ <!--
2
+ Deliberately NO `package="..."` attribute here, even though this
3
+ module's Kotlin package (com.kbachnative, see KbachModule.kt) differs
4
+ from its Gradle build.gradle `namespace` (com.kbachnative.nativemodule)
5
+ — adding one here to help React Native's autolinking guess the right
6
+ Kotlin package (see react-native.config.js at this package's root, which
7
+ fixes that instead) breaks the actual Gradle build on AGP 9+: it now
8
+ hard-errors ("Setting the namespace via the package attribute in the
9
+ source AndroidManifest.xml is no longer supported") whenever the
10
+ manifest's package doesn't match the namespace exactly. If you're
11
+ tempted to add one back to fix an autolinking symbol-resolution error,
12
+ fix react-native.config.js's packageImportPath/packageInstance instead.
13
+ -->
14
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,83 @@
1
+ package com.kbachnative
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+
5
+ /**
6
+ * Bridges to the same Rust core engine every other Kbach platform uses
7
+ * (packages/core-engine) — via JNI here instead of WASM. The native
8
+ * `generateCss`/`resolveStyle` externs below (in the companion object) must
9
+ * match `Java_com_kbachnative_KbachModule_generateCss`/`resolveStyle`'s
10
+ * signatures exactly (packages/core-engine/src/jni_bridge.rs): STATIC
11
+ * native methods (hence @JvmStatic in a companion object, not instance
12
+ * methods — the Rust side takes JClass, not JObject, as its second
13
+ * parameter).
14
+ *
15
+ * TurboModule since Phase 13 (was the legacy bridge's
16
+ * `isBlockingSynchronousMethod` support directly on `ReactContextBaseJavaModule`
17
+ * before) — extends the Codegen-generated `NativeKbachModuleSpec`
18
+ * (packages/react-native/specs/NativeKbachModule.ts -> this package's own
19
+ * codegenConfig in package.json, discovered via autolinking ->
20
+ * android/build/generated/source/codegen/java/com/kbachnative/NativeKbachModuleSpec.java),
21
+ * which already provides `getName()` and declares `generateCss`/`resolveStyle`
22
+ * as abstract `@ReactMethod(isBlockingSynchronousMethod = true)` methods —
23
+ * Codegen still routes a synchronously-returning TurboModule method through
24
+ * that same Android bridge mechanism; the real gain here is JSI-backed
25
+ * dispatch + build-time type checking against the JS spec, not the removal
26
+ * of `isBlockingSynchronousMethod` as a concept.
27
+ *
28
+ * The companion's JNI externs are named `nativeGenerateCss`/`nativeResolveStyle`
29
+ * — NOT the same names as the spec's `generateCss`/`resolveStyle` overrides.
30
+ * Tried keeping identical names first (disambiguated via an explicit
31
+ * `Companion.generateCss(...)` qualifier) since that would've meant zero
32
+ * Rust changes, but that fails to compile: `@JvmStatic` duplicates each
33
+ * companion function directly onto the outer class for Java-interop
34
+ * purposes, which collides at the JVM bytecode level with the identically-
35
+ * named, identically-shaped instance override ("platform declaration
36
+ * clash"), regardless of how the Kotlin-source call site is qualified. The
37
+ * rename requires matching `Java_com_kbachnative_KbachModule_nativeGenerateCss`/
38
+ * `nativeResolveStyle` `#[no_mangle]` export names in
39
+ * packages/core-engine/src/jni_bridge.rs — JNI's implicit symbol linking
40
+ * binds by the extern's own method name, not by whatever calls it.
41
+ *
42
+ * Moved here from apps/native-sandbox/android/app in Phase 13 — Android
43
+ * autolinking only discovers native modules from npm dependencies with
44
+ * their own `android/` folder, never from native code declared directly
45
+ * inside the app itself, so `KbachModule` had to become a real (locally
46
+ * `file:`-linked) package to be found by `TurboModuleRegistry.getEnforcing`.
47
+ * Originally that "real package" was a separate `@kbach/android` npm
48
+ * package; later folded directly into `@kbach/react-native` (this
49
+ * package's own `android/` folder, sibling to its `src/`) once it became
50
+ * clear the same autolinking requirement is satisfied just as well by a
51
+ * SINGLE npm package containing both JS and native code — the same layout
52
+ * react-native-safe-area-context itself uses — so consumers only ever run
53
+ * one `npm install @kbach/react-native`, not two.
54
+ */
55
+ class KbachModule(reactContext: ReactApplicationContext) : NativeKbachModuleSpec(reactContext) {
56
+
57
+ companion object {
58
+ init {
59
+ System.loadLibrary("kbach_core_engine")
60
+ }
61
+
62
+ @JvmStatic
63
+ external fun nativeGenerateCss(classString: String, themeJson: String): String
64
+
65
+ @JvmStatic
66
+ external fun nativeResolveStyle(classString: String, themeJson: String, colorScheme: String, pressed: Boolean, width: Double): String
67
+ }
68
+
69
+ override fun generateCss(classString: String, themeJson: String): String {
70
+ return nativeGenerateCss(classString, themeJson)
71
+ }
72
+
73
+ /**
74
+ * `colorScheme` ("light"/"dark", from Appearance.getColorScheme() on the
75
+ * JS side) gates whether `dark:` declarations are applied; `pressed`
76
+ * (only ever true when the caller is RN's Pressable) gates `active:`;
77
+ * `width` (the JS side's current `Dimensions.get('window').width`) gates
78
+ * `sm:`/`md:`/`lg:`/`xl:`/`2xl:` against the theme's `screens` scale.
79
+ */
80
+ override fun resolveStyle(classString: String, themeJson: String, colorScheme: String, pressed: Boolean, width: Double): String {
81
+ return nativeResolveStyle(classString, themeJson, colorScheme, pressed, width)
82
+ }
83
+ }
@@ -0,0 +1,54 @@
1
+ package com.kbachnative
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+
10
+ /**
11
+ * `BaseReactPackage` (not plain `ReactPackage`) since Phase 13's TurboModule
12
+ * migration — a plain `ReactPackage`'s `createNativeModules()`-provided
13
+ * modules stay visible to the legacy `NativeModules` interop but are never
14
+ * consulted by `TurboModuleRegistry`'s resolution path, which only walks
15
+ * packages that can answer `getModule(name, ...)` directly. Modeled on
16
+ * react-native-safe-area-context's own `SafeAreaContextPackage.kt` (a real,
17
+ * New-Architecture-compatible package already in this project's
18
+ * dependencies) rather than reflection over a `@ReactModule` annotation —
19
+ * KbachModule doesn't have one, and there's only ever this one module, so
20
+ * building its `ReactModuleInfo` directly is simpler than adding an
21
+ * annotation just to read it back via reflection.
22
+ *
23
+ * Auto-discovered via Android autolinking, not manually registered in
24
+ * apps/native-sandbox's MainApplication.kt — same as SafeAreaContextPackage
25
+ * already was, and the entire reason this module became a real npm package
26
+ * with its own `android/` folder (this one — `@kbach/react-native` itself,
27
+ * see KbachModule.kt's own doc comment) in Phase 13 rather than living
28
+ * inside the app.
29
+ */
30
+ class KbachPackage : BaseReactPackage() {
31
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
32
+ return if (name == NativeKbachModuleSpec.NAME) KbachModule(reactContext) else null
33
+ }
34
+
35
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
36
+ return ReactModuleInfoProvider {
37
+ mapOf(
38
+ NativeKbachModuleSpec.NAME to
39
+ ReactModuleInfo(
40
+ NativeKbachModuleSpec.NAME,
41
+ KbachModule::class.java.name,
42
+ false, // canOverrideExistingModule
43
+ false, // needsEagerInit
44
+ false, // isCxxModule
45
+ true, // isTurboModule
46
+ )
47
+ )
48
+ }
49
+ }
50
+
51
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
52
+ return emptyList()
53
+ }
54
+ }
@@ -289,7 +289,13 @@ var defaultTheme = {
289
289
  "6": 24,
290
290
  "8": 32
291
291
  },
292
- screens: {},
292
+ screens: {
293
+ sm: 640,
294
+ md: 768,
295
+ lg: 1024,
296
+ xl: 1280,
297
+ "2xl": 1536
298
+ },
293
299
  darkMode: "attribute"
294
300
  };
295
301
  var activeTheme = defaultTheme;
@@ -352,11 +358,12 @@ function seedDefaultMode(fallback) {
352
358
  }
353
359
 
354
360
  // src/nativeBridge.ts
355
- import { TurboModuleRegistry } from "react-native";
361
+ import { Dimensions, TurboModuleRegistry } from "react-native";
356
362
  function resolveStyle(classString, pressed = false) {
357
363
  const KbachModule = TurboModuleRegistry.getEnforcing("KbachModule");
358
364
  const colorScheme = getGlobalDarkMode() ? "dark" : "light";
359
- const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed);
365
+ const width = Dimensions.get("window").width;
366
+ const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
360
367
  return JSON.parse(json);
361
368
  }
362
369
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveStyle
3
- } from "./chunk-YHF3QLRL.mjs";
3
+ } from "./chunk-7KUNKZBM.mjs";
4
4
 
5
5
  // src/jsx-runtime.tsx
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
package/dist/index.d.mts CHANGED
@@ -59,6 +59,19 @@ interface StyleObject {
59
59
  * `Pressable` ever knows this, so it's passed in directly by the caller
60
60
  * (jsx-runtime, from Pressable's own style-function callback) rather than
61
61
  * queried here. Defaults to `false` for every other element type.
62
+ *
63
+ * `width` gates `sm:`/`md:`/`lg:`/`xl:`/`2xl:` against the active theme's
64
+ * `screens` scale (resolve_style.rs's `native_modifier_state`) — read fresh
65
+ * from `Dimensions.get('window').width` on every call, the same "no
66
+ * caching, no store" choice `colorScheme`/`pressed` already make, and for
67
+ * the same reason: unlike dark mode there's no explicit-override concept to
68
+ * preserve here (nobody manually overrides their screen width), so a plain
69
+ * synchronous read is enough — no need for the subscribable store
70
+ * darkModeStore.ts is for. As with `colorScheme`, this function has no
71
+ * subscription of its own, so rotating/resizing the window doesn't
72
+ * re-render anything by itself — call RN's own `useWindowDimensions()`
73
+ * somewhere in the tree for that (same role `useColorScheme()` plays for
74
+ * `dark:` in apps/native-sandbox/App.tsx).
62
75
  */
63
76
  declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
64
77
 
package/dist/index.d.ts CHANGED
@@ -59,6 +59,19 @@ interface StyleObject {
59
59
  * `Pressable` ever knows this, so it's passed in directly by the caller
60
60
  * (jsx-runtime, from Pressable's own style-function callback) rather than
61
61
  * queried here. Defaults to `false` for every other element type.
62
+ *
63
+ * `width` gates `sm:`/`md:`/`lg:`/`xl:`/`2xl:` against the active theme's
64
+ * `screens` scale (resolve_style.rs's `native_modifier_state`) — read fresh
65
+ * from `Dimensions.get('window').width` on every call, the same "no
66
+ * caching, no store" choice `colorScheme`/`pressed` already make, and for
67
+ * the same reason: unlike dark mode there's no explicit-override concept to
68
+ * preserve here (nobody manually overrides their screen width), so a plain
69
+ * synchronous read is enough — no need for the subscribable store
70
+ * darkModeStore.ts is for. As with `colorScheme`, this function has no
71
+ * subscription of its own, so rotating/resizing the window doesn't
72
+ * re-render anything by itself — call RN's own `useWindowDimensions()`
73
+ * somewhere in the tree for that (same role `useColorScheme()` plays for
74
+ * `dark:` in apps/native-sandbox/App.tsx).
62
75
  */
63
76
  declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
64
77
 
package/dist/index.js CHANGED
@@ -326,7 +326,13 @@ var defaultTheme = {
326
326
  "6": 24,
327
327
  "8": 32
328
328
  },
329
- screens: {},
329
+ screens: {
330
+ sm: 640,
331
+ md: 768,
332
+ lg: 1024,
333
+ xl: 1280,
334
+ "2xl": 1536
335
+ },
330
336
  darkMode: "attribute"
331
337
  };
332
338
  var activeTheme = defaultTheme;
@@ -395,7 +401,8 @@ function seedDefaultMode(fallback) {
395
401
  function resolveStyle(classString, pressed = false) {
396
402
  const KbachModule = import_react_native2.TurboModuleRegistry.getEnforcing("KbachModule");
397
403
  const colorScheme = getGlobalDarkMode() ? "dark" : "light";
398
- const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed);
404
+ const width = import_react_native2.Dimensions.get("window").width;
405
+ const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
399
406
  return JSON.parse(json);
400
407
  }
401
408
 
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  setTheme,
10
10
  subscribeGlobalDarkMode,
11
11
  toggleGlobalDarkMode
12
- } from "./chunk-YHF3QLRL.mjs";
12
+ } from "./chunk-7KUNKZBM.mjs";
13
13
 
14
14
  // src/useGlobalDarkMode.ts
15
15
  import { useSyncExternalStore } from "react";
@@ -324,7 +324,13 @@ var defaultTheme = {
324
324
  "6": 24,
325
325
  "8": 32
326
326
  },
327
- screens: {},
327
+ screens: {
328
+ sm: 640,
329
+ md: 768,
330
+ lg: 1024,
331
+ xl: 1280,
332
+ "2xl": 1536
333
+ },
328
334
  darkMode: "attribute"
329
335
  };
330
336
  var activeThemeJson = JSON.stringify(defaultTheme);
@@ -359,7 +365,8 @@ function getGlobalDarkMode() {
359
365
  function resolveStyle(classString, pressed = false) {
360
366
  const KbachModule = import_react_native2.TurboModuleRegistry.getEnforcing("KbachModule");
361
367
  const colorScheme = getGlobalDarkMode() ? "dark" : "light";
362
- const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed);
368
+ const width = import_react_native2.Dimensions.get("window").width;
369
+ const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
363
370
  return JSON.parse(json);
364
371
  }
365
372
 
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-SVMYVXY6.mjs";
6
- import "./chunk-YHF3QLRL.mjs";
5
+ } from "./chunk-UP5ZN7CN.mjs";
6
+ import "./chunk-7KUNKZBM.mjs";
7
7
 
8
8
  // src/jsx-dev-runtime.tsx
9
9
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -7,10 +7,13 @@ export { Fragment, JSX } from 'react';
7
7
  * Drop-in replacement for react/jsx-runtime — same interception shape as
8
8
  * @kbach/react/jsx-runtime (Phase 3), but the resolved value becomes the
9
9
  * `style` prop instead of `className`: View/Text have no `className` prop
10
- * at all. Covers layout, color, spacing, border, typography, dark:, and
11
- * active: (Pressable only) — see nativeBridge.ts / resolve_style.rs for
12
- * exactly what's resolved; every other modifier (hover:/responsive/...)
13
- * parses but isn't applied yet.
10
+ * at all. Covers layout, color, spacing, border, typography, dark:,
11
+ * active: (Pressable only), and sm:/md:/lg:/xl:/2xl: (gated against the
12
+ * current window width, live) — see nativeBridge.ts / resolve_style.rs for
13
+ * exactly what's resolved; every other modifier (hover:, group- and peer-
14
+ * variants, has-[...], container queries, ...) parses but isn't applied —
15
+ * there's no selector/pseudo-state/ancestor system on native to apply them
16
+ * with.
14
17
  *
15
18
  * `active:` needs different handling from every other modifier: whether an
16
19
  * element is "pressed" isn't a value that can be read once and reused like
@@ -7,10 +7,13 @@ export { Fragment, JSX } from 'react';
7
7
  * Drop-in replacement for react/jsx-runtime — same interception shape as
8
8
  * @kbach/react/jsx-runtime (Phase 3), but the resolved value becomes the
9
9
  * `style` prop instead of `className`: View/Text have no `className` prop
10
- * at all. Covers layout, color, spacing, border, typography, dark:, and
11
- * active: (Pressable only) — see nativeBridge.ts / resolve_style.rs for
12
- * exactly what's resolved; every other modifier (hover:/responsive/...)
13
- * parses but isn't applied yet.
10
+ * at all. Covers layout, color, spacing, border, typography, dark:,
11
+ * active: (Pressable only), and sm:/md:/lg:/xl:/2xl: (gated against the
12
+ * current window width, live) — see nativeBridge.ts / resolve_style.rs for
13
+ * exactly what's resolved; every other modifier (hover:, group- and peer-
14
+ * variants, has-[...], container queries, ...) parses but isn't applied —
15
+ * there's no selector/pseudo-state/ancestor system on native to apply them
16
+ * with.
14
17
  *
15
18
  * `active:` needs different handling from every other modifier: whether an
16
19
  * element is "pressed" isn't a value that can be read once and reused like
@@ -323,7 +323,13 @@ var defaultTheme = {
323
323
  "6": 24,
324
324
  "8": 32
325
325
  },
326
- screens: {},
326
+ screens: {
327
+ sm: 640,
328
+ md: 768,
329
+ lg: 1024,
330
+ xl: 1280,
331
+ "2xl": 1536
332
+ },
327
333
  darkMode: "attribute"
328
334
  };
329
335
  var activeThemeJson = JSON.stringify(defaultTheme);
@@ -358,7 +364,8 @@ function getGlobalDarkMode() {
358
364
  function resolveStyle(classString, pressed = false) {
359
365
  const KbachModule = import_react_native2.TurboModuleRegistry.getEnforcing("KbachModule");
360
366
  const colorScheme = getGlobalDarkMode() ? "dark" : "light";
361
- const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed);
367
+ const width = import_react_native2.Dimensions.get("window").width;
368
+ const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
362
369
  return JSON.parse(json);
363
370
  }
364
371
 
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-SVMYVXY6.mjs";
6
- import "./chunk-YHF3QLRL.mjs";
5
+ } from "./chunk-UP5ZN7CN.mjs";
6
+ import "./chunk-7KUNKZBM.mjs";
7
7
  export {
8
8
  Fragment,
9
9
  jsx,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kbach/react-native",
3
- "version": "1.0.0-beta.2",
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. Beta, Android-only. Works with the React Native CLI or an Expo dev client (not Expo Go).",
3
+ "version": "1.0.0-beta.3",
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. Ships its own native Android module (Kotlin + prebuilt Rust/JNI .so) — one npm install, no separate native package. Beta, Android-only. Works with the React Native CLI or an Expo dev client (not Expo Go).",
5
5
  "keywords": ["css", "react-native", "expo", "tailwind", "utility-first", "styling", "android"],
6
6
  "license": "MIT",
7
7
  "author": "semsakadanupol",
@@ -17,7 +17,7 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
- "files": ["dist", "src/babel-plugin.js"],
20
+ "files": ["dist", "src/babel-plugin.js", "android/build.gradle", "android/src", "specs", "react-native.config.js"],
21
21
  "source": "./src/index.ts",
22
22
  "main": "./dist/index.js",
23
23
  "module": "./dist/index.mjs",
@@ -50,9 +50,6 @@
50
50
  "lint": "tsc --noEmit",
51
51
  "generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
52
52
  },
53
- "dependencies": {
54
- "@kbach/android": "1.0.0-beta.0"
55
- },
56
53
  "devDependencies": {
57
54
  "@types/react": "^19.2.0",
58
55
  "@types/react-test-renderer": "^19.1.0",
@@ -65,5 +62,13 @@
65
62
  "peerDependencies": {
66
63
  "react": ">=18.0.0",
67
64
  "react-native": ">=0.70.0"
65
+ },
66
+ "codegenConfig": {
67
+ "name": "KbachSpec",
68
+ "type": "modules",
69
+ "jsSrcsDir": "specs",
70
+ "android": {
71
+ "javaPackageName": "com.kbachnative"
72
+ }
68
73
  }
69
74
  }
@@ -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');