@expo/ui 56.0.7 → 56.0.9

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.
Files changed (208) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +44 -3
  4. package/android/src/main/java/expo/modules/ui/HostView.kt +2 -0
  5. package/android/src/main/java/expo/modules/ui/LoadingView.kt +80 -0
  6. package/android/src/main/java/expo/modules/ui/ModifierRegistry.kt +50 -4
  7. package/android/src/main/java/expo/modules/ui/RNHostView.kt +8 -3
  8. package/android/src/main/java/expo/modules/ui/ShadowNodeSyncFlush.kt +28 -0
  9. package/android/src/main/java/expo/modules/ui/SnackbarView.kt +126 -0
  10. package/android/src/main/java/expo/modules/ui/state/ObservableState.kt +10 -0
  11. package/assets/keyboard_arrow_down.xml +10 -0
  12. package/build/State/useNativeState.d.ts +32 -3
  13. package/build/State/useNativeState.d.ts.map +1 -1
  14. package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
  15. package/build/community/menu/MenuView.android.d.ts +16 -0
  16. package/build/community/menu/MenuView.android.d.ts.map +1 -0
  17. package/build/community/menu/MenuView.d.ts +19 -0
  18. package/build/community/menu/MenuView.d.ts.map +1 -0
  19. package/build/community/menu/MenuView.ios.d.ts +10 -0
  20. package/build/community/menu/MenuView.ios.d.ts.map +1 -0
  21. package/build/community/menu/index.d.ts +5 -0
  22. package/build/community/menu/index.d.ts.map +1 -0
  23. package/build/community/menu/types.d.ts +166 -0
  24. package/build/community/menu/types.d.ts.map +1 -0
  25. package/build/jetpack-compose/LoadingIndicator/index.d.ts +41 -0
  26. package/build/jetpack-compose/LoadingIndicator/index.d.ts.map +1 -0
  27. package/build/jetpack-compose/Snackbar/index.d.ts +94 -0
  28. package/build/jetpack-compose/Snackbar/index.d.ts.map +1 -0
  29. package/build/jetpack-compose/index.d.ts +2 -0
  30. package/build/jetpack-compose/index.d.ts.map +1 -1
  31. package/build/jetpack-compose/modifiers/index.d.ts +21 -2
  32. package/build/jetpack-compose/modifiers/index.d.ts.map +1 -1
  33. package/build/swift-ui/Alert/index.d.ts +42 -0
  34. package/build/swift-ui/Alert/index.d.ts.map +1 -0
  35. package/build/swift-ui/BottomSheet/index.d.ts +5 -1
  36. package/build/swift-ui/BottomSheet/index.d.ts.map +1 -1
  37. package/build/swift-ui/SlotView.d.ts +5 -2
  38. package/build/swift-ui/SlotView.d.ts.map +1 -1
  39. package/build/swift-ui/SwipeActions/index.d.ts +38 -0
  40. package/build/swift-ui/SwipeActions/index.d.ts.map +1 -0
  41. package/build/swift-ui/index.d.ts +3 -0
  42. package/build/swift-ui/index.d.ts.map +1 -1
  43. package/build/swift-ui/modifiers/index.d.ts +3 -1
  44. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  45. package/build/swift-ui/modifiers/symbolEffect.d.ts +103 -0
  46. package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -0
  47. package/build/swift-ui/withAnimation.d.ts +26 -0
  48. package/build/swift-ui/withAnimation.d.ts.map +1 -0
  49. package/build/universal/BottomSheet/index.android.d.ts +1 -1
  50. package/build/universal/BottomSheet/index.android.d.ts.map +1 -1
  51. package/build/universal/BottomSheet/index.d.ts +1 -1
  52. package/build/universal/BottomSheet/index.d.ts.map +1 -1
  53. package/build/universal/BottomSheet/index.ios.d.ts +1 -1
  54. package/build/universal/BottomSheet/index.ios.d.ts.map +1 -1
  55. package/build/universal/BottomSheet/types.d.ts +27 -0
  56. package/build/universal/BottomSheet/types.d.ts.map +1 -1
  57. package/build/universal/Collapsible/index.android.d.ts +8 -0
  58. package/build/universal/Collapsible/index.android.d.ts.map +1 -0
  59. package/build/universal/Collapsible/index.d.ts +8 -0
  60. package/build/universal/Collapsible/index.d.ts.map +1 -0
  61. package/build/universal/Collapsible/index.ios.d.ts +7 -0
  62. package/build/universal/Collapsible/index.ios.d.ts.map +1 -0
  63. package/build/universal/Collapsible/types.d.ts +23 -0
  64. package/build/universal/Collapsible/types.d.ts.map +1 -0
  65. package/build/universal/Column/index.d.ts.map +1 -1
  66. package/build/universal/Host/index.d.ts +5 -7
  67. package/build/universal/Host/index.d.ts.map +1 -1
  68. package/build/universal/Host/types.d.ts +72 -0
  69. package/build/universal/Host/types.d.ts.map +1 -0
  70. package/build/universal/List/index.android.d.ts +9 -0
  71. package/build/universal/List/index.android.d.ts.map +1 -0
  72. package/build/universal/List/index.d.ts +8 -0
  73. package/build/universal/List/index.d.ts.map +1 -0
  74. package/build/universal/List/index.ios.d.ts +8 -0
  75. package/build/universal/List/index.ios.d.ts.map +1 -0
  76. package/build/universal/List/types.d.ts +26 -0
  77. package/build/universal/List/types.d.ts.map +1 -0
  78. package/build/universal/ListItem/ListItem.android.d.ts +8 -0
  79. package/build/universal/ListItem/ListItem.android.d.ts.map +1 -0
  80. package/build/universal/ListItem/ListItem.d.ts +9 -0
  81. package/build/universal/ListItem/ListItem.d.ts.map +1 -0
  82. package/build/universal/ListItem/ListItem.ios.d.ts +8 -0
  83. package/build/universal/ListItem/ListItem.ios.d.ts.map +1 -0
  84. package/build/universal/ListItem/ListItemSlots.d.ts +21 -0
  85. package/build/universal/ListItem/ListItemSlots.d.ts.map +1 -0
  86. package/build/universal/ListItem/index.d.ts +10 -0
  87. package/build/universal/ListItem/index.d.ts.map +1 -0
  88. package/build/universal/ListItem/types.d.ts +59 -0
  89. package/build/universal/ListItem/types.d.ts.map +1 -0
  90. package/build/universal/Picker/Picker.android.d.ts +9 -0
  91. package/build/universal/Picker/Picker.android.d.ts.map +1 -0
  92. package/build/universal/Picker/Picker.d.ts +8 -0
  93. package/build/universal/Picker/Picker.d.ts.map +1 -0
  94. package/build/universal/Picker/Picker.ios.d.ts +9 -0
  95. package/build/universal/Picker/Picker.ios.d.ts.map +1 -0
  96. package/build/universal/Picker/PickerItem.d.ts +9 -0
  97. package/build/universal/Picker/PickerItem.d.ts.map +1 -0
  98. package/build/universal/Picker/index.d.ts +8 -0
  99. package/build/universal/Picker/index.d.ts.map +1 -0
  100. package/build/universal/Picker/types.d.ts +69 -0
  101. package/build/universal/Picker/types.d.ts.map +1 -0
  102. package/build/universal/index.d.ts +4 -0
  103. package/build/universal/index.d.ts.map +1 -1
  104. package/expo-module.config.json +1 -1
  105. package/ios/Alert/Alert.swift +56 -0
  106. package/ios/Alert/AlertProps.swift +8 -0
  107. package/ios/BottomSheetView.swift +4 -1
  108. package/ios/ExpoUIModule.swift +43 -1
  109. package/ios/ExpoUITouchHandlerHelper.h +4 -1
  110. package/ios/ExpoUITouchHandlerHelper.mm +1 -0
  111. package/ios/Modifiers/AnimationConfig.swift +109 -0
  112. package/ios/Modifiers/SwipeActionsModifier.swift +97 -0
  113. package/ios/Modifiers/SymbolEffectModifier.swift +452 -0
  114. package/ios/Modifiers/ViewModifierRegistry.swift +5 -112
  115. package/ios/SlotView.swift +5 -0
  116. package/ios/State/ObservableState.swift +12 -1
  117. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7-sources.jar → 56.0.9/expo.modules.ui-56.0.9-sources.jar} +0 -0
  118. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.md5 +1 -0
  119. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha1 +1 -0
  120. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha256 +1 -0
  121. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha512 +1 -0
  122. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar +0 -0
  123. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.md5 +1 -0
  124. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha1 +1 -0
  125. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha256 +1 -0
  126. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha512 +1 -0
  127. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.module → 56.0.9/expo.modules.ui-56.0.9.module} +22 -22
  128. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.md5 +1 -0
  129. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha1 +1 -0
  130. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha256 +1 -0
  131. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha512 +1 -0
  132. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.7/expo.modules.ui-56.0.7.pom → 56.0.9/expo.modules.ui-56.0.9.pom} +1 -1
  133. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.md5 +1 -0
  134. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha1 +1 -0
  135. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha256 +1 -0
  136. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha512 +1 -0
  137. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  138. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  139. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  140. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  141. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  142. package/package.json +7 -3
  143. package/src/State/useNativeState.ts +70 -10
  144. package/src/community/bottom-sheet/BottomSheet.ios.tsx +0 -17
  145. package/src/community/menu/MenuView.android.tsx +224 -0
  146. package/src/community/menu/MenuView.ios.tsx +149 -0
  147. package/src/community/menu/MenuView.tsx +36 -0
  148. package/src/community/menu/index.tsx +14 -0
  149. package/src/community/menu/types.tsx +171 -0
  150. package/src/jetpack-compose/LoadingIndicator/index.tsx +92 -0
  151. package/src/jetpack-compose/Snackbar/index.tsx +135 -0
  152. package/src/jetpack-compose/index.ts +2 -0
  153. package/src/jetpack-compose/modifiers/index.ts +30 -2
  154. package/src/swift-ui/Alert/index.tsx +87 -0
  155. package/src/swift-ui/BottomSheet/index.tsx +32 -15
  156. package/src/swift-ui/SlotView.tsx +17 -4
  157. package/src/swift-ui/SwipeActions/index.tsx +73 -0
  158. package/src/swift-ui/index.tsx +3 -0
  159. package/src/swift-ui/modifiers/index.ts +3 -0
  160. package/src/swift-ui/modifiers/symbolEffect.ts +181 -0
  161. package/src/swift-ui/withAnimation.ts +71 -0
  162. package/src/ts-declarations/react-native-web.d.ts +27 -0
  163. package/src/universal/BottomSheet/index.android.tsx +27 -3
  164. package/src/universal/BottomSheet/index.ios.tsx +30 -12
  165. package/src/universal/BottomSheet/index.tsx +46 -4
  166. package/src/universal/BottomSheet/types.ts +25 -0
  167. package/src/universal/Collapsible/index.android.tsx +72 -0
  168. package/src/universal/Collapsible/index.ios.tsx +16 -0
  169. package/src/universal/Collapsible/index.tsx +58 -0
  170. package/src/universal/Collapsible/types.ts +25 -0
  171. package/src/universal/Column/index.tsx +3 -1
  172. package/src/universal/Host/index.tsx +69 -5
  173. package/src/universal/Host/types.ts +70 -0
  174. package/src/universal/List/index.android.tsx +44 -0
  175. package/src/universal/List/index.ios.tsx +19 -0
  176. package/src/universal/List/index.tsx +26 -0
  177. package/src/universal/List/types.ts +28 -0
  178. package/src/universal/ListItem/ListItem.android.tsx +52 -0
  179. package/src/universal/ListItem/ListItem.ios.tsx +58 -0
  180. package/src/universal/ListItem/ListItem.tsx +72 -0
  181. package/src/universal/ListItem/ListItemSlots.tsx +66 -0
  182. package/src/universal/ListItem/index.ts +15 -0
  183. package/src/universal/ListItem/types.ts +67 -0
  184. package/src/universal/Picker/Picker.android.tsx +69 -0
  185. package/src/universal/Picker/Picker.ios.tsx +45 -0
  186. package/src/universal/Picker/Picker.tsx +52 -0
  187. package/src/universal/Picker/PickerItem.tsx +27 -0
  188. package/src/universal/Picker/index.ts +11 -0
  189. package/src/universal/Picker/types.ts +79 -0
  190. package/src/universal/index.ts +4 -0
  191. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.md5 +0 -1
  192. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha1 +0 -1
  193. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha256 +0 -1
  194. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7-sources.jar.sha512 +0 -1
  195. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar +0 -0
  196. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.md5 +0 -1
  197. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha1 +0 -1
  198. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha256 +0 -1
  199. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.aar.sha512 +0 -1
  200. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.md5 +0 -1
  201. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha1 +0 -1
  202. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha256 +0 -1
  203. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.module.sha512 +0 -1
  204. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.md5 +0 -1
  205. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha1 +0 -1
  206. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha256 +0 -1
  207. package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.7/expo.modules.ui-56.0.7.pom.sha512 +0 -1
  208. package/src/community/bottom-sheet/CLAUDE.md +0 -55
@@ -0,0 +1,52 @@
1
+ import { ListItem as ComposeListItem, Text } from '@expo/ui/jetpack-compose';
2
+ import { clickable } from '@expo/ui/jetpack-compose/modifiers';
3
+ import { Children, type ReactNode } from 'react';
4
+
5
+ import { extractListItemSlots } from './ListItemSlots';
6
+ import type { ListItemProps } from './types';
7
+
8
+ // Compose hosts can't render raw strings — they need a `Text` composable.
9
+ // Wrap any string/number node so consumers can pass plain strings via shorthand props or compound children.
10
+ function wrapStrings(node: ReactNode): ReactNode {
11
+ if (node == null || typeof node === 'boolean') return node;
12
+ if (typeof node === 'string' || typeof node === 'number') return <Text>{node}</Text>;
13
+ if (Array.isArray(node)) return Children.map(node, wrapStrings);
14
+ return node;
15
+ }
16
+
17
+ /**
18
+ * Android implementation of `ListItem`.
19
+ * Delegates to Material 3 `ListItem` and applies `clickable` for tap handling.
20
+ */
21
+ export function ListItem(props: ListItemProps) {
22
+ const { children, onPress, leading: leadingProp, trailing: trailingProp, supportingText } = props;
23
+ const slots = extractListItemSlots(children);
24
+ const leading = slots.leading ?? leadingProp;
25
+ const trailing = slots.trailing ?? trailingProp;
26
+ const supporting = slots.supporting ?? supportingText;
27
+
28
+ return (
29
+ <ComposeListItem modifiers={onPress ? [clickable(onPress)] : undefined}>
30
+ <ComposeListItem.HeadlineContent>
31
+ <>{wrapStrings(slots.headline)}</>
32
+ </ComposeListItem.HeadlineContent>
33
+ {supporting != null ? (
34
+ <ComposeListItem.SupportingContent>
35
+ {typeof supporting === 'string' || typeof supporting === 'number' ? (
36
+ <Text>{supporting}</Text>
37
+ ) : (
38
+ supporting
39
+ )}
40
+ </ComposeListItem.SupportingContent>
41
+ ) : null}
42
+ {leading != null ? (
43
+ <ComposeListItem.LeadingContent>{wrapStrings(leading)}</ComposeListItem.LeadingContent>
44
+ ) : null}
45
+ {trailing != null ? (
46
+ <ComposeListItem.TrailingContent>{wrapStrings(trailing)}</ComposeListItem.TrailingContent>
47
+ ) : null}
48
+ </ComposeListItem>
49
+ );
50
+ }
51
+
52
+ export * from './types';
@@ -0,0 +1,58 @@
1
+ import { Button, HStack, Spacer, Text, VStack } from '@expo/ui/swift-ui';
2
+ import { buttonStyle, contentShape, foregroundStyle, shapes } from '@expo/ui/swift-ui/modifiers';
3
+ import { Children, type ReactNode } from 'react';
4
+
5
+ import { extractListItemSlots } from './ListItemSlots';
6
+ import type { ListItemProps } from './types';
7
+
8
+ // Wrap raw strings/numbers in a SwiftUI `Text` — SwiftUI hosts can't render bare strings.
9
+ function wrapStrings(node: ReactNode): ReactNode {
10
+ if (node == null || typeof node === 'boolean') return node;
11
+ if (typeof node === 'string' || typeof node === 'number') return <Text>{node}</Text>;
12
+ if (Array.isArray(node)) return Children.map(node, wrapStrings);
13
+ return node;
14
+ }
15
+
16
+ function renderSupporting(node: ReactNode): ReactNode {
17
+ if (typeof node === 'string' || typeof node === 'number') {
18
+ return (
19
+ <Text modifiers={[foregroundStyle({ type: 'color', color: 'secondaryLabel' })]}>{node}</Text>
20
+ );
21
+ }
22
+ return node;
23
+ }
24
+
25
+ /**
26
+ * iOS implementation of `ListItem`.
27
+ * Wraps a plain SwiftUI `Button` and applies `contentShape(.rectangle())` so the full row rectangle registers taps, including the gap between slots.
28
+ */
29
+ export function ListItem(props: ListItemProps) {
30
+ const {
31
+ children,
32
+ onPress,
33
+ leading: leadingProp,
34
+ trailing: trailingProp,
35
+ supportingText,
36
+ testID,
37
+ } = props;
38
+ const slots = extractListItemSlots(children);
39
+ const leading = slots.leading ?? leadingProp;
40
+ const trailing = slots.trailing ?? trailingProp;
41
+ const supporting = slots.supporting ?? supportingText;
42
+
43
+ return (
44
+ <Button onPress={onPress} modifiers={[buttonStyle('plain')]} testID={testID}>
45
+ <HStack alignment="center" spacing={12} modifiers={[contentShape(shapes.rectangle())]}>
46
+ {wrapStrings(leading)}
47
+ <VStack alignment="leading" spacing={2}>
48
+ <>{wrapStrings(slots.headline)}</>
49
+ {supporting != null ? renderSupporting(supporting) : null}
50
+ </VStack>
51
+ <Spacer />
52
+ {wrapStrings(trailing)}
53
+ </HStack>
54
+ </Button>
55
+ );
56
+ }
57
+
58
+ export * from './types';
@@ -0,0 +1,72 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
3
+
4
+ import { extractListItemSlots } from './ListItemSlots';
5
+ import type { ListItemProps } from './types';
6
+
7
+ function renderSupporting(node: ReactNode): ReactNode {
8
+ if (typeof node === 'string') {
9
+ return <Text style={styles.supportingText}>{node}</Text>;
10
+ }
11
+ return node;
12
+ }
13
+
14
+ /**
15
+ * A tappable row in a list.
16
+ * Composes with [`List`](#list).
17
+ * Pass row content via the `leading` / `trailing` / `supportingText` shorthand props or the compound `<ListItem.Leading>` / `<ListItem.Trailing>` / `<ListItem.Supporting>` slot children.
18
+ */
19
+ export function ListItem(props: ListItemProps) {
20
+ const {
21
+ children,
22
+ onPress,
23
+ leading: leadingProp,
24
+ trailing: trailingProp,
25
+ supportingText,
26
+ testID,
27
+ } = props;
28
+ const slots = extractListItemSlots(children);
29
+ const leading = slots.leading ?? leadingProp;
30
+ const trailing = slots.trailing ?? trailingProp;
31
+ const supporting = slots.supporting ?? supportingText;
32
+
33
+ return (
34
+ <Pressable onPress={onPress} style={styles.row} testID={testID}>
35
+ {leading != null ? <View style={styles.slot}>{leading}</View> : null}
36
+ <View style={styles.main}>
37
+ <Text>{slots.headline}</Text>
38
+ {supporting != null ? renderSupporting(supporting) : null}
39
+ </View>
40
+ {trailing != null ? <View style={styles.slot}>{trailing}</View> : null}
41
+ </Pressable>
42
+ );
43
+ }
44
+
45
+ const styles = StyleSheet.create({
46
+ row: {
47
+ flexDirection: 'row',
48
+ alignItems: 'center',
49
+ gap: 12,
50
+ width: '100%',
51
+ paddingVertical: 12,
52
+ paddingHorizontal: 16,
53
+ cursor: 'pointer',
54
+ },
55
+ main: {
56
+ flexDirection: 'column',
57
+ gap: 2,
58
+ flex: 1,
59
+ minWidth: 0,
60
+ },
61
+ slot: {
62
+ flexDirection: 'row',
63
+ alignItems: 'center',
64
+ flexShrink: 0,
65
+ },
66
+ supportingText: {
67
+ fontSize: 13,
68
+ color: '#6b7280',
69
+ },
70
+ });
71
+
72
+ export * from './types';
@@ -0,0 +1,66 @@
1
+ import { Children, Fragment, isValidElement, type ReactNode } from 'react';
2
+
3
+ import type { ListItemLeadingProps, ListItemSupportingProps, ListItemTrailingProps } from './types';
4
+
5
+ /** Leading-slot marker for [`ListItem`](#listitem). */
6
+ export function Leading(props: ListItemLeadingProps) {
7
+ return props.children;
8
+ }
9
+
10
+ /** Trailing-slot marker for [`ListItem`](#listitem). */
11
+ export function Trailing(props: ListItemTrailingProps) {
12
+ return props.children;
13
+ }
14
+
15
+ /** Supporting-text-slot marker for [`ListItem`](#listitem), rendered below the headline. */
16
+ export function Supporting(props: ListItemSupportingProps) {
17
+ return props.children;
18
+ }
19
+
20
+ export type ExtractedListItemSlots = {
21
+ leading?: ReactNode;
22
+ trailing?: ReactNode;
23
+ supporting?: ReactNode;
24
+ headline: ReactNode[];
25
+ };
26
+
27
+ /**
28
+ * Walks `children`, pulls out any `<ListItem.Leading>` /
29
+ * `<ListItem.Trailing>` / `<ListItem.Supporting>` slots, and returns the
30
+ * remaining nodes as the headline content. Recurses into `React.Fragment`.
31
+ */
32
+ export function extractListItemSlots(children: ReactNode): ExtractedListItemSlots {
33
+ let leading: ReactNode | undefined;
34
+ let trailing: ReactNode | undefined;
35
+ let supporting: ReactNode | undefined;
36
+ const headline: ReactNode[] = [];
37
+
38
+ const walk = (node: ReactNode) => {
39
+ Children.forEach(node, (child) => {
40
+ if (!isValidElement(child)) {
41
+ headline.push(child);
42
+ return;
43
+ }
44
+ if (child.type === Leading) {
45
+ leading = (child.props as { children?: ReactNode }).children;
46
+ return;
47
+ }
48
+ if (child.type === Trailing) {
49
+ trailing = (child.props as { children?: ReactNode }).children;
50
+ return;
51
+ }
52
+ if (child.type === Supporting) {
53
+ supporting = (child.props as { children?: ReactNode }).children;
54
+ return;
55
+ }
56
+ if (child.type === Fragment) {
57
+ walk((child.props as { children?: ReactNode }).children);
58
+ return;
59
+ }
60
+ headline.push(child);
61
+ });
62
+ };
63
+
64
+ walk(children);
65
+ return { leading, trailing, supporting, headline };
66
+ }
@@ -0,0 +1,15 @@
1
+ import { ListItem as ListItemBase } from './ListItem';
2
+ import { Leading, Supporting, Trailing } from './ListItemSlots';
3
+ import type { ListItemLeadingProps, ListItemSupportingProps, ListItemTrailingProps } from './types';
4
+
5
+ const ListItem = ListItemBase as typeof ListItemBase & {
6
+ Leading: React.FC<ListItemLeadingProps>;
7
+ Trailing: React.FC<ListItemTrailingProps>;
8
+ Supporting: React.FC<ListItemSupportingProps>;
9
+ };
10
+ ListItem.Leading = Leading;
11
+ ListItem.Trailing = Trailing;
12
+ ListItem.Supporting = Supporting;
13
+
14
+ export { ListItem };
15
+ export * from './types';
@@ -0,0 +1,67 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Props for the [`ListItem.Leading`](#listitemleading) slot marker.
5
+ */
6
+ export interface ListItemLeadingProps {
7
+ /** Content rendered in the leading (start) slot. */
8
+ children?: ReactNode;
9
+ }
10
+
11
+ /**
12
+ * Props for the [`ListItem.Trailing`](#listitemtrailing) slot marker.
13
+ */
14
+ export interface ListItemTrailingProps {
15
+ /** Content rendered in the trailing (end) slot. */
16
+ children?: ReactNode;
17
+ }
18
+
19
+ /**
20
+ * Props for the [`ListItem.Supporting`](#listitemsupporting) slot marker.
21
+ */
22
+ export interface ListItemSupportingProps {
23
+ /** Content rendered below the headline. */
24
+ children?: ReactNode;
25
+ }
26
+
27
+ /**
28
+ * Props for the [`ListItem`](#listitem) component.
29
+ * A tappable row in a list.
30
+ */
31
+ export interface ListItemProps {
32
+ /**
33
+ * Headline content of the row.
34
+ * The remaining (non-slot) children are rendered in the headline area.
35
+ */
36
+ children?: ReactNode;
37
+
38
+ /**
39
+ * Tap handler.
40
+ * Activates over the entire row rectangle, including the empty gap between leading/headline/trailing.
41
+ */
42
+ onPress?: () => void;
43
+
44
+ /**
45
+ * Shorthand for the leading slot.
46
+ * Overridden by `<ListItem.Leading>` if both are provided.
47
+ */
48
+ leading?: ReactNode;
49
+
50
+ /**
51
+ * Shorthand for the trailing slot.
52
+ * Overridden by `<ListItem.Trailing>` if both are provided.
53
+ */
54
+ trailing?: ReactNode;
55
+
56
+ /**
57
+ * Shorthand for the supporting (sub-)text slot.
58
+ * Strings are rendered with platform-appropriate secondary styling; pass a `ReactNode` for richer content.
59
+ * Overridden by `<ListItem.Supporting>` if both are provided.
60
+ */
61
+ supportingText?: string | ReactNode;
62
+
63
+ /**
64
+ * Identifier used to locate the component in end-to-end tests.
65
+ */
66
+ testID?: string;
67
+ }
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import { extractPickerItems } from './PickerItem';
4
+ import type { PickerItemValue, PickerProps } from './types';
5
+ import { DropdownMenuItem } from '../../jetpack-compose/DropdownMenu/DropdownMenuItem';
6
+ import {
7
+ ExposedDropdownMenuBox,
8
+ ExposedDropdownMenu,
9
+ } from '../../jetpack-compose/ExposedDropdownMenuBox';
10
+ import { Text } from '../../jetpack-compose/Text';
11
+ import { TextField, type TextFieldRef } from '../../jetpack-compose/TextField';
12
+ import { menuAnchor, onVisibilityChanged } from '../../jetpack-compose/modifiers';
13
+
14
+ /**
15
+ * Android implementation of `Picker`.
16
+ * Renders a Material 3 `ExposedDropdownMenuBox`.
17
+ * `appearance` is accepted for API parity but ignored — Material 3 has no wheel-style picker.
18
+ */
19
+ export function Picker<T extends PickerItemValue>({
20
+ selectedValue,
21
+ onValueChange,
22
+ enabled = true,
23
+ children,
24
+ }: PickerProps<T>) {
25
+ const items = extractPickerItems<T>(children);
26
+ const [expanded, setExpanded] = useState(false);
27
+ const [isVisible, setIsVisible] = useState(false);
28
+ const textFieldRef = useRef<TextFieldRef>(null);
29
+
30
+ const selectedLabel = items.find((item) => item.value === selectedValue)?.label ?? '';
31
+ // The anchor `TextField` is uncontrolled — push the current label imperatively once the view is on screen.
32
+ useEffect(() => {
33
+ if (!isVisible) return;
34
+ textFieldRef.current?.setText(selectedLabel);
35
+ }, [selectedLabel, isVisible]);
36
+
37
+ return (
38
+ <ExposedDropdownMenuBox
39
+ expanded={expanded}
40
+ onExpandedChange={enabled ? setExpanded : undefined}>
41
+ <TextField
42
+ ref={textFieldRef}
43
+ readOnly
44
+ enabled={enabled}
45
+ modifiers={[menuAnchor(), onVisibilityChanged((visible) => setIsVisible(visible))]}
46
+ />
47
+ <ExposedDropdownMenu expanded={expanded} onDismissRequest={() => setExpanded(false)}>
48
+ {items.map((item) => (
49
+ <DropdownMenuItem
50
+ key={String(item.value)}
51
+ onClick={
52
+ enabled
53
+ ? () => {
54
+ onValueChange(item.value);
55
+ setExpanded(false);
56
+ }
57
+ : undefined
58
+ }>
59
+ <DropdownMenuItem.Text>
60
+ <Text>{item.label}</Text>
61
+ </DropdownMenuItem.Text>
62
+ </DropdownMenuItem>
63
+ ))}
64
+ </ExposedDropdownMenu>
65
+ </ExposedDropdownMenuBox>
66
+ );
67
+ }
68
+
69
+ export * from './types';
@@ -0,0 +1,45 @@
1
+ import { Picker as SwiftUIPicker, Text } from '@expo/ui/swift-ui';
2
+ import {
3
+ disabled as disabledModifier,
4
+ pickerStyle,
5
+ tag,
6
+ type ModifierConfig,
7
+ } from '@expo/ui/swift-ui/modifiers';
8
+
9
+ import { extractPickerItems } from './PickerItem';
10
+ import type { PickerItemValue, PickerProps } from './types';
11
+
12
+ /**
13
+ * iOS implementation of `Picker`.
14
+ * Wraps SwiftUI's `Picker` and applies the matching `.pickerStyle` for the requested `appearance`.
15
+ * Embed inside a parent `<Host>` (same as `Column` / `Row`).
16
+ */
17
+ export function Picker<T extends PickerItemValue>({
18
+ selectedValue,
19
+ onValueChange,
20
+ appearance = 'menu',
21
+ enabled = true,
22
+ children,
23
+ testID,
24
+ }: PickerProps<T>) {
25
+ const items = extractPickerItems<T>(children);
26
+ const swiftUIStyle = appearance === 'wheel' ? 'wheel' : 'menu';
27
+ const modifiers: ModifierConfig[] = [pickerStyle(swiftUIStyle)];
28
+ if (!enabled) modifiers.push(disabledModifier(true));
29
+
30
+ return (
31
+ <SwiftUIPicker
32
+ selection={selectedValue}
33
+ onSelectionChange={(value) => onValueChange(value as T)}
34
+ modifiers={modifiers}
35
+ testID={testID}>
36
+ {items.map((item) => (
37
+ <Text key={String(item.value)} modifiers={[tag(item.value)]}>
38
+ {item.label}
39
+ </Text>
40
+ ))}
41
+ </SwiftUIPicker>
42
+ );
43
+ }
44
+
45
+ export * from './types';
@@ -0,0 +1,52 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { StyleSheet, unstable_createElement, type ViewProps } from 'react-native';
3
+
4
+ import { extractPickerItems } from './PickerItem';
5
+ import type { PickerItemValue, PickerProps } from './types';
6
+
7
+ const Select = (props: Omit<ComponentProps<'select'>, 'style'> & { style?: ViewProps['style'] }) =>
8
+ unstable_createElement('select', props);
9
+
10
+ /**
11
+ * A single-selection input.
12
+ * Declare options via `<Picker.Item label value />` children.
13
+ */
14
+ export function Picker<T extends PickerItemValue>({
15
+ selectedValue,
16
+ onValueChange,
17
+ enabled = true,
18
+ children,
19
+ testID,
20
+ }: PickerProps<T>) {
21
+ const items = extractPickerItems<T>(children);
22
+
23
+ return (
24
+ <Select
25
+ disabled={!enabled}
26
+ value={String(selectedValue)}
27
+ onChange={(e) => {
28
+ const index = e.target.selectedIndex;
29
+ const item = items[index];
30
+ if (item) onValueChange(item.value);
31
+ }}
32
+ style={styles.select}
33
+ data-testid={testID}>
34
+ {items.map((item) => (
35
+ <option key={String(item.value)} value={String(item.value)}>
36
+ {item.label}
37
+ </option>
38
+ ))}
39
+ </Select>
40
+ );
41
+ }
42
+
43
+ const styles = StyleSheet.create({
44
+ select: {
45
+ fontFamily:
46
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
47
+ fontSize: 14,
48
+ margin: 0,
49
+ },
50
+ });
51
+
52
+ export * from './types';
@@ -0,0 +1,27 @@
1
+ import { Children, isValidElement, type ReactElement, type ReactNode } from 'react';
2
+
3
+ import type { ExtractedPickerItem, PickerItemProps, PickerItemValue } from './types';
4
+
5
+ /**
6
+ * Data-only option marker for [`Picker`](#picker).
7
+ * Used via the compound API: `<Picker.Item label="…" value={…} />`.
8
+ */
9
+ export function PickerItem<T extends PickerItemValue>(_props: PickerItemProps<T>): null {
10
+ return null;
11
+ }
12
+
13
+ // Walk `<Picker>` children and extract each `<Picker.Item>`'s props.
14
+ // Non-`PickerItem` children are ignored.
15
+ export function extractPickerItems<T extends PickerItemValue>(
16
+ children: ReactNode
17
+ ): ExtractedPickerItem<T>[] {
18
+ return Children.toArray(children)
19
+ .filter(
20
+ (child): child is ReactElement<PickerItemProps<T>> =>
21
+ isValidElement(child) && child.type === PickerItem
22
+ )
23
+ .map((child) => ({
24
+ label: child.props.label,
25
+ value: child.props.value,
26
+ }));
27
+ }
@@ -0,0 +1,11 @@
1
+ import { Picker as PickerBase } from './Picker';
2
+ import { PickerItem } from './PickerItem';
3
+ import type { PickerItemProps, PickerItemValue } from './types';
4
+
5
+ const Picker = PickerBase as typeof PickerBase & {
6
+ Item: <T extends PickerItemValue>(props: PickerItemProps<T>) => null;
7
+ };
8
+ Picker.Item = PickerItem;
9
+
10
+ export { Picker };
11
+ export * from './types';
@@ -0,0 +1,79 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ /**
4
+ * The type of values a [`Picker.Item`](#pickeritem) can carry.
5
+ */
6
+ export type PickerItemValue = string | number;
7
+
8
+ /**
9
+ * Visual appearance of the picker.
10
+ *
11
+ * - `'menu'` — Compact button that opens a popup/dropdown on tap.
12
+ * Cross-platform default.
13
+ * - `'wheel'` — Scrollable rotor UI that's always visible inline.
14
+ * iOS only; on Android and web this falls back to the platform's default dropdown.
15
+ */
16
+ export type PickerAppearance = 'wheel' | 'menu';
17
+
18
+ /**
19
+ * Props for the [`Picker.Item`](#pickeritem) component.
20
+ * A data-only marker used to declare options inside a [`Picker`](#picker).
21
+ */
22
+ export interface PickerItemProps<T extends PickerItemValue = PickerItemValue> {
23
+ /**
24
+ * Display text for this option.
25
+ */
26
+ label: string;
27
+
28
+ /**
29
+ * Value passed to `onValueChange` when this option is selected.
30
+ */
31
+ value: T;
32
+ }
33
+
34
+ /**
35
+ * Props for the [`Picker`](#picker) component, a single-selection input.
36
+ */
37
+ export interface PickerProps<T extends PickerItemValue = PickerItemValue> {
38
+ /**
39
+ * The currently selected value.
40
+ * Must match the `value` of one of the `<Picker.Item>` children.
41
+ */
42
+ selectedValue: T;
43
+
44
+ /**
45
+ * Called when the user selects an option.
46
+ */
47
+ onValueChange: (value: T) => void;
48
+
49
+ /**
50
+ * Visual appearance of the picker.
51
+ * See [`PickerAppearance`](#pickerappearance).
52
+ * @default 'menu'
53
+ */
54
+ appearance?: PickerAppearance;
55
+
56
+ /**
57
+ * Whether the picker accepts input.
58
+ * @default true
59
+ */
60
+ enabled?: boolean;
61
+
62
+ /**
63
+ * `<Picker.Item>` children that declare the available options.
64
+ */
65
+ children?: ReactNode;
66
+
67
+ /**
68
+ * Identifier used to locate the component in end-to-end tests.
69
+ */
70
+ testID?: string;
71
+ }
72
+
73
+ /**
74
+ * Internal: extracted item data from `<Picker.Item>` children.
75
+ */
76
+ export interface ExtractedPickerItem<T extends PickerItemValue = PickerItemValue> {
77
+ label: string;
78
+ value: T;
79
+ }
@@ -8,8 +8,12 @@ export * from './Switch';
8
8
  export * from './Slider';
9
9
  export * from './Checkbox';
10
10
  export * from './BottomSheet';
11
+ export * from './Collapsible';
11
12
  export * from './FieldGroup';
12
13
  export * from './Icon';
14
+ export * from './List';
15
+ export * from './ListItem';
16
+ export * from './Picker';
13
17
  export * from './Spacer';
14
18
  export * from './State';
15
19
  export { TextInput, type TextInputProps, type TextInputRef } from './TextInput';
@@ -1 +0,0 @@
1
- a534f795e8ea46629011b2f8939608619b954ff1
@@ -1 +0,0 @@
1
- 213a1693673392a710d27fef37d99ef030c8a8783b4ccaa971ea914588dc5241
@@ -1 +0,0 @@
1
- 26e067865e2bbdd9f770d670f8212c2a309bee05009f722931b339ca9f76c235bbed2286a876d9c7a7b0ddb0e4b152617031f487c047ae78d0672ecc2649c6be
@@ -1 +0,0 @@
1
- ea2684e30595f6e153476b3bd38b40f7
@@ -1 +0,0 @@
1
- 63505c031167b651fcd2b3ed1cddbcb5a014bfa8
@@ -1 +0,0 @@
1
- 05e8833b0c8b22442fa1d70cd203ec9f986fd4e9d8541393c843779bca1bed6a
@@ -1 +0,0 @@
1
- 2214ac6df49634ec222a3563eb91161f0ba5977ea600f354473f9dd5d166dd79b5230c634d59c15fe027d77f9010fd3d984718d4c392adf444acf687e40e076e
@@ -1 +0,0 @@
1
- aa1f1c7ec449e0d76d64fc008bf3af9d9928e0da
@@ -1 +0,0 @@
1
- e15fb389c9d21d25522483c1e58b1cc0a57ec2b0ee710c49bd79528c3faa467a
@@ -1 +0,0 @@
1
- dec030fd37a92340a477da17d8da83a39d8b2f1cdfbf66754d56f9c6b7152d125aeaa8438f0b7247d945acc9723dcceaa74a22e059ded7bd2dc784cad21732d0