From b27f83ded341c0d2413a4877e3ae2da5a11d5294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nishan=20=28o=5E=E2=96=BD=5Eo=29?= Date: Fri, 1 May 2026 19:08:21 +0530 Subject: [PATCH 1/5] [ui][docs] - `useNativeState` docs (#45282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Adds docs for `useNativeState` hook. # How Added docs and generated API data json # Test Plan Tested locally # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --------- Co-authored-by: Kudo Chien Co-authored-by: Aman Mittal Co-authored-by: nishan (o^▽^o) --- .../sdk/ui/jetpack-compose/usenativestate.mdx | 81 +++++++++++++++++++ .../sdk/ui/swift-ui/usenativestate.mdx | 75 +++++++++++++++++ .../jetpack-compose/usenativestate.json | 1 + .../expo-ui/swift-ui/usenativestate.json | 1 + .../expo-ui/build/State/useNativeState.d.ts | 7 +- .../build/State/useNativeState.d.ts.map | 2 +- packages/expo-ui/src/State/useNativeState.ts | 7 +- tools/src/commands/GenerateDocsAPIData.ts | 2 + 8 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 docs/pages/versions/unversioned/sdk/ui/jetpack-compose/usenativestate.mdx create mode 100644 docs/pages/versions/unversioned/sdk/ui/swift-ui/usenativestate.mdx create mode 100644 docs/public/static/data/unversioned/expo-ui/jetpack-compose/usenativestate.json create mode 100644 docs/public/static/data/unversioned/expo-ui/swift-ui/usenativestate.json diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/usenativestate.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/usenativestate.mdx new file mode 100644 index 00000000000000..2a83c9c54159ce --- /dev/null +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/usenativestate.mdx @@ -0,0 +1,81 @@ +--- +title: useNativeState +description: A React hook that creates observable state shared between JavaScript and native Jetpack Compose views. +sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui/src/State/useNativeState.ts' +packageName: '@expo/ui' +platforms: ['android', 'expo-go'] +--- + +import APISection from '~/components/plugins/APISection'; +import { APIInstallSection } from '~/components/plugins/InstallSection'; + +`useNativeState` returns an [`ObservableState`](#observablestate) that maps to a Compose [`MutableState`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/MutableState) on the native side, so reads and writes to `.value` are tracked directly by Compose without going through the React render cycle. This lets you update the native view synchronously from a worklet on the UI thread. + +## Installation + + + +## Usage + +> **Note:** Using worklets requires installing [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) and [`react-native-worklets`](https://docs.swmansion.com/react-native-worklets/) in your project. `useNativeState` itself works without them, but the synchronous UI-thread updates shown below depend on the worklet runtime. + +The example below masks a phone number as the user types. The formatting and the write to `maskedPhone.value` both happen synchronously on the UI thread, so there is no flicker between the typed value and the masked value. + +```tsx WorkletPhoneMaskExample.tsx +import { + Host, + TextField, + TextFieldValue, + Text as ComposeText, + useNativeState, +} from '@expo/ui/jetpack-compose'; +import { fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers'; + +export default function WorkletPhoneMaskExample() { + const maskedPhone = useNativeState({ + text: '', + selection: { start: 0, end: 0 }, + }); + + return ( + + { + 'worklet'; + const digits = v.text.replace(/\D/g, '').slice(0, 10); + let formatted: string; + if (digits.length === 0) { + formatted = ''; + } else if (digits.length <= 3) { + formatted = digits; + } else if (digits.length <= 6) { + formatted = `(${digits.slice(0, 3)}) ${digits.slice(3)}`; + } else { + formatted = `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; + } + if (formatted !== v.text) { + maskedPhone.value = { + text: formatted, + selection: { start: formatted.length, end: formatted.length }, + }; + } + }}> + + (555) 123-4567 + + + + ); +} +``` + +## API + +```tsx +import { useNativeState } from '@expo/ui/jetpack-compose'; +``` + + diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/usenativestate.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/usenativestate.mdx new file mode 100644 index 00000000000000..b3d13813f6f9bb --- /dev/null +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/usenativestate.mdx @@ -0,0 +1,75 @@ +--- +title: useNativeState +description: A React hook that creates observable state shared between JavaScript and native SwiftUI views. +sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui/src/State/useNativeState.ts' +packageName: '@expo/ui' +platforms: ['ios', 'tvos', 'expo-go'] +--- + +import APISection from '~/components/plugins/APISection'; +import { APIInstallSection } from '~/components/plugins/InstallSection'; + +`useNativeState` returns an [`ObservableState`](#observablestate) that maps to a SwiftUI [`ObservableObject`](https://developer.apple.com/documentation/combine/observableobject) on the native side, so reads and writes to `.value` are observed directly by SwiftUI without going through the React render cycle. This lets you update the native view synchronously from a worklet on the UI thread. + +## Installation + + + +## Usage + +> **Note:** Using worklets requires installing [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) and [`react-native-worklets`](https://docs.swmansion.com/react-native-worklets/) in your project. `useNativeState` itself works without them, but the synchronous UI-thread updates shown below depend on the worklet runtime. + +The example below masks a phone number as the user types. The formatting and the write to `maskedPhone.value` both happen synchronously on the UI thread, so there is no flicker between the typed value and the masked value. + +```tsx WorkletPhoneMaskExample.tsx +import { Host, TextField, TextFieldRef, useNativeState } from '@expo/ui/swift-ui'; +import { keyboardType } from '@expo/ui/swift-ui/modifiers'; +import { useCallback, useRef } from 'react'; +import { runOnJS } from 'react-native-worklets'; + +export default function WorkletPhoneMaskExample() { + const phoneRef = useRef(null); + const maskedPhone = useNativeState(''); + + const setPhoneCursor = useCallback((position: number) => { + phoneRef.current?.setSelection(position, position); + }, []); + + return ( + + { + 'worklet'; + const digits = v.replace(/\D/g, '').slice(0, 10); + let formatted: string; + if (digits.length === 0) { + formatted = ''; + } else if (digits.length <= 3) { + formatted = digits; + } else if (digits.length <= 6) { + formatted = `(${digits.slice(0, 3)}) ${digits.slice(3)}`; + } else { + formatted = `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; + } + if (formatted !== v) { + maskedPhone.value = formatted; + runOnJS(setPhoneCursor)(formatted.length); + } + }} + /> + + ); +} +``` + +## API + +```tsx +import { useNativeState } from '@expo/ui/swift-ui'; +``` + + diff --git a/docs/public/static/data/unversioned/expo-ui/jetpack-compose/usenativestate.json b/docs/public/static/data/unversioned/expo-ui/jetpack-compose/usenativestate.json new file mode 100644 index 00000000000000..e8ffe960976fba --- /dev/null +++ b/docs/public/static/data/unversioned/expo-ui/jetpack-compose/usenativestate.json @@ -0,0 +1 @@ +{"schemaVersion":"2.0","name":"expo-ui/jetpack-compose/usenativestate","variant":"project","kind":1,"children":[{"name":"ObservableState","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Observable state shared between JavaScript and native views (SwiftUI on iOS,\nJetpack Compose on Android)."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072}],"type":{"type":"intersection","types":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/SharedObject.ts","qualifiedName":"SharedObject"},"name":"SharedObject","package":"expo-modules-core"},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"value","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current value. Reads are safe from any thread; prefer writing from a worklet\nso the update runs on the native UI thread. Updating state from the JS thread\nmight show a development warning."}]},"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}}]}}]}},{"name":"useNativeState","variant":"declaration","kind":64,"signatures":[{"name":"useNativeState","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Creates an observable native state that is automatically cleaned up when the component unmounts."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072}],"parameters":[{"name":"initialValue","variant":"param","kind":32768,"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}}],"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}],"name":"ObservableState","package":"@expo/ui"}}]}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/docs/public/static/data/unversioned/expo-ui/swift-ui/usenativestate.json b/docs/public/static/data/unversioned/expo-ui/swift-ui/usenativestate.json new file mode 100644 index 00000000000000..593a65b7677c17 --- /dev/null +++ b/docs/public/static/data/unversioned/expo-ui/swift-ui/usenativestate.json @@ -0,0 +1 @@ +{"schemaVersion":"2.0","name":"expo-ui/swift-ui/usenativestate","variant":"project","kind":1,"children":[{"name":"ObservableState","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Observable state shared between JavaScript and native views (SwiftUI on iOS,\nJetpack Compose on Android)."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072}],"type":{"type":"intersection","types":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/SharedObject.ts","qualifiedName":"SharedObject"},"name":"SharedObject","package":"expo-modules-core"},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"value","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current value. Reads are safe from any thread; prefer writing from a worklet\nso the update runs on the native UI thread. Updating state from the JS thread\nmight show a development warning."}]},"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}}]}}]}},{"name":"useNativeState","variant":"declaration","kind":64,"signatures":[{"name":"useNativeState","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Creates an observable native state that is automatically cleaned up when the component unmounts."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072}],"parameters":[{"name":"initialValue","variant":"param","kind":32768,"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}}],"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}],"name":"ObservableState","package":"@expo/ui"}}]}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/packages/expo-ui/build/State/useNativeState.d.ts b/packages/expo-ui/build/State/useNativeState.d.ts index 20c69858252867..2862d1af32702f 100644 --- a/packages/expo-ui/build/State/useNativeState.d.ts +++ b/packages/expo-ui/build/State/useNativeState.d.ts @@ -1,12 +1,13 @@ import { type SharedObject } from 'expo-modules-core'; /** - * Observable state shared between JavaScript and native SwiftUI views. + * Observable state shared between JavaScript and native views (Jetpack Compose + * on Android and SwiftUI on iOS). */ export type ObservableState = SharedObject & { /** * The current value. Reads are safe from any thread; prefer writing from a worklet - * so the update runs on SwiftUI's UI thread. Updating state from the JS thread - * might show a SwiftUI warning. + * so the update runs on the native UI thread. Updating state from the JS thread + * might show a development warning. */ value: T; }; diff --git a/packages/expo-ui/build/State/useNativeState.d.ts.map b/packages/expo-ui/build/State/useNativeState.d.ts.map index 009756b934135e..9eb4ad485c5741 100644 --- a/packages/expo-ui/build/State/useNativeState.d.ts.map +++ b/packages/expo-ui/build/State/useNativeState.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useNativeState.d.ts","sourceRoot":"","sources":["../../src/State/useNativeState.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAA4B,MAAM,mBAAmB,CAAC;AAMhF;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,YAAY,GAAG;IAC9C;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAMrE"} \ No newline at end of file +{"version":3,"file":"useNativeState.d.ts","sourceRoot":"","sources":["../../src/State/useNativeState.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAA4B,MAAM,mBAAmB,CAAC;AAMhF;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,YAAY,GAAG;IAC9C;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAMrE"} \ No newline at end of file diff --git a/packages/expo-ui/src/State/useNativeState.ts b/packages/expo-ui/src/State/useNativeState.ts index 85c92c24b7ff98..d4dd47433fe5ac 100644 --- a/packages/expo-ui/src/State/useNativeState.ts +++ b/packages/expo-ui/src/State/useNativeState.ts @@ -6,13 +6,14 @@ import { worklets } from './optionalWorklets'; const ExpoUI = requireNativeModule('ExpoUI'); /** - * Observable state shared between JavaScript and native SwiftUI views. + * Observable state shared between JavaScript and native views (Jetpack Compose + * on Android and SwiftUI on iOS). */ export type ObservableState = SharedObject & { /** * The current value. Reads are safe from any thread; prefer writing from a worklet - * so the update runs on SwiftUI's UI thread. Updating state from the JS thread - * might show a SwiftUI warning. + * so the update runs on the native UI thread. Updating state from the JS thread + * might show a development warning. */ value: T; }; diff --git a/tools/src/commands/GenerateDocsAPIData.ts b/tools/src/commands/GenerateDocsAPIData.ts index d7229a36808194..5328820dbfef27 100644 --- a/tools/src/commands/GenerateDocsAPIData.ts +++ b/tools/src/commands/GenerateDocsAPIData.ts @@ -65,6 +65,7 @@ const uiPackagesMapping: Record = { 'expo-ui/swift-ui/text': ['swift-ui/Text/index.tsx', 'expo-ui'], 'expo-ui/swift-ui/textfield': ['swift-ui/TextField/index.tsx', 'expo-ui'], 'expo-ui/swift-ui/toggle': ['swift-ui/Toggle/index.tsx', 'expo-ui'], + 'expo-ui/swift-ui/usenativestate': ['State/useNativeState.ts', 'expo-ui'], 'expo-ui/swift-ui/vstack': ['swift-ui/VStack/index.tsx', 'expo-ui'], 'expo-ui/swift-ui/zstack': ['swift-ui/ZStack/index.tsx', 'expo-ui'], @@ -135,6 +136,7 @@ const uiPackagesMapping: Record = { 'expo-ui/jetpack-compose/textfield': ['jetpack-compose/TextField/index.tsx', 'expo-ui'], 'expo-ui/jetpack-compose/togglebutton': ['jetpack-compose/ToggleButton/index.tsx', 'expo-ui'], 'expo-ui/jetpack-compose/tooltip': ['jetpack-compose/Tooltip/index.tsx', 'expo-ui'], + 'expo-ui/jetpack-compose/usenativestate': ['State/useNativeState.ts', 'expo-ui'], }; const PACKAGES_MAPPING: Record = { From 20eef843c798ff57b6f87722288eeaeab5ea32f2 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Fri, 1 May 2026 22:24:26 +0800 Subject: [PATCH 2/5] run spotlessApply (#45283) --- .../expo/modules/appmetrics/AppMetadata.kt | 2 +- .../appmetrics/updates/UpdatesStateEvent.kt | 2 +- .../expo/modules/observe/EventDispatcher.kt | 4 +- .../expo/modules/observe/OpenTelemetry.kt | 4 +- .../expo/modules/observe/OpenTelemetryTest.kt | 10 ++-- .../src/main/java/expo/modules/ui/ChipView.kt | 19 +++++++ .../java/expo/modules/ui/DatePickerView.kt | 41 +++++++++++++-- .../ui/HorizontalFloatingToolbarView.kt | 7 ++- .../expo/modules/ui/HorizontalPagerView.kt | 8 ++- .../src/main/java/expo/modules/ui/HostView.kt | 8 +-- .../expo/modules/ui/ModalBottomSheetView.kt | 2 +- .../java/expo/modules/ui/ModifierRegistry.kt | 2 +- .../expo/modules/ui/PaddingValuesExtension.kt | 3 ++ .../main/java/expo/modules/ui/ProgressView.kt | 2 + .../main/java/expo/modules/ui/RNHostView.kt | 8 +-- .../java/expo/modules/ui/TextFieldView.kt | 50 +++++++++++++------ .../java/expo/modules/ui/button/Button.kt | 6 +++ .../expo/modules/ui/colors/MaterialColors.kt | 1 + .../expo/modules/ui/menu/DropdownMenuItem.kt | 5 ++ .../expo/modules/ui/state/WorkletCallback.kt | 8 +-- 20 files changed, 146 insertions(+), 46 deletions(-) diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetadata.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetadata.kt index 09f8364e1b2e48..ff79680c5f7b40 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetadata.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetadata.kt @@ -14,7 +14,7 @@ data class AppMetadata( val deviceName: String?, val expoSdkVersion: String, val reactNativeVersion: String, - val clientVersion: String?, + val clientVersion: String? ) data class AppUpdatesInfo( diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/updates/UpdatesStateEvent.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/updates/UpdatesStateEvent.kt index 2ddcab63862a87..71fd1a7f3719af 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/updates/UpdatesStateEvent.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/updates/UpdatesStateEvent.kt @@ -18,7 +18,7 @@ data class UpdatesStateEvent( DownloadCompleteWithRollback("downloadCompleteWithRollback"), DownloadError("downloadError"), DownloadProgress("downloadProgress"), - Restart("restart"); + Restart("restart") } companion object { diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/EventDispatcher.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/EventDispatcher.kt index b2baa8516cce4c..db4d9f3b6cd1ac 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/EventDispatcher.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/EventDispatcher.kt @@ -23,8 +23,8 @@ class EventDispatcher( ) { private fun endpointUrl(): String { val base = when (baseUrl.endsWith("/")) { - true -> "${baseUrl}${projectId}" - else -> "${baseUrl}/${projectId}" + true -> "${baseUrl}$projectId" + else -> "$baseUrl/$projectId" } return if (useOpenTelemetry) "$base/v1/metrics" else base } diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt index eb3dd87fc3d794..54e32208ceffd7 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt @@ -94,7 +94,7 @@ private val metricNameMap = mapOf( "launchTime" to "expo.app_startup.launch_time", // Updates - "updateDownloadTime" to "expo.updates.download_time", + "updateDownloadTime" to "expo.updates.download_time" ) fun EASMetric.toOTMetric(): OTMetric { @@ -134,7 +134,7 @@ fun Event.toOTMetadata(easClientId: String): OTMetadata { OTAttribute.of("telemetry.sdk.language", "kotlin"), OTAttribute.of("expo.sdk.version", metadata.expoSdkVersion), OTAttribute.of("expo.react_native.version", metadata.reactNativeVersion), - OTAttribute.of("expo.eas_client.id", easClientId), + OTAttribute.of("expo.eas_client.id", easClientId) ) // Send optional attributes only if they are set. diff --git a/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt b/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt index edd5d45eb399e4..03ca51a6fd1cea 100644 --- a/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt +++ b/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt @@ -165,10 +165,12 @@ class OpenTelemetryTest { category = "appStartup", name = "bundleLoadTime", value = 1.0, - customParams = JsonObject(mapOf( - "screen" to JsonPrimitive("dashboard"), - "variant" to JsonPrimitive("A") - )) + customParams = JsonObject( + mapOf( + "screen" to JsonPrimitive("dashboard"), + "variant" to JsonPrimitive("A") + ) + ) ) val otMetric = metric.toOTMetric() val attrs = otMetric.gauge.dataPoints[0].attributes.associate { it.key to it.value.stringValue } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ChipView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ChipView.kt index c649d67a0d986c..a7b3ff8f303a3e 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ChipView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ChipView.kt @@ -28,44 +28,63 @@ open class ChipPressedEvent : Record, Serializable @OptimizedRecord class AssistChipColors : Record { @Field val containerColor: Color? = null + @Field val labelColor: Color? = null + @Field val leadingIconContentColor: Color? = null + @Field val trailingIconContentColor: Color? = null } @OptimizedRecord class FilterChipColors : Record { @Field val containerColor: Color? = null + @Field val labelColor: Color? = null + @Field val iconColor: Color? = null + @Field val selectedContainerColor: Color? = null + @Field val selectedLabelColor: Color? = null + @Field val selectedLeadingIconColor: Color? = null + @Field val selectedTrailingIconColor: Color? = null } @OptimizedRecord class InputChipColors : Record { @Field val containerColor: Color? = null + @Field val labelColor: Color? = null + @Field val leadingIconColor: Color? = null + @Field val trailingIconColor: Color? = null + @Field val selectedContainerColor: Color? = null + @Field val selectedLabelColor: Color? = null + @Field val selectedLeadingIconColor: Color? = null + @Field val selectedTrailingIconColor: Color? = null } @OptimizedRecord class SuggestionChipColors : Record { @Field val containerColor: Color? = null + @Field val labelColor: Color? = null + @Field val iconContentColor: Color? = null } @OptimizedRecord class ChipBorder : Record { @Field val width: Float = 1f + @Field val color: Color? = null } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/DatePickerView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/DatePickerView.kt index 4d97284d0d9048..0ea798eb30f898 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/DatePickerView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/DatePickerView.kt @@ -37,7 +37,6 @@ data class DatePickerResult( val date: Long? ) : Record - enum class DisplayedComponents(val value: String) : Enumerable { DATE("date"), HOUR_AND_MINUTE("hourAndMinute"), @@ -61,49 +60,85 @@ enum class Variant(val value: String) : Enumerable { class DateTimePickerColorOverrides : Record { // DatePicker colors @Field val containerColor: AndroidColor? = null + @Field val titleContentColor: AndroidColor? = null + @Field val headlineContentColor: AndroidColor? = null + @Field val weekdayContentColor: AndroidColor? = null + @Field val subheadContentColor: AndroidColor? = null + @Field val navigationContentColor: AndroidColor? = null + @Field val yearContentColor: AndroidColor? = null + @Field val disabledYearContentColor: AndroidColor? = null + @Field val currentYearContentColor: AndroidColor? = null + @Field val selectedYearContentColor: AndroidColor? = null + @Field val disabledSelectedYearContentColor: AndroidColor? = null + @Field val selectedYearContainerColor: AndroidColor? = null + @Field val disabledSelectedYearContainerColor: AndroidColor? = null + @Field val dayContentColor: AndroidColor? = null + @Field val disabledDayContentColor: AndroidColor? = null + @Field val selectedDayContentColor: AndroidColor? = null + @Field val disabledSelectedDayContentColor: AndroidColor? = null + @Field val selectedDayContainerColor: AndroidColor? = null + @Field val disabledSelectedDayContainerColor: AndroidColor? = null + @Field val todayContentColor: AndroidColor? = null + @Field val todayDateBorderColor: AndroidColor? = null + @Field val dayInSelectionRangeContentColor: AndroidColor? = null + @Field val dayInSelectionRangeContainerColor: AndroidColor? = null + @Field val dividerColor: AndroidColor? = null // TimePicker colors @Field val clockDialColor: AndroidColor? = null + @Field val clockDialSelectedContentColor: AndroidColor? = null + @Field val clockDialUnselectedContentColor: AndroidColor? = null + @Field val selectorColor: AndroidColor? = null + @Field val periodSelectorBorderColor: AndroidColor? = null + @Field val periodSelectorSelectedContainerColor: AndroidColor? = null + @Field val periodSelectorUnselectedContainerColor: AndroidColor? = null + @Field val periodSelectorSelectedContentColor: AndroidColor? = null + @Field val periodSelectorUnselectedContentColor: AndroidColor? = null + @Field val timeSelectorSelectedContainerColor: AndroidColor? = null + @Field val timeSelectorUnselectedContainerColor: AndroidColor? = null + @Field val timeSelectorSelectedContentColor: AndroidColor? = null + @Field val timeSelectorUnselectedContentColor: AndroidColor? = null } @OptimizedRecord class SelectableDatesRecord : Record { @Field val start: Long? = null + @Field val end: Long? = null } @@ -129,7 +164,7 @@ data class DatePickerDialogProps( val dismissButtonLabel: String? = null, val color: AndroidColor? = null, val elementColors: DateTimePickerColorOverrides = DateTimePickerColorOverrides(), - val selectableDates: SelectableDatesRecord? = null, + val selectableDates: SelectableDatesRecord? = null ) : ComposeProps @OptimizedComposeProps @@ -139,7 +174,7 @@ data class TimePickerDialogProps( val confirmButtonLabel: String? = null, val dismissButtonLabel: String? = null, val color: AndroidColor? = null, - val elementColors: DateTimePickerColorOverrides = DateTimePickerColorOverrides(), + val elementColors: DateTimePickerColorOverrides = DateTimePickerColorOverrides() ) : ComposeProps private fun toUtcDayMillis(localMillis: Long): Long { diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalFloatingToolbarView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalFloatingToolbarView.kt index 7cee7528c882ae..989704755ee564 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalFloatingToolbarView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalFloatingToolbarView.kt @@ -24,8 +24,11 @@ enum class HorizontalFloatingToolbarVariant(val value: String) : Enumerable { @OptimizedRecord class HorizontalFloatingToolbarColors : Record { @Field val toolbarContainerColor: Color? = null + @Field val toolbarContentColor: Color? = null + @Field val fabContainerColor: Color? = null + @Field val fabContentColor: Color? = null } @@ -89,7 +92,7 @@ fun FunctionalComposableScope.HorizontalFloatingToolbarContent(props: Horizontal floatingActionButton = floatingActionButton, colors = colors, scrollBehavior = scrollBehavior, - modifier = modifier, + modifier = modifier ) { Children(UIComposableScope(), filter = { !isSlotView(it) }) } @@ -98,7 +101,7 @@ fun FunctionalComposableScope.HorizontalFloatingToolbarContent(props: Horizontal expanded = true, colors = colors, scrollBehavior = scrollBehavior, - modifier = modifier, + modifier = modifier ) { Children(UIComposableScope(), filter = { !isSlotView(it) }) } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt index 8c74cb15802a7d..30e5497984e973 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt @@ -61,8 +61,12 @@ fun FunctionalComposableScope.HorizontalPagerContent( val pageCountState = remember { mutableIntStateOf(view.size) } DisposableEffect(view) { view.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { - override fun onChildViewAdded(parent: View?, child: View?) { pageCountState.intValue = view.size } - override fun onChildViewRemoved(parent: View?, child: View?) { pageCountState.intValue = view.size } + override fun onChildViewAdded(parent: View?, child: View?) { + pageCountState.intValue = view.size + } + override fun onChildViewRemoved(parent: View?, child: View?) { + pageCountState.intValue = view.size + } }) pageCountState.intValue = view.size onDispose { view.setOnHierarchyChangeListener(null) } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt index b4069aa1da8816..3b6258da2d39ac 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt @@ -157,10 +157,10 @@ internal class HostView(context: Context, appContext: AppContext) : ) { measurables, constraints -> val useViewportSizeMeasurement = props.useViewportSizeMeasurement.value - // useViewportSizeMeasurement: clamp Infinity/0 maxConstraints to the safe area so the - // content has a concrete size to fill. - // matchContents: pass through, so children measure - // at intrinsic size (the unbounded constraint comes from onMeasure's UNSPECIFIED). + // useViewportSizeMeasurement: clamp Infinity/0 maxConstraints to the safe area so the + // content has a concrete size to fill. + // matchContents: pass through, so children measure + // at intrinsic size (the unbounded constraint comes from onMeasure's UNSPECIFIED). val boundedConstraints = Constraints( minWidth = constraints.minWidth, maxWidth = if (useViewportSizeMeasurement && diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt index d9040acf979345..610a022b0ca25d 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt @@ -82,7 +82,7 @@ fun FunctionalComposableScope.ModalBottomSheetContent( // Swallowing the exception avoids an unhandled promise rejection on the JS side. } } - + val resolvedContainerColor = props.containerColor.composeOrNull ?: BottomSheetDefaults.ContainerColor val resolvedContentColor = props.contentColor.composeOrNull ?: contentColorFor(resolvedContainerColor) val resolvedScrimColor = props.scrimColor.composeOrNull ?: BottomSheetDefaults.ScrimColor diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt index 6876aaef4c7e4f..e01797a27f9d65 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt @@ -586,7 +586,7 @@ object ModifierRegistry { val params = recordFromMap(map) Modifier.onVisibilityChanged( minDurationMs = params.minDurationMs, - minFractionVisible = params.minFractionVisible, + minFractionVisible = params.minFractionVisible ) { isVisible -> eventDispatcher("onVisibilityChanged", mapOf("isVisible" to isVisible)) } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/PaddingValuesExtension.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/PaddingValuesExtension.kt index cfb6c322c7ad59..86810eccde3929 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/PaddingValuesExtension.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/PaddingValuesExtension.kt @@ -10,8 +10,11 @@ import expo.modules.kotlin.types.OptimizedRecord @OptimizedRecord class PaddingValuesRecord : Record { @Field val start: Float? = null + @Field val top: Float? = null + @Field val end: Float? = null + @Field val bottom: Float? = null fun toPaddingValues(): PaddingValues { diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ProgressView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ProgressView.kt index 61dbf7021ae080..a43631621f5df6 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ProgressView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ProgressView.kt @@ -26,7 +26,9 @@ import expo.modules.kotlin.views.OptimizedComposeProps @OptimizedRecord class DrawStopIndicatorConfig : Record { @Field val color: Color? = null + @Field val strokeCap: String? = null + @Field val stopSize: Float? = null } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt index 9bb798f4957533..d871185f78d8d8 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/RNHostView.kt @@ -32,7 +32,6 @@ import expo.modules.kotlin.AppContext import expo.modules.kotlin.views.ComposableScope import expo.modules.kotlin.views.ComposeProps import expo.modules.kotlin.views.ExpoComposeView -import expo.modules.kotlin.views.RNHostViewInterface import expo.modules.kotlin.views.OptimizedComposeProps @OptimizedComposeProps @@ -116,8 +115,11 @@ internal class RNHostView(context: Context, appContext: AppContext) : val childSize = remember { mutableStateOf( - if (childView.width > 0 && childView.height > 0) IntSize(childView.width, childView.height) - else IntSize.Zero + if (childView.width > 0 && childView.height > 0) { + IntSize(childView.width, childView.height) + } else { + IntSize.Zero + } ) } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/TextFieldView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/TextFieldView.kt index f865754388d43c..60c19c9a6afc42 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/TextFieldView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/TextFieldView.kt @@ -36,7 +36,7 @@ import expo.modules.kotlin.views.OptimizedComposeProps enum class TextFieldVariant(val value: String) : Enumerable { FILLED("filled"), - OUTLINED("outlined"), + OUTLINED("outlined") } @OptimizedRecord @@ -44,7 +44,7 @@ data class TextFieldKeyboardOptionsRecord( @Field val capitalization: String? = null, @Field val autoCorrectEnabled: Boolean? = null, @Field val keyboardType: String? = null, - @Field val imeAction: String? = null, + @Field val imeAction: String? = null ) : Record @OptimizedRecord @@ -101,22 +101,22 @@ data class TextFieldColorsRecord( @Field val focusedSuffixColor: Color? = null, @Field val unfocusedSuffixColor: Color? = null, @Field val disabledSuffixColor: Color? = null, - @Field val errorSuffixColor: Color? = null, + @Field val errorSuffixColor: Color? = null ) : Record data class KeyboardActionEvent( @Field val action: String, - @Field val value: String, + @Field val value: String ) : Record data class TextFieldSelectionPayload( @Field val start: Int, - @Field val end: Int, + @Field val end: Int ) : Record data class TextFieldValuePayload( @Field val text: String, - @Field val selection: TextFieldSelectionPayload, + @Field val selection: TextFieldSelectionPayload ) : Record // endregion Records @@ -169,7 +169,7 @@ fun TextFieldColorsRecord.toColors(isOutlined: Boolean): TextFieldColors { focusedSuffixColor = focusedSuffixColor.composeOrNull ?: defaults.focusedSuffixColor, unfocusedSuffixColor = unfocusedSuffixColor.composeOrNull ?: defaults.unfocusedSuffixColor, disabledSuffixColor = disabledSuffixColor.composeOrNull ?: defaults.disabledSuffixColor, - errorSuffixColor = errorSuffixColor.composeOrNull ?: defaults.errorSuffixColor, + errorSuffixColor = errorSuffixColor.composeOrNull ?: defaults.errorSuffixColor ) } @@ -192,7 +192,7 @@ data class TextFieldProps( val shape: ShapeRecord? = null, val colors: TextFieldColorsRecord? = null, val onValueChangeSync: WorkletCallback? = null, - val modifiers: ModifierList = emptyList(), + val modifiers: ModifierList = emptyList() ) : ComposeProps // endregion Props @@ -317,12 +317,30 @@ fun FunctionalComposableScope.TextFieldContent( ) val currentText = { state.value.extractText() } val keyboardActions = KeyboardActions( - onDone = { defaultKeyboardAction(ImeAction.Done); onKeyboardActionTriggered(KeyboardActionEvent("done", currentText())) }, - onGo = { defaultKeyboardAction(ImeAction.Go); onKeyboardActionTriggered(KeyboardActionEvent("go", currentText())) }, - onNext = { defaultKeyboardAction(ImeAction.Next); onKeyboardActionTriggered(KeyboardActionEvent("next", currentText())) }, - onPrevious = { defaultKeyboardAction(ImeAction.Previous); onKeyboardActionTriggered(KeyboardActionEvent("previous", currentText())) }, - onSearch = { defaultKeyboardAction(ImeAction.Search); onKeyboardActionTriggered(KeyboardActionEvent("search", currentText())) }, - onSend = { defaultKeyboardAction(ImeAction.Send); onKeyboardActionTriggered(KeyboardActionEvent("send", currentText())) }, + onDone = { + defaultKeyboardAction(ImeAction.Done) + onKeyboardActionTriggered(KeyboardActionEvent("done", currentText())) + }, + onGo = { + defaultKeyboardAction(ImeAction.Go) + onKeyboardActionTriggered(KeyboardActionEvent("go", currentText())) + }, + onNext = { + defaultKeyboardAction(ImeAction.Next) + onKeyboardActionTriggered(KeyboardActionEvent("next", currentText())) + }, + onPrevious = { + defaultKeyboardAction(ImeAction.Previous) + onKeyboardActionTriggered(KeyboardActionEvent("previous", currentText())) + }, + onSearch = { + defaultKeyboardAction(ImeAction.Search) + onKeyboardActionTriggered(KeyboardActionEvent("search", currentText())) + }, + onSend = { + defaultKeyboardAction(ImeAction.Send) + onKeyboardActionTriggered(KeyboardActionEvent("send", currentText())) + } ) // Lines @@ -384,7 +402,7 @@ fun FunctionalComposableScope.TextFieldContent( isError = props.isError, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, minLines = minLines, - shape = shape, colors = colors, + shape = shape, colors = colors ) } else { TextField( @@ -396,7 +414,7 @@ fun FunctionalComposableScope.TextFieldContent( isError = props.isError, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = singleLine, maxLines = maxLines, minLines = minLines, - shape = shape, colors = colors, + shape = shape, colors = colors ) } } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/button/Button.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/button/Button.kt index 9545d97246805d..82220bb6bddb46 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/button/Button.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/button/Button.kt @@ -29,16 +29,22 @@ open class ButtonPressedEvent() : Record, Serializable @OptimizedRecord class ButtonColors : Record { @Field val containerColor: Color? = null + @Field val contentColor: Color? = null + @Field val disabledContainerColor: Color? = null + @Field val disabledContentColor: Color? = null } @OptimizedRecord class ContentPaddingRecord : Record { @Field val start: Double? = null + @Field val top: Double? = null + @Field val end: Double? = null + @Field val bottom: Double? = null } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt index fd2f05a26204b1..2dd359c098e436 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt @@ -23,6 +23,7 @@ internal val isDynamicColorSupported: Boolean = Build.VERSION.SDK_INT >= Build.V internal class MaterialColorsOptions : Record { @Field val scheme: ExpoColorScheme? = null + @Field val seedColor: android.graphics.Color? = null } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/menu/DropdownMenuItem.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/menu/DropdownMenuItem.kt index edaa8551a7ab9e..e4654ea01b1794 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/menu/DropdownMenuItem.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/menu/DropdownMenuItem.kt @@ -19,10 +19,15 @@ import expo.modules.kotlin.views.OptimizedComposeProps @OptimizedRecord class DropdownMenuItemColors : Record { @Field val textColor: Color? = null + @Field val leadingIconColor: Color? = null + @Field val trailingIconColor: Color? = null + @Field val disabledTextColor: Color? = null + @Field val disabledLeadingIconColor: Color? = null + @Field val disabledTrailingIconColor: Color? = null } diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/state/WorkletCallback.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/state/WorkletCallback.kt index 09012f13e8b721..83708686e4d589 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/state/WorkletCallback.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/state/WorkletCallback.kt @@ -14,12 +14,12 @@ class WorkletCallback : SharedObject() { fun invoke(vararg arguments: Any?) { val worklet = worklet ?: run { - Log.w("ExpoUI", "WorkletCallback.invoke: worklet is nil, the callback will not run.") - return + Log.w("ExpoUI", "WorkletCallback.invoke: worklet is nil, the callback will not run.") + return } val runtime = appContext?.uiRuntime ?: run { - Log.w("ExpoUI", "WorkletCallback.invoke: UI worklet runtime is not available, the callback will not run.") - return + Log.w("ExpoUI", "WorkletCallback.invoke: UI worklet runtime is not available, the callback will not run.") + return } worklet.execute(runtime, *arguments) } From f71375c0ce8e9ce07aba96b60242d5d9d50041d9 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Fri, 1 May 2026 22:25:52 +0800 Subject: [PATCH 3/5] [go] add expo ui (#45267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why close ENG-20850 # How - add expo-ui to expo-go - add expo-ui to default template # Test Plan - ✅ local build ios expo-go + expo-ui NCL - ✅ eas build android expo-go + expo-ui NCL # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../versioned/host/exp/exponent/ExperiencePackagePicker.kt | 2 ++ apps/expo-go/android/settings.gradle | 1 - apps/expo-go/ios/Podfile | 1 - apps/expo-go/package.json | 1 + pnpm-lock.yaml | 3 +++ templates/expo-template-default/package.json | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt index 4fb6183399b87a..2284e0613e54fc 100644 --- a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt +++ b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt @@ -80,6 +80,7 @@ import expo.modules.systemui.SystemUIPackage import expo.modules.taskManager.TaskManagerModule import expo.modules.taskManager.TaskManagerPackage import expo.modules.trackingtransparency.TrackingTransparencyModule +import expo.modules.ui.ExpoUIModule import expo.modules.updates.UpdatesPackage import expo.modules.video.VideoModule import expo.modules.videothumbnails.VideoThumbnailsModule @@ -156,6 +157,7 @@ object ExperiencePackagePicker : ModulesProvider { FontUtilsModule::class.java to null, ExpoLinkingModule::class.java to null, ExpoRouterModule::class.java to null, + ExpoUIModule::class.java to null, FileSystemModule::class.java to null, FileSystemLegacyModule::class.java to null, FontLoaderModule::class.java to null, diff --git a/apps/expo-go/android/settings.gradle b/apps/expo-go/android/settings.gradle index e8248465b187e1..971462fe5bc490 100644 --- a/apps/expo-go/android/settings.gradle +++ b/apps/expo-go/android/settings.gradle @@ -39,7 +39,6 @@ expoAutolinking.exclude = [ 'expo-maps', 'expo-network-addons', 'expo-splash-screen', - '@expo/ui', 'expo-mesh-gradient', '@expo/app-integrity', '@expo/home', diff --git a/apps/expo-go/ios/Podfile b/apps/expo-go/ios/Podfile index 774f9b8c7dd7a7..f27e949e88ea6a 100644 --- a/apps/expo-go/ios/Podfile +++ b/apps/expo-go/ios/Podfile @@ -56,7 +56,6 @@ target 'Expo Go' do 'expo-network-addons', 'expo-insights', 'expo-splash-screen', - '@expo/ui', '@expo/app-integrity', 'expo-brownfield', 'expo-widgets' diff --git a/apps/expo-go/package.json b/apps/expo-go/package.json index b47f8eb91b91bb..a769a9ddad0917 100644 --- a/apps/expo-go/package.json +++ b/apps/expo-go/package.json @@ -7,6 +7,7 @@ "author": "Expo", "license": "MIT", "dependencies": { + "@expo/ui": "workspace:*", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/datetimepicker": "^9.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3436e0e85d056b..eb25da9aae8e1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -431,6 +431,9 @@ importers: apps/expo-go: dependencies: + '@expo/ui': + specifier: workspace:* + version: link:../../packages/expo-ui '@expo/vector-icons': specifier: ^15.0.2 version: 15.1.1(expo-font@packages+expo-font)(react-native@0.85.2(@babel/core@7.29.0)(@react-native/jest-preset@0.85.2(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) diff --git a/templates/expo-template-default/package.json b/templates/expo-template-default/package.json index 31fe29613fdb4e..b0235bf3c55801 100644 --- a/templates/expo-template-default/package.json +++ b/templates/expo-template-default/package.json @@ -12,6 +12,7 @@ "lint": "expo lint" }, "dependencies": { + "@expo/ui": "~55.0.1", "expo": "~55.0.2", "expo-constants": "~55.0.7", "expo-device": "~55.0.9", From b7362a90eb6a28eb56ab4880248690c61bac01ed Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Fri, 1 May 2026 22:27:39 +0800 Subject: [PATCH 4/5] [docs] remove alpha/beta for expo-ui (#45268) # Why expo-ui for sdk 56. update missing piece from #45129 close ENG-20850 # How remove alpha/beta status for the unversioned expo-ui docs # Test Plan local docs # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../versions/unversioned/sdk/ui/jetpack-compose/index.mdx | 3 --- docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx | 3 --- 2 files changed, 6 deletions(-) diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/index.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/index.mdx index dfdcbcf33041f7..85d7725027ece4 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/index.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/index.mdx @@ -5,14 +5,11 @@ description: Jetpack Compose components for building native Android interfaces w sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui' packageName: '@expo/ui' platforms: ['android', 'expo-go'] -isAlpha: true hideTOC: true --- import { APIInstallSection } from '~/components/plugins/InstallSection'; -> **important** **This library is currently in [alpha](/more/release-statuses/#alpha) and will frequently experience breaking changes.** It is not available in the Expo Go app — use [development builds](/develop/development-builds/introduction/) to try it out. - The Jetpack Compose components in `@expo/ui/jetpack-compose` allow you to build fully native Android interfaces using Jetpack Compose from React Native. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx index 34e297dac82ec8..b1fcece2db72bc 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx @@ -5,7 +5,6 @@ description: SwiftUI components for building native iOS interfaces with @expo/ui sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui' packageName: '@expo/ui' platforms: ['ios', 'tvos', 'expo-go'] -isBeta: true hasVideoLink: true --- @@ -16,8 +15,6 @@ import { BoxLink } from '~/ui/components/BoxLink'; import { CODE } from '~/ui/components/Text'; import { VideoBoxLink } from '~/ui/components/VideoBoxLink'; -> **important** **This library is currently in [beta](/more/release-statuses/#beta) and subject to breaking changes.** It is not available in the Expo Go app — use [development builds](/develop/development-builds/introduction/) to try it out. - The SwiftUI components in `@expo/ui/swift-ui` allow you to build fully native iOS interfaces using SwiftUI from React Native. ## Installation From 15d35298c9a397c23bcbf6b20e2b9761564acbc4 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Fri, 1 May 2026 16:44:15 +0200 Subject: [PATCH 5/5] [jsi] Make expo-modules-jsi publishable (#45285) --- packages/expo-modules-jsi/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/expo-modules-jsi/package.json b/packages/expo-modules-jsi/package.json index b934a088d38b2c..4246fb5beddc23 100644 --- a/packages/expo-modules-jsi/package.json +++ b/packages/expo-modules-jsi/package.json @@ -3,7 +3,6 @@ "version": "55.0.0", "description": "The JavaScript Interface for Expo Modules", "main": "index.js", - "private": true, "sideEffects": [], "exports": { "./package.json": "./package.json",