Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion apps/expo-go/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ expoAutolinking.exclude = [
'expo-maps',
'expo-network-addons',
'expo-splash-screen',
'@expo/ui',
'expo-mesh-gradient',
'@expo/app-integrity',
'@expo/home',
Expand Down
1 change: 0 additions & 1 deletion apps/expo-go/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions apps/expo-go/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

<APIInstallSection />

## 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<TextFieldValue>({
text: '',
selection: { start: 0, end: 0 },
});

return (
<Host matchContents>
<TextField
value={maskedPhone}
keyboardOptions={{ keyboardType: 'phone' }}
modifiers={[fillMaxWidth()]}
onValueChange={v => {
'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 },
};
}
}}>
<TextField.Placeholder>
<ComposeText>(555) 123-4567</ComposeText>
</TextField.Placeholder>
</TextField>
</Host>
);
}
```

## API

```tsx
import { useNativeState } from '@expo/ui/jetpack-compose';
```

<APISection packageName="expo-ui/jetpack-compose/usenativestate" apiName="useNativeState" />
3 changes: 0 additions & 3 deletions docs/pages/versions/unversioned/sdk/ui/swift-ui/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

Expand All @@ -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 &mdash; 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
Expand Down
75 changes: 75 additions & 0 deletions docs/pages/versions/unversioned/sdk/ui/swift-ui/usenativestate.mdx
Original file line number Diff line number Diff line change
@@ -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

<APIInstallSection />

## 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<TextFieldRef>(null);
const maskedPhone = useNativeState('');

const setPhoneCursor = useCallback((position: number) => {
phoneRef.current?.setSelection(position, position);
}, []);

return (
<Host matchContents>
<TextField
ref={phoneRef}
text={maskedPhone}
placeholder="(555) 123-4567"
modifiers={[keyboardType('phone-pad')]}
onTextChange={v => {
'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);
}
}}
/>
</Host>
);
}
```

## API

```tsx
import { useNativeState } from '@expo/ui/swift-ui';
```

<APISection packageName="expo-ui/swift-ui/usenativestate" apiName="useNativeState" />
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class UpdatesStateEvent(
DownloadCompleteWithRollback("downloadCompleteWithRollback"),
DownloadError("downloadError"),
DownloadProgress("downloadProgress"),
Restart("restart");
Restart("restart")
}

companion object {
Expand Down
1 change: 0 additions & 1 deletion packages/expo-modules-jsi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
19 changes: 19 additions & 0 deletions packages/expo-ui/android/src/main/java/expo/modules/ui/ChipView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading
Loading