@symbiote-native/system-ui 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @symbiote-native/system-ui
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-system-ui`](https://github.com/expo/expo/tree/main/packages/expo-system-ui)
5
+ — setting and reading the root view's background color — usable from **every** adapter,
6
+ React, Vue, and Angular, not just React. Like
7
+ [`@symbiote-native/device`](../device) and [`@symbiote-native/local-auth`](../local-auth) and
8
+ unlike this repo's stateful Expo wrapper ([`@symbiote-native/sensors`](../sensors), an
9
+ `EventEmitter` + live-subscription surface), both exports here are one-shot async calls with no
10
+ per-instance state, so there is no hook/composable/service to wrap — the React, Vue, and
11
+ Angular entry points are plain re-exports of the same `core`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @symbiote-native/system-ui
17
+ ```
18
+
19
+ `expo-system-ui` and `expo-modules-core` come along as regular dependencies, pinned to exact
20
+ versions — never install either yourself, and never add the `expo` meta-package to your project
21
+ (it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
22
+
23
+ ## Required one-time step: native autolinking wiring
24
+
25
+ Unlike a plain RN native module, `expo-system-ui`'s native code is discovered by
26
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
27
+ into the native host app **once**, covering this package and every other `expo-modules-core`
28
+ package with zero further changes:
29
+
30
+ | Platform | Touches |
31
+ |---|---|
32
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
33
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
34
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
35
+ | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
36
+
37
+ Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
38
+ implementation: `examples/expo-react/ios/Podfile` and
39
+ `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
40
+
41
+ `expo-system-ui` needs no runtime permission on either platform — it only sets/reads a stored
42
+ background color, nothing gated by a permission prompt.
43
+
44
+ ## Shape
45
+
46
+ ```
47
+ src/core/ setBackgroundColorAsync / getBackgroundColorAsync. native-module.ts resolves the
48
+ native module via expo-modules-core's requireNativeModule.
49
+ src/react/ @symbiote-native/system-ui/react — export * from '../core'
50
+ src/vue/ @symbiote-native/system-ui/vue — export * from '../core'
51
+ src/angular/ @symbiote-native/system-ui/angular — export * from '../core'
52
+ ```
53
+
54
+ No per-adapter lifecycle wrapper exists because there's nothing to subscribe to or clean up —
55
+ each adapter entry is a single-file re-export.
56
+
57
+ ## Use it
58
+
59
+ ```tsx
60
+ // React
61
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/react';
62
+
63
+ setBackgroundColorAsync('black');
64
+ ```
65
+
66
+ ```vue
67
+ <!-- Vue -->
68
+ <script setup lang="ts">
69
+ import { onMounted } from 'vue';
70
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/vue';
71
+
72
+ onMounted(() => {
73
+ void setBackgroundColorAsync('black');
74
+ });
75
+ </script>
76
+ ```
77
+
78
+ ```ts
79
+ // Angular
80
+ import { Component } from '@angular/core';
81
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/angular';
82
+
83
+ @Component({ standalone: true, template: `` })
84
+ export class RootComponent {
85
+ constructor() {
86
+ void setBackgroundColorAsync('black');
87
+ }
88
+ }
89
+ ```
90
+
91
+ There's no per-instance service to `inject()` in the Angular case — both functions are plain
92
+ exports off the core package, called straight in the constructor or wherever the app's root is
93
+ set up. These snippets are not yet demoed in a real canary screen — no `SystemUiScreen.*` exists
94
+ yet in `examples/expo-*` — so treat them as realistic reference code, not a trimmed-down copy of
95
+ a running demo.
96
+
97
+ ## API
98
+
99
+ Two one-shot async functions, no event stream, no per-instance state — so the React/Vue/Angular
100
+ entry points above are plain re-exports of `core` with nothing adapter-specific to add.
101
+
102
+ ```ts
103
+ setBackgroundColorAsync(color: ColorValue | null): Promise<void>
104
+ getBackgroundColorAsync(): Promise<ColorValue | null>
105
+ ```
106
+
107
+ ```ts
108
+ import { getBackgroundColorAsync, setBackgroundColorAsync } from '@symbiote-native/system-ui';
109
+ // or the framework-scoped entry points — identical surface, re-exported verbatim:
110
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/react';
111
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/vue';
112
+ import { setBackgroundColorAsync } from '@symbiote-native/system-ui/angular';
113
+ ```
114
+
115
+ ## Notes
116
+
117
+ - **`setBackgroundColorAsync(null)` clears the override** rather than setting an actual color —
118
+ it's passed straight through to the native module without running `processColor`.
119
+ - **On web, the raw `ColorValue` is passed through untouched** instead of being run through RN's
120
+ `processColor` — matching upstream, since `processColor` is a native-color-parsing step that
121
+ doesn't apply on that platform.
122
+ - **On iOS/Android, a non-null color is run through RN's own `processColor` before reaching the
123
+ native module** — the same conversion RN's style pipeline uses internally.
124
+
125
+ ## Test it
126
+
127
+ No Fabric/Descriptor angle at all — both exports here are one-shot async calls, never a view or
128
+ per-instance state. Tests inject a fake native-module object in place of the real
129
+ `requireNativeModule` resolution (`src/core/system-ui.test.ts`, `vitest`) — no `installFabric()`,
130
+ no ViewConfig. Native rendering itself is verified on-device (see the parent
131
+ [README](../../README.md) for the project's testing model).
132
+
133
+ Native autolinking wiring for `expo-modules-core` packages is already done in the four Expo
134
+ canary apps (`examples/expo-react`, `examples/expo-vue-sfc`, `examples/expo-vue-tsx`,
135
+ `examples/expo-angular`) via `@symbiote-native/local-auth`/`@symbiote-native/sensors` — this
136
+ package reuses that same wiring with zero further app-side changes, since
137
+ `expo-modules-autolinking` discovers any `expo-modules-core` package already present in
138
+ `node_modules`. A dedicated `SystemUiScreen` demo has not been wired into those canaries yet.
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/system-ui/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export { getBackgroundColorAsync, setBackgroundColorAsync } from './system-ui';
@@ -0,0 +1 @@
1
+ export { getBackgroundColorAsync, setBackgroundColorAsync } from './system-ui';
@@ -0,0 +1,6 @@
1
+ import type { ColorValue, ProcessedColorValue } from 'react-native';
2
+ export type INativeSystemUIModule = {
3
+ setBackgroundColorAsync(color: ColorValue | ProcessedColorValue | null | undefined): Promise<void>;
4
+ getBackgroundColorAsync(): Promise<ColorValue | null>;
5
+ };
6
+ export declare const expoSystemUI: INativeSystemUIModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_SYSTEM_UI_MODULE_NAME = 'ExpoSystemUI';
3
+ export const expoSystemUI = requireNativeModule(EXPO_SYSTEM_UI_MODULE_NAME);
@@ -0,0 +1,22 @@
1
+ import type { ColorValue } from 'react-native';
2
+ /**
3
+ * Changes the root view background color. Call this outside of your component tree, e.g. in
4
+ * the root file, since it affects the whole app.
5
+ * @example
6
+ * ```ts
7
+ * setBackgroundColorAsync("black");
8
+ * ```
9
+ * @param color Any valid [CSS 3 (SVG) color](http://www.w3.org/TR/css3-color/#svg-color), or
10
+ * `null` to clear the override.
11
+ */
12
+ export declare function setBackgroundColorAsync(color: ColorValue | null): Promise<void>;
13
+ /**
14
+ * Gets the root view background color.
15
+ * @example
16
+ * ```ts
17
+ * const color = await getBackgroundColorAsync();
18
+ * ```
19
+ * @returns Current root view background color in hex format. `null` if the background color is
20
+ * not set.
21
+ */
22
+ export declare function getBackgroundColorAsync(): Promise<ColorValue | null>;
@@ -0,0 +1,33 @@
1
+ import { Platform, processColor } from 'react-native';
2
+ import { expoSystemUI } from './native-module';
3
+ /**
4
+ * Changes the root view background color. Call this outside of your component tree, e.g. in
5
+ * the root file, since it affects the whole app.
6
+ * @example
7
+ * ```ts
8
+ * setBackgroundColorAsync("black");
9
+ * ```
10
+ * @param color Any valid [CSS 3 (SVG) color](http://www.w3.org/TR/css3-color/#svg-color), or
11
+ * `null` to clear the override.
12
+ */
13
+ export async function setBackgroundColorAsync(color) {
14
+ if (color == null) {
15
+ return await expoSystemUI.setBackgroundColorAsync(null);
16
+ }
17
+ else {
18
+ const colorNumber = Platform.OS === 'web' ? color : processColor(color);
19
+ return await expoSystemUI.setBackgroundColorAsync(colorNumber);
20
+ }
21
+ }
22
+ /**
23
+ * Gets the root view background color.
24
+ * @example
25
+ * ```ts
26
+ * const color = await getBackgroundColorAsync();
27
+ * ```
28
+ * @returns Current root view background color in hex format. `null` if the background color is
29
+ * not set.
30
+ */
31
+ export async function getBackgroundColorAsync() {
32
+ return await expoSystemUI.getBackgroundColorAsync();
33
+ }
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/system-ui/react: the React entry over the framework-agnostic core. Both
2
+ // exports are plain async functions with no per-instance state and no event stream (unlike
3
+ // @symbiote-native/sensors) — there is nothing for a hook to wrap, so this is a plain
4
+ // re-export, mirroring how @symbiote-native/device's React entry passes its own free
5
+ // functions through untouched.
6
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/system-ui/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/system-ui/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,+FAA+F;AAC/F,yCAAyC;AACzC,cAAc,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export { getBackgroundColorAsync, setBackgroundColorAsync } from './system-ui';
@@ -0,0 +1,2 @@
1
+ export { getBackgroundColorAsync, setBackgroundColorAsync } from './system-ui';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ColorValue, ProcessedColorValue } from 'react-native';
2
+ export type INativeSystemUIModule = {
3
+ setBackgroundColorAsync(color: ColorValue | ProcessedColorValue | null | undefined): Promise<void>;
4
+ getBackgroundColorAsync(): Promise<ColorValue | null>;
5
+ };
6
+ export declare const expoSystemUI: INativeSystemUIModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_SYSTEM_UI_MODULE_NAME = 'ExpoSystemUI';
3
+ export const expoSystemUI = requireNativeModule(EXPO_SYSTEM_UI_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,0BAA0B,GAAG,cAAc,CAAC;AAYlD,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAwB,0BAA0B,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ColorValue } from 'react-native';
2
+ /**
3
+ * Changes the root view background color. Call this outside of your component tree, e.g. in
4
+ * the root file, since it affects the whole app.
5
+ * @example
6
+ * ```ts
7
+ * setBackgroundColorAsync("black");
8
+ * ```
9
+ * @param color Any valid [CSS 3 (SVG) color](http://www.w3.org/TR/css3-color/#svg-color), or
10
+ * `null` to clear the override.
11
+ */
12
+ export declare function setBackgroundColorAsync(color: ColorValue | null): Promise<void>;
13
+ /**
14
+ * Gets the root view background color.
15
+ * @example
16
+ * ```ts
17
+ * const color = await getBackgroundColorAsync();
18
+ * ```
19
+ * @returns Current root view background color in hex format. `null` if the background color is
20
+ * not set.
21
+ */
22
+ export declare function getBackgroundColorAsync(): Promise<ColorValue | null>;
@@ -0,0 +1,34 @@
1
+ import { Platform, processColor } from 'react-native';
2
+ import { expoSystemUI } from './native-module';
3
+ /**
4
+ * Changes the root view background color. Call this outside of your component tree, e.g. in
5
+ * the root file, since it affects the whole app.
6
+ * @example
7
+ * ```ts
8
+ * setBackgroundColorAsync("black");
9
+ * ```
10
+ * @param color Any valid [CSS 3 (SVG) color](http://www.w3.org/TR/css3-color/#svg-color), or
11
+ * `null` to clear the override.
12
+ */
13
+ export async function setBackgroundColorAsync(color) {
14
+ if (color == null) {
15
+ return await expoSystemUI.setBackgroundColorAsync(null);
16
+ }
17
+ else {
18
+ const colorNumber = Platform.OS === 'web' ? color : processColor(color);
19
+ return await expoSystemUI.setBackgroundColorAsync(colorNumber);
20
+ }
21
+ }
22
+ /**
23
+ * Gets the root view background color.
24
+ * @example
25
+ * ```ts
26
+ * const color = await getBackgroundColorAsync();
27
+ * ```
28
+ * @returns Current root view background color in hex format. `null` if the background color is
29
+ * not set.
30
+ */
31
+ export async function getBackgroundColorAsync() {
32
+ return await expoSystemUI.getBackgroundColorAsync();
33
+ }
34
+ //# sourceMappingURL=system-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-ui.js","sourceRoot":"","sources":["../../src/core/system-ui.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,KAAwB;IACpE,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,MAAM,YAAY,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,MAAM,YAAY,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,OAAO,MAAM,YAAY,CAAC,uBAAuB,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,12 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-system-ui",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.systemui.SystemUIModule",
7
+ "className": "SystemUIModule",
8
+ "nativeName": "ExpoSystemUI"
9
+ }
10
+ ]
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "@symbiote-native/system-ui",
3
+ "version": "0.0.1",
4
+ "description": "expo-system-ui wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Sets and reads the root view's background color.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "packages/system-ui"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/system-ui#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
+ },
15
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "type": "module",
17
+ "main": "./build/core/index.js",
18
+ "module": "./build/core/index.js",
19
+ "types": "./build/core/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./build/core/index.d.ts",
23
+ "default": "./build/core/index.js"
24
+ },
25
+ "./vue": {
26
+ "types": "./build/vue/index.d.ts",
27
+ "default": "./build/vue/index.js"
28
+ },
29
+ "./react": {
30
+ "types": "./build/react/index.d.ts",
31
+ "default": "./build/react/index.js"
32
+ },
33
+ "./angular": {
34
+ "types": "./build-ngc/angular/index.d.ts",
35
+ "react-native": "./build-ngc/angular/index.js",
36
+ "default": "./src/angular/index.ts"
37
+ }
38
+ },
39
+ "files": [
40
+ "src",
41
+ "build",
42
+ "build-ngc",
43
+ "native-link.json"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "expo-system-ui": "57.0.2",
50
+ "expo-modules-core": "57.0.5"
51
+ },
52
+ "peerDependencies": {
53
+ "@symbiote-native/engine": ">=0.1.7",
54
+ "@angular/core": ">=20",
55
+ "@vue/runtime-core": "^3.5.13",
56
+ "react": ">=19.0.0",
57
+ "react-native": ">=0.86",
58
+ "vue": ">=3.5.0",
59
+ "@symbiote-native/angular": "0.6.1",
60
+ "@symbiote-native/react": "0.2.8",
61
+ "@symbiote-native/vue": "0.3.8"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@symbiote-native/angular": {
65
+ "optional": true
66
+ },
67
+ "@symbiote-native/react": {
68
+ "optional": true
69
+ },
70
+ "@symbiote-native/vue": {
71
+ "optional": true
72
+ },
73
+ "@angular/core": {
74
+ "optional": true
75
+ },
76
+ "@vue/runtime-core": {
77
+ "optional": true
78
+ },
79
+ "react": {
80
+ "optional": true
81
+ },
82
+ "vue": {
83
+ "optional": true
84
+ }
85
+ },
86
+ "devDependencies": {
87
+ "@angular/compiler": "^22",
88
+ "@angular/compiler-cli": "^22",
89
+ "@angular/core": "^22",
90
+ "@types/node": "^26.0.0",
91
+ "@types/react": "^19.2.0",
92
+ "@vue/runtime-core": "^3.5.13",
93
+ "react": "19.2.3",
94
+ "typescript": "~6.0.0",
95
+ "@symbiote-native/angular": "0.6.1",
96
+ "@symbiote-native/react": "0.2.8",
97
+ "@symbiote-native/vue": "0.3.8",
98
+ "@symbiote-native/test-utils": "0.1.6",
99
+ "@symbiote-native/engine": "0.1.7"
100
+ },
101
+ "scripts": {
102
+ "typecheck": "tsc --build",
103
+ "clean": "rm -rf build-ngc",
104
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
105
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
106
+ }
107
+ }
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/system-ui/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1 @@
1
+ export { getBackgroundColorAsync, setBackgroundColorAsync } from './system-ui';
@@ -0,0 +1,16 @@
1
+ import type { ColorValue, ProcessedColorValue } from 'react-native';
2
+ import { requireNativeModule } from 'expo-modules-core';
3
+
4
+ const EXPO_SYSTEM_UI_MODULE_NAME = 'ExpoSystemUI';
5
+
6
+ // The color argument accepts everything system-ui.ts can pass it: the raw ColorValue on web,
7
+ // or RN's own processColor() result (a number, an OpaqueColorValue, or undefined) on
8
+ // iOS/Android.
9
+ export type INativeSystemUIModule = {
10
+ setBackgroundColorAsync(
11
+ color: ColorValue | ProcessedColorValue | null | undefined,
12
+ ): Promise<void>;
13
+ getBackgroundColorAsync(): Promise<ColorValue | null>;
14
+ };
15
+
16
+ export const expoSystemUI = requireNativeModule<INativeSystemUIModule>(EXPO_SYSTEM_UI_MODULE_NAME);
@@ -0,0 +1,62 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const FAKE_NATIVE_SYSTEM_UI = {
4
+ setBackgroundColorAsync: vi.fn(async () => undefined),
5
+ getBackgroundColorAsync: vi.fn(async () => '#000000'),
6
+ };
7
+
8
+ // The real ExpoSystemUI native module only exists on device — resolving it via
9
+ // requireNativeModule() at import time would throw in this headless test run, so the
10
+ // module-lookup file is faked in place of expo-modules-core's runtime resolution, the same
11
+ // pattern packages/device/src/core/device.test.ts uses.
12
+ vi.mock('./native-module', () => ({
13
+ expoSystemUI: FAKE_NATIVE_SYSTEM_UI,
14
+ }));
15
+
16
+ // react-native's real Platform module reads native constants that don't exist in this headless
17
+ // run; processColor is stubbed to a simple, observable transform so the two branches
18
+ // (color == null / Platform.OS === 'web' / native processColor) are each independently provable.
19
+ vi.mock('react-native', () => ({
20
+ Platform: { OS: 'ios' },
21
+ processColor: vi.fn((color: string) => `processed(${color})`),
22
+ }));
23
+
24
+ const { getBackgroundColorAsync, setBackgroundColorAsync } = await import('./system-ui');
25
+ const { Platform, processColor } = await import('react-native');
26
+
27
+ afterEach(() => {
28
+ vi.clearAllMocks();
29
+ Platform.OS = 'ios';
30
+ });
31
+
32
+ describe('setBackgroundColorAsync', () => {
33
+ it('passes null straight through when color is null', async () => {
34
+ await setBackgroundColorAsync(null);
35
+
36
+ expect(processColor).not.toHaveBeenCalled();
37
+ expect(FAKE_NATIVE_SYSTEM_UI.setBackgroundColorAsync).toHaveBeenCalledWith(null);
38
+ });
39
+
40
+ it('runs a non-null color through processColor on native platforms', async () => {
41
+ await setBackgroundColorAsync('tomato');
42
+
43
+ expect(processColor).toHaveBeenCalledWith('tomato');
44
+ expect(FAKE_NATIVE_SYSTEM_UI.setBackgroundColorAsync).toHaveBeenCalledWith('processed(tomato)');
45
+ });
46
+
47
+ it('passes the raw color straight through on web, skipping processColor', async () => {
48
+ Platform.OS = 'web';
49
+
50
+ await setBackgroundColorAsync('tomato');
51
+
52
+ expect(processColor).not.toHaveBeenCalled();
53
+ expect(FAKE_NATIVE_SYSTEM_UI.setBackgroundColorAsync).toHaveBeenCalledWith('tomato');
54
+ });
55
+ });
56
+
57
+ describe('getBackgroundColorAsync', () => {
58
+ it('delegates to the native module', async () => {
59
+ await expect(getBackgroundColorAsync()).resolves.toBe('#000000');
60
+ expect(FAKE_NATIVE_SYSTEM_UI.getBackgroundColorAsync).toHaveBeenCalled();
61
+ });
62
+ });
@@ -0,0 +1,40 @@
1
+ // Hand-ported from .vendors/expo/packages/expo-system-ui/src/SystemUI.ts (sdk-57), verbatim
2
+ // apart from resolving the native module through native-module.ts (this repo's convention,
3
+ // matching local-auth/device/cellular) instead of a bare `import ExpoSystemUI from
4
+ // './ExpoSystemUI'`.
5
+ import type { ColorValue } from 'react-native';
6
+ import { Platform, processColor } from 'react-native';
7
+
8
+ import { expoSystemUI } from './native-module';
9
+
10
+ /**
11
+ * Changes the root view background color. Call this outside of your component tree, e.g. in
12
+ * the root file, since it affects the whole app.
13
+ * @example
14
+ * ```ts
15
+ * setBackgroundColorAsync("black");
16
+ * ```
17
+ * @param color Any valid [CSS 3 (SVG) color](http://www.w3.org/TR/css3-color/#svg-color), or
18
+ * `null` to clear the override.
19
+ */
20
+ export async function setBackgroundColorAsync(color: ColorValue | null): Promise<void> {
21
+ if (color == null) {
22
+ return await expoSystemUI.setBackgroundColorAsync(null);
23
+ } else {
24
+ const colorNumber = Platform.OS === 'web' ? color : processColor(color);
25
+ return await expoSystemUI.setBackgroundColorAsync(colorNumber);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Gets the root view background color.
31
+ * @example
32
+ * ```ts
33
+ * const color = await getBackgroundColorAsync();
34
+ * ```
35
+ * @returns Current root view background color in hex format. `null` if the background color is
36
+ * not set.
37
+ */
38
+ export async function getBackgroundColorAsync(): Promise<ColorValue | null> {
39
+ return await expoSystemUI.getBackgroundColorAsync();
40
+ }
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/system-ui/react: the React entry over the framework-agnostic core. Both
2
+ // exports are plain async functions with no per-instance state and no event stream (unlike
3
+ // @symbiote-native/sensors) — there is nothing for a hook to wrap, so this is a plain
4
+ // re-export, mirroring how @symbiote-native/device's React entry passes its own free
5
+ // functions through untouched.
6
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/system-ui/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export.
4
+ export * from '../core';