@expo/ui 0.0.2 → 0.1.0-alpha.0

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 (93) hide show
  1. package/BottomSheet/index.ios.tsx +34 -0
  2. package/BottomSheet/index.tsx +12 -0
  3. package/{components/Button → Button}/index.tsx +47 -16
  4. package/Button/types.ts +54 -0
  5. package/CHANGELOG.md +40 -0
  6. package/ColorPicker/index.tsx +54 -0
  7. package/ContextMenu/index.android.tsx +72 -0
  8. package/{components/ContextMenu → ContextMenu}/index.tsx +80 -30
  9. package/{components/ContextMenu → ContextMenu}/utils.ts +4 -0
  10. package/DatePicker/index.tsx +97 -0
  11. package/Gauge/index.tsx +73 -0
  12. package/Label/index.ios.tsx +16 -0
  13. package/Label/index.tsx +34 -0
  14. package/List/index.ios.tsx +35 -0
  15. package/List/index.tsx +99 -0
  16. package/{components/Picker → Picker}/index.tsx +32 -19
  17. package/Progress/index.tsx +48 -0
  18. package/Section/index.ios.tsx +12 -0
  19. package/Section/index.tsx +19 -0
  20. package/{components/Slider → Slider}/index.tsx +19 -10
  21. package/{components/Switch → Switch}/index.tsx +37 -54
  22. package/TextInput/index.tsx +108 -0
  23. package/android/build.gradle +2 -2
  24. package/android/src/main/java/expo/modules/ui/DatePickerView.kt +160 -0
  25. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +10 -0
  26. package/android/src/main/java/expo/modules/ui/PickerView.kt +93 -31
  27. package/android/src/main/java/expo/modules/ui/ProgressView.kt +83 -0
  28. package/android/src/main/java/expo/modules/ui/SwitchView.kt +56 -39
  29. package/android/src/main/java/expo/modules/ui/TextInputView.kt +75 -0
  30. package/android/src/main/java/expo/modules/ui/Utils.kt +17 -0
  31. package/android/src/main/java/expo/modules/ui/button/Button.kt +33 -4
  32. package/android/src/main/java/expo/modules/ui/menu/ContextMenu.kt +49 -32
  33. package/android/src/main/java/expo/modules/ui/menu/ContextMenuRecords.kt +10 -3
  34. package/build/{components/Button → Button}/index.d.ts +36 -13
  35. package/build/Button/index.d.ts.map +1 -0
  36. package/build/Button/types.d.ts +5 -0
  37. package/build/Button/types.d.ts.map +1 -0
  38. package/build/{components/ContextMenu → ContextMenu}/index.d.ts +68 -19
  39. package/build/ContextMenu/index.d.ts.map +1 -0
  40. package/build/ContextMenu/utils.d.ts.map +1 -0
  41. package/build/Label/index.d.ts +28 -0
  42. package/build/Label/index.d.ts.map +1 -0
  43. package/build/List/index.d.ts +87 -0
  44. package/build/List/index.d.ts.map +1 -0
  45. package/build/Picker/index.d.ts +76 -0
  46. package/build/Picker/index.d.ts.map +1 -0
  47. package/build/Section/index.d.ts +16 -0
  48. package/build/Section/index.d.ts.map +1 -0
  49. package/build/{components/Slider → Slider}/index.d.ts +18 -10
  50. package/build/Slider/index.d.ts.map +1 -0
  51. package/build/{components/Switch → Switch}/index.d.ts +18 -36
  52. package/build/Switch/index.d.ts.map +1 -0
  53. package/build/src/types.d.ts +15 -0
  54. package/build/src/types.d.ts.map +1 -0
  55. package/expo-module.config.json +3 -3
  56. package/ios/BottomSheetView.swift +86 -0
  57. package/ios/Button/Button.swift +52 -44
  58. package/ios/Button/ButtonProps.swift +1 -0
  59. package/ios/ColorPickerView.swift +54 -0
  60. package/ios/ContextMenu/ContextMenu.swift +71 -8
  61. package/ios/ContextMenu/ContextMenuRecords.swift +6 -0
  62. package/ios/DateTimePickerView.swift +85 -0
  63. package/ios/ExpoUIModule.swift +10 -0
  64. package/ios/Gauge/Gauge.swift +49 -0
  65. package/ios/Gauge/GaugeProps.swift +28 -0
  66. package/ios/Label.swift +29 -0
  67. package/ios/List.swift +127 -0
  68. package/ios/PickerView.swift +38 -29
  69. package/ios/ProgressView.swift +40 -0
  70. package/ios/SectionView.swift +12 -5
  71. package/ios/SliderView.swift +6 -2
  72. package/ios/SwitchView.swift +6 -3
  73. package/ios/TextInput/TextInputView.swift +83 -0
  74. package/package.json +4 -6
  75. package/src/types.ts +18 -0
  76. package/tsconfig.json +1 -1
  77. package/build/components/Button/index.d.ts.map +0 -1
  78. package/build/components/ContextMenu/index.d.ts.map +0 -1
  79. package/build/components/ContextMenu/utils.d.ts.map +0 -1
  80. package/build/components/Picker/index.d.ts +0 -65
  81. package/build/components/Picker/index.d.ts.map +0 -1
  82. package/build/components/Section/index.d.ts +0 -12
  83. package/build/components/Section/index.d.ts.map +0 -1
  84. package/build/components/Section/index.ios.d.ts +0 -8
  85. package/build/components/Section/index.ios.d.ts.map +0 -1
  86. package/build/components/Slider/index.d.ts.map +0 -1
  87. package/build/components/Switch/index.d.ts.map +0 -1
  88. package/build/src/index.d.ts +0 -13
  89. package/build/src/index.d.ts.map +0 -1
  90. package/components/Section/index.ios.tsx +0 -58
  91. package/components/Section/index.tsx +0 -56
  92. package/src/index.ts +0 -22
  93. /package/build/{components/ContextMenu → ContextMenu}/utils.d.ts +0 -0
@@ -0,0 +1,34 @@
1
+ import { requireNativeView } from 'expo';
2
+ import { Dimensions, NativeSyntheticEvent, View } from 'react-native';
3
+
4
+ import { BottomSheetProps } from '.';
5
+
6
+ type NativeBottomSheetProps = Omit<BottomSheetProps, 'onIsOpenedChange'> & {
7
+ onIsOpenedChange: (event: NativeSyntheticEvent<{ isOpened: boolean }>) => void;
8
+ };
9
+
10
+ const BottomSheetNativeView: React.ComponentType<NativeBottomSheetProps> = requireNativeView(
11
+ 'ExpoUI',
12
+ 'BottomSheetView'
13
+ );
14
+
15
+ export function transformBottomSheetProps(props: BottomSheetProps): NativeBottomSheetProps {
16
+ return {
17
+ ...props,
18
+ onIsOpenedChange: ({ nativeEvent: { isOpened } }) => {
19
+ props?.onIsOpenedChange?.(isOpened);
20
+ },
21
+ };
22
+ }
23
+
24
+ export function BottomSheet(props: BottomSheetProps) {
25
+ const { width } = Dimensions.get('window');
26
+ return (
27
+ <View>
28
+ <BottomSheetNativeView
29
+ style={{ position: 'absolute', width }}
30
+ {...transformBottomSheetProps(props)}
31
+ />
32
+ </View>
33
+ );
34
+ }
@@ -0,0 +1,12 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ export type BottomSheetProps = {
4
+ style?: StyleProp<ViewStyle>;
5
+ children: any;
6
+ isOpened: boolean;
7
+ onIsOpenedChange: (isOpened: boolean) => void;
8
+ };
9
+
10
+ export function BottomSheet({ children }: BottomSheetProps) {
11
+ return children;
12
+ }
@@ -1,7 +1,8 @@
1
1
  import { requireNativeView } from 'expo';
2
- import { StyleProp, StyleSheet, ViewStyle } from 'react-native';
2
+ import { Platform, StyleProp, StyleSheet, ViewStyle } from 'react-native';
3
3
 
4
- import { ViewEvent } from '../../src';
4
+ import { MaterialIcon } from './types';
5
+ import { ViewEvent } from '../src/types';
5
6
 
6
7
  /**
7
8
  * The role of the button.
@@ -14,10 +15,12 @@ export type ButtonRole = 'default' | 'cancel' | 'destructive';
14
15
 
15
16
  /**
16
17
  * The built-in button styles available on iOS and Android.
18
+ *
17
19
  * Common styles:
18
20
  * - `default` - The default system button style.
19
21
  * - `bordered` - A button with a light fill. On Android equivalent to `FilledTonalButton`.
20
22
  * - `borderless` - A button with no background or border. On Android equivalent to `TextButton`.
23
+ *
21
24
  * Apple-only styles:
22
25
  * - `borderedProminent` - A bordered button with a prominent appearance.
23
26
  * - `plain` - A button with no border or background and a less prominent text.
@@ -26,12 +29,10 @@ export type ButtonRole = 'default' | 'cancel' | 'destructive';
26
29
  * - `accessoryBarAction` - A button style for accessory bar actions.
27
30
  * - `card` - A button style for cards.
28
31
  * - `link` - A button style for links.
32
+ *
29
33
  * Android-only styles:
30
34
  * - `outlined` - A button with an outline.
31
35
  * - `elevated` - A filled button with a shadow.
32
- *
33
- * @platform android
34
- * @platform ios
35
36
  */
36
37
  export type ButtonVariant =
37
38
  // Common
@@ -50,6 +51,17 @@ export type ButtonVariant =
50
51
  | 'outlined'
51
52
  | 'elevated';
52
53
 
54
+ /**
55
+ * Colors for button's core elements.
56
+ * @platform android
57
+ */
58
+ export type ButtonElementColors = {
59
+ containerColor?: string;
60
+ contentColor?: string;
61
+ disabledContainerColor?: string;
62
+ disabledContentColor?: string;
63
+ };
64
+
53
65
  export type ButtonProps = {
54
66
  /**
55
67
  * A callback that is called when the button is pressed.
@@ -57,9 +69,12 @@ export type ButtonProps = {
57
69
  onPress?: () => void;
58
70
  /**
59
71
  * A string describing the system image to display in the button.
60
- * @platform ios
72
+ * Uses Material Icons on Android and SF Symbols on iOS.
61
73
  */
62
- systemImage?: string;
74
+ systemImage?: {
75
+ ios?: string;
76
+ android?: MaterialIcon;
77
+ };
63
78
  /**
64
79
  * Indicated the role of the button.
65
80
  * @platform ios
@@ -81,21 +96,27 @@ export type ButtonProps = {
81
96
  * Colors for button's core elements.
82
97
  * @platform android
83
98
  */
84
- elementColors?: {
85
- containerColor?: string;
86
- contentColor?: string;
87
- disabledContainerColor?: string;
88
- disabledContentColor?: string;
89
- };
99
+ elementColors?: ButtonElementColors;
90
100
  /**
91
101
  * Button color.
92
102
  */
93
103
  color?: string;
104
+ /**
105
+ * Disabled state of the button.
106
+ */
107
+ disabled?: boolean;
94
108
  };
95
109
 
96
- export type NativeButtonProps = Omit<ButtonProps, 'role' | 'onPress' | 'children'> & {
110
+ /**
111
+ * @hidden
112
+ */
113
+ export type NativeButtonProps = Omit<
114
+ ButtonProps,
115
+ 'role' | 'onPress' | 'children' | 'systemImage'
116
+ > & {
97
117
  buttonRole?: ButtonRole;
98
118
  text: string;
119
+ systemImage?: string;
99
120
  } & ViewEvent<'onButtonPressed', void>;
100
121
 
101
122
  // We have to work around the `role` and `onPress` props being reserved by React Native.
@@ -104,12 +125,16 @@ const ButtonNativeView: React.ComponentType<NativeButtonProps> = requireNativeVi
104
125
  'Button'
105
126
  );
106
127
 
128
+ /**
129
+ * @hidden
130
+ */
107
131
  export function transformButtonProps(props: ButtonProps): NativeButtonProps {
108
- const { role, children, onPress, ...restProps } = props;
132
+ const { role, children, onPress, systemImage, ...restProps } = props;
109
133
  return {
110
134
  ...restProps,
111
135
  text: children ?? '',
112
136
  buttonRole: role,
137
+ systemImage: systemImage?.[Platform.OS as 'ios' | 'android'],
113
138
  onButtonPressed: onPress,
114
139
  elementColors: props.elementColors
115
140
  ? props.elementColors
@@ -121,12 +146,18 @@ export function transformButtonProps(props: ButtonProps): NativeButtonProps {
121
146
  };
122
147
  }
123
148
 
149
+ /**
150
+ * Displays a native button component.
151
+ */
124
152
  export function Button(props: ButtonProps) {
125
153
  // Min height from https://m3.material.io/components/buttons/specs, minWidth
126
154
  return (
127
155
  <ButtonNativeView
128
156
  {...transformButtonProps(props)}
129
- style={StyleSheet.compose({ minWidth: 80, minHeight: 40 }, props.style)}
157
+ style={StyleSheet.compose(
158
+ Platform.OS === 'android' ? { minWidth: 80, minHeight: 40 } : {},
159
+ props.style
160
+ )}
130
161
  />
131
162
  );
132
163
  }
@@ -0,0 +1,54 @@
1
+ type Variant = 'rounded' | 'twotone' | 'outlined' | 'filled' | 'sharp';
2
+
3
+ type Icon =
4
+ | 'AccountBox'
5
+ | 'AccountCircle'
6
+ | 'Add'
7
+ | 'AddCircle'
8
+ | 'ArrowBack'
9
+ | 'ArrowDropDown'
10
+ | 'ArrowForward'
11
+ | 'Build'
12
+ | 'Call'
13
+ | 'Check'
14
+ | 'CheckCircle'
15
+ | 'Clear'
16
+ | 'Close'
17
+ | 'Create'
18
+ | 'DateRange'
19
+ | 'Delete'
20
+ | 'Done'
21
+ | 'Edit'
22
+ | 'Email'
23
+ | 'ExitToApp'
24
+ | 'Face'
25
+ | 'Favorite'
26
+ | 'FavoriteBorder'
27
+ | 'Home'
28
+ | 'Info'
29
+ | 'KeyboardArrowDown'
30
+ | 'KeyboardArrowLeft'
31
+ | 'KeyboardArrowRight'
32
+ | 'KeyboardArrowUp'
33
+ | 'List'
34
+ | 'LocationOn'
35
+ | 'Lock'
36
+ | 'MailOutline'
37
+ | 'Menu'
38
+ | 'MoreVert'
39
+ | 'Notifications'
40
+ | 'Person'
41
+ | 'Phone'
42
+ | 'Place'
43
+ | 'PlayArrow'
44
+ | 'Refresh'
45
+ | 'Search'
46
+ | 'Send'
47
+ | 'Settings'
48
+ | 'Share'
49
+ | 'ShoppingCart'
50
+ | 'Star'
51
+ | 'ThumbUp'
52
+ | 'Warning';
53
+
54
+ export type MaterialIcon = `${Variant}.${Icon}`;
package/CHANGELOG.md CHANGED
@@ -10,23 +10,63 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.1.0-alpha.0 — 2025-04-04
14
+
15
+ ### 🛠 Breaking changes
16
+
17
+ - upgrade RN to 0.78 ([#35050](https://github.com/expo/expo/pull/35050) by [@vonovak](https://github.com/vonovak))
18
+
19
+ ### 🎉 New features
20
+
21
+ - [iOS] Add bottom sheet. ([#35455](https://github.com/expo/expo/pull/35455) by [@aleqsio](https://github.com/aleqsio))
22
+ - [iOS] Add support for `pallete` and `inline` pickers. ([#35435](https://github.com/expo/expo/pull/35435) by [@aleqsio](https://github.com/aleqsio))
23
+ - Add TextInput for Android. ([#35228](https://github.com/expo/expo/pull/35228) by [@aleqsio](https://github.com/aleqsio))
24
+ - Add context menu previews. ([#35369](https://github.com/expo/expo/pull/35369) by [@aleqsio](https://github.com/aleqsio)).
25
+ - Add TextInput for iOS. ([#35098](https://github.com/expo/expo/pull/35098) by [@aleqsio](https://github.com/aleqsio))
26
+ - Add `disabled` button prop. ([#35115](https://github.com/expo/expo/pull/35115) by [@andrew-levy](https://github.com/andrew-levy))
27
+ - Add Radio option to Picker Component for Android. ([#34629](https://github.com/expo/expo/pull/34629)) by [@benjaminkomen](https://github.com/benjaminkomen)
28
+ - Add color support for `ContextMenu` components. ([#34787](https://github.com/expo/expo/pull/34787) by [@behenate](https://github.com/behenate))
29
+ - Adds `DateTimePicker` component. ([#34883](https://github.com/expo/expo/pull/34883) by [@alanjhughes](https://github.com/alanjhughes))
30
+ - Add CircularProgress and LinearProgress component. ([#34907](https://github.com/expo/expo/pull/34907) by [@janicduplessis](https://github.com/janicduplessis))
31
+ - Add Gauge component ([#35032](https://github.com/expo/expo/pull/35032) by [@jakex7](https://github.com/jakex7))
32
+ - Add List and Label component ([#35222](https://github.com/expo/expo/pull/35222) by [@Pflaumenbaum](https://github.com/Pflaumenbaum))
33
+
34
+ ### 🐛 Bug fixes
35
+
36
+ - [iOS] Fix tvOS breakage. ([#35146](https://github.com/expo/expo/pull/35146) by [@douglowder](https://github.com/douglowder))
37
+
38
+ ### 💡 Others
39
+
40
+ - Refactor imports, update docs ([#35819](https://github.com/expo/expo/pull/35819) by [@aleqsio](https://github.com/aleqsio))
41
+ - Drop section polyfill for Android ([#35305](https://github.com/expo/expo/pull/35305) by [@aleqsio](https://github.com/aleqsio))
42
+ - Standardize platform key ordering in `expo-module.config.json`. ([#35003](https://github.com/expo/expo/pull/35003) by [@reichhartd](https://github.com/reichhartd))
43
+ - Dismiss context menu when a menu item is tapped on Android ([#35365](https://github.com/expo/expo/pull/35365) by [@fobos531](https://github.com/fobos531))
44
+ - Migrated SwiftUI views with backward compatible `WithHostingView`. ([#35553](https://github.com/expo/expo/pull/35553) by [@kudo](https://github.com/kudo))
45
+
13
46
  ## 0.0.2 — 2025-02-11
14
47
 
15
48
  ### 🎉 New features
16
49
 
50
+ - Add `systemImage` prop to Android `Button` component. ([#34862](https://github.com/expo/expo/pull/34862) by [@andrew-levy](https://github.com/andrew-levy))
51
+ - Add `UnwrappedChildren` for nested SwiftUI views. ([#34954](https://github.com/expo/expo/pull/34954) by [@andrew-levy](https://github.com/andrew-levy))
17
52
  - Add `color` and `elementColors` props. ([#34666](https://github.com/expo/expo/pull/34666) by [@aleqsio](https://github.com/aleqsio))
18
53
  - Add Button component.([#34340](https://github.com/expo/expo/pull/34340) by [@behenate](https://github.com/behenate))
19
54
  - Apple TV support and source restructure. ([#34532](https://github.com/expo/expo/pull/34532) by [@douglowder](https://github.com/douglowder))
20
55
  - Add `ContextMenu` component. ([#34553](https://github.com/expo/expo/pull/34553) by [@behenate](https://github.com/behenate))
56
+ - Add `ColorPicker` component. ([#34819](https://github.com/expo/expo/pull/34819) by [@andrew-levy](https://github.com/andrew-levy))
21
57
 
22
58
  ### 🐛 Bug fixes
23
59
 
60
+ - Fix flex/width/height props for autosized components. ([#34922](https://github.com/expo/expo/pull/34922) by [@aleqsio](https://github.com/aleqsio))
24
61
  - Fix tvOS compilation. ([#34730](https://github.com/expo/expo/pull/34730) by [@douglowder](https://github.com/douglowder))
62
+ - Exclude `ColorPicker` on tvOS. ([#34929](https://github.com/expo/expo/pull/34929) by [@alanjhughes](https://github.com/alanjhughes))
25
63
 
26
64
  ### 💡 Others
27
65
 
66
+ - Add docs ([#34808](https://github.com/expo/expo/pull/34808) by [@aleqsio](https://github.com/aleqsio))
28
67
  - [apple] Migrate remaining `expo-module.config.json` to unified platform syntax. ([#34445](https://github.com/expo/expo/pull/34445) by [@reichhartd](https://github.com/reichhartd))
29
68
  - Rename the events for the `Switch` component. ([#34577](https://github.com/expo/expo/pull/34577) by [@behenate](https://github.com/behenate))
69
+ - Allow lower case section titles ([#35113](https://github.com/expo/expo/pull/35113) by [@Pflaumenbaum](https://github.com/Pflaumenbaum))
30
70
 
31
71
  ## 0.0.1 — 2025-01-21
32
72
 
@@ -0,0 +1,54 @@
1
+ import { requireNativeView } from 'expo';
2
+ import { useCallback } from 'react';
3
+ import { NativeSyntheticEvent, processColor, StyleProp, ViewStyle } from 'react-native';
4
+
5
+ /**
6
+ * Props for the ColorPicker component.
7
+ */
8
+ export type ColorPickerProps = {
9
+ /**
10
+ * The currently selected color in the format `#RRGGBB` or `#RRGGBBAA`.
11
+ */
12
+ selection: string | null;
13
+ /**
14
+ * A label displayed on the ColorPicker.
15
+ */
16
+ label?: string;
17
+ /**
18
+ * Callback function that is called when a new color is selected.
19
+ */
20
+ onValueChanged?: (value: string) => void;
21
+ /**
22
+ * Optional style to apply to the ColorPicker component.
23
+ */
24
+ style?: StyleProp<ViewStyle>;
25
+ /**
26
+ * Whether the color picker should support opacity.
27
+ */
28
+ supportsOpacity?: boolean;
29
+ };
30
+
31
+ type OnValueChangedEvent = NativeSyntheticEvent<{ value: string }>;
32
+
33
+ const ColorPickerNativeView: React.ComponentType<
34
+ Omit<ColorPickerProps, 'selection' | 'onValueChanged'> & {
35
+ selection: ReturnType<typeof processColor>;
36
+ onValueChanged: (event: OnValueChangedEvent) => void;
37
+ }
38
+ > = requireNativeView('ExpoUI', 'ColorPickerView');
39
+
40
+ export function ColorPicker({ selection, onValueChanged, ...restProps }: ColorPickerProps) {
41
+ const onNativeValueChanged = useCallback(
42
+ (event: OnValueChangedEvent) => {
43
+ onValueChanged?.(event.nativeEvent.value);
44
+ },
45
+ [onValueChanged]
46
+ );
47
+ return (
48
+ <ColorPickerNativeView
49
+ selection={processColor(selection || '')}
50
+ onValueChanged={onNativeValueChanged}
51
+ {...restProps}
52
+ />
53
+ );
54
+ }
@@ -0,0 +1,72 @@
1
+ import { requireNativeView } from 'expo';
2
+ import { Children, useMemo } from 'react';
3
+ import { NativeSyntheticEvent } from 'react-native';
4
+
5
+ import { ContextMenuProps, EventHandlers, NativeMenuProps } from '.';
6
+ import { transformChildrenToElementArray } from './utils';
7
+
8
+ const MenuNativeView: React.ComponentType<NativeMenuProps> = requireNativeView(
9
+ 'ExpoUI',
10
+ 'ContextMenu'
11
+ );
12
+
13
+ export function Submenu() {
14
+ return <></>;
15
+ }
16
+
17
+ export function Items() {
18
+ return <></>;
19
+ }
20
+ Items.tag = 'Items';
21
+
22
+ export function Trigger(props: { children: React.ReactNode }) {
23
+ return <></>;
24
+ }
25
+ Trigger.tag = 'Trigger';
26
+
27
+ export function Preview(props: { children: React.ReactNode }) {
28
+ return <></>;
29
+ }
30
+
31
+ function ContextMenu(props: ContextMenuProps) {
32
+ const eventHandlersMap: EventHandlers = {};
33
+ const initialChildren = Children.map(
34
+ props.children as any,
35
+ (c: { type: { tag: string }; props: { children: React.ReactNode } }) =>
36
+ c.type.tag === Items.tag ? c.props.children : null
37
+ );
38
+ const processedElements = useMemo(
39
+ () => transformChildrenToElementArray(initialChildren, eventHandlersMap),
40
+ [initialChildren]
41
+ );
42
+
43
+ const activationElement = Children.map(
44
+ props.children as any,
45
+ (c: { type: { tag: string }; props: { children: React.ReactNode } }) =>
46
+ c.type.tag === Trigger.tag ? c.props.children : null
47
+ );
48
+
49
+ const createEventHandler =
50
+ (handlerType: string) => (e: NativeSyntheticEvent<{ contextMenuElementID: string }>) => {
51
+ const handler = eventHandlersMap[e.nativeEvent.contextMenuElementID]?.[handlerType];
52
+ handler?.(e);
53
+ };
54
+
55
+ return (
56
+ <MenuNativeView
57
+ style={props.style}
58
+ elements={processedElements}
59
+ onContextMenuButtonPressed={createEventHandler('onPress')}
60
+ onContextMenuSwitchValueChanged={createEventHandler('onValueChange')}
61
+ onContextMenuPickerOptionSelected={createEventHandler('onOptionSelected')}
62
+ {...props}>
63
+ {activationElement}
64
+ </MenuNativeView>
65
+ );
66
+ }
67
+
68
+ ContextMenu.Trigger = Trigger;
69
+ ContextMenu.Preview = Preview;
70
+ ContextMenu.Items = Items;
71
+
72
+ export { ContextMenu };
@@ -1,11 +1,26 @@
1
1
  import { requireNativeView } from 'expo';
2
- import React, { ReactElement, ReactNode, useMemo } from 'react';
2
+ import { ComponentType, Children, ReactElement, ReactNode, useMemo } from 'react';
3
3
  import { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native';
4
4
 
5
+ import { MenuElement, transformChildrenToElementArray } from './utils';
5
6
  import { ButtonProps } from '../Button';
6
7
  import { PickerProps } from '../Picker';
7
8
  import { SwitchProps } from '../Switch';
8
- import { MenuElement, transformChildrenToElementArray } from './utils';
9
+
10
+ const MenuNativeView: ComponentType<NativeMenuProps> = requireNativeView(
11
+ 'ExpoUI',
12
+ 'ContextMenu'
13
+ );
14
+
15
+ const MenuNativeTriggerView: ComponentType<object> = requireNativeView(
16
+ 'ExpoUI',
17
+ 'ContextMenuActivationElement'
18
+ );
19
+
20
+ const MenuNativePreviewView: ComponentType<object> = requireNativeView(
21
+ 'ExpoUI',
22
+ 'ContextMenuPreview'
23
+ );
9
24
 
10
25
  type SubmenuElement =
11
26
  | ReactElement<ButtonProps>
@@ -13,18 +28,21 @@ type SubmenuElement =
13
28
  | ReactElement<PickerProps>
14
29
  | ReactElement<SubmenuProps>;
15
30
 
16
- type ContentChildren = SubmenuElement | SubmenuElement[];
17
-
18
31
  export type ContextMenuContentProps = {
19
- children: ContentChildren;
32
+ children: SubmenuElement | SubmenuElement[];
20
33
  };
21
34
 
22
- export type EventHandlers = {
23
- [key: string]: {
24
- [key: string]: (event: NativeSyntheticEvent<any>) => void;
25
- };
26
- };
35
+ /**
36
+ * @hidden
37
+ */
38
+ export type EventHandlers = Record<
39
+ string,
40
+ Record<string, (event: NativeSyntheticEvent<any>) => void>
41
+ >;
27
42
 
43
+ /**
44
+ * @hidden
45
+ */
28
46
  export type ContextMenuElementBase = { contextMenuElementID: string };
29
47
 
30
48
  /**
@@ -38,13 +56,6 @@ export type ActivationMethod = 'singlePress' | 'longPress';
38
56
  * Props of the `ContextMenu` component.
39
57
  */
40
58
  export type ContextMenuProps = {
41
- /**
42
- * Items visible inside the context menu. The items should be wrapped in a `React.Fragment`.
43
- * `Button`, `Switch` and `Submenu` components are supported on both Android and iOS.
44
- * The `Picker` component is supported only on iOS. Remember to use components from the `@expo/ui` library.
45
- */
46
- Items: React.ReactElement<ContextMenuContentProps>;
47
-
48
59
  /**
49
60
  * Determines how the context menu will be activated.
50
61
  *
@@ -58,6 +69,13 @@ export type ContextMenuProps = {
58
69
  */
59
70
  children: ReactNode;
60
71
 
72
+ /**
73
+ * The color of the container holding the context menu items.
74
+ *
75
+ * @platform android
76
+ */
77
+ color?: string;
78
+
61
79
  /**
62
80
  * Optional styles to apply to the `ContextMenu`
63
81
  */
@@ -71,14 +89,17 @@ export type SubmenuProps = {
71
89
  /**
72
90
  * The button that will be used to expand the submenu. On Android the `text` prop of the `Button` will be used as a section title.
73
91
  */
74
- button: React.ReactElement<ButtonProps>;
92
+ button: ReactElement<ButtonProps>;
75
93
  /**
76
94
  * Children of the submenu. Only `Button`, `Switch`, `Picker` and `Submenu` elements should be used.
77
95
  */
78
- children: React.ReactNode;
96
+ children: ReactNode;
79
97
  };
80
98
 
81
- type NativeMenuProps = ContextMenuProps & {
99
+ /**
100
+ * @hidden
101
+ */
102
+ export type NativeMenuProps = ContextMenuProps & {
82
103
  elements: MenuElement[];
83
104
  onContextMenuButtonPressed: (
84
105
  event: NativeSyntheticEvent<{ contextMenuElementID: string }>
@@ -98,11 +119,6 @@ type NativeMenuProps = ContextMenuProps & {
98
119
  ) => void;
99
120
  };
100
121
 
101
- const MenuNativeView: React.ComponentType<NativeMenuProps> = requireNativeView(
102
- 'ExpoUI',
103
- 'ContextMenu'
104
- );
105
-
106
122
  /**
107
123
  * The `Submenu` component is used to create a nested context menu. Submenus can be infinitely nested.
108
124
  * Android does not support nesting in the context menu. All the submenus will be flat-mapped into a single level with multiple titled sections.
@@ -111,6 +127,30 @@ export function Submenu(props: SubmenuProps) {
111
127
  return <></>;
112
128
  }
113
129
 
130
+ /**
131
+ * Items visible inside the context menu. Pass input components as immidiate children of the tag.
132
+ * `Button`, `Switch` and `Submenu` components are supported on both Android and iOS.
133
+ * The `Picker` component is supported only on iOS. Remember to use components from the `@expo/ui` library.
134
+ */
135
+ export function Items(props: { children: React.ReactNode }) {
136
+ return <></>;
137
+ }
138
+ Items.tag = 'Items';
139
+ /**
140
+ * The component visible all the time that triggers the menu when tapped or long-pressed.
141
+ */
142
+ export function Trigger(props: { children: React.ReactNode }) {
143
+ return <MenuNativeTriggerView {...props} />;
144
+ }
145
+
146
+ /**
147
+ * The component visible above the menu when it is opened.
148
+ * @platform ios
149
+ */
150
+ export function Preview(props: { children: React.ReactNode }) {
151
+ return <MenuNativePreviewView {...props} />;
152
+ }
153
+
114
154
  /**
115
155
  * `ContextMenu` allows you to create a context menu, which can be used to provide additional options to the user.
116
156
  *
@@ -120,18 +160,22 @@ export function Submenu(props: SubmenuProps) {
120
160
  * - Android does not support nesting in the context menu. All the submenus will be flat-mapped into a single level with multiple sections. The `title` prop of the `Button`, which opens the submenu on iOS will be used as a section title.
121
161
  * - Android does not support showing a `Picker` element in the context menu.
122
162
  */
123
- export function ContextMenu(props: ContextMenuProps) {
163
+ function ContextMenu(props: ContextMenuProps) {
124
164
  const eventHandlersMap: EventHandlers = {};
125
- const initialChildren = props.Items.props.children;
165
+ const initialChildren = Children.map(
166
+ props.children as any,
167
+ (c: { type: { tag: string }; props: { children: React.ReactNode } }) =>
168
+ c.type.tag === Items.tag ? c.props.children : null
169
+ );
126
170
  const processedElements = useMemo(
127
171
  () => transformChildrenToElementArray(initialChildren, eventHandlersMap),
128
172
  [initialChildren]
129
173
  );
130
174
 
131
175
  const createEventHandler =
132
- (handlerType: string) => (e: NativeSyntheticEvent<{ contextMenuElementID: string }>) => {
133
- const handler = eventHandlersMap[e.nativeEvent.contextMenuElementID]?.[handlerType];
134
- handler?.(e);
176
+ (handlerType: string) => (event: NativeSyntheticEvent<{ contextMenuElementID: string }>) => {
177
+ const handler = eventHandlersMap[event.nativeEvent.contextMenuElementID]?.[handlerType];
178
+ handler?.(event);
135
179
  };
136
180
 
137
181
  return (
@@ -145,3 +189,9 @@ export function ContextMenu(props: ContextMenuProps) {
145
189
  />
146
190
  );
147
191
  }
192
+
193
+ ContextMenu.Trigger = Trigger;
194
+ ContextMenu.Preview = Preview;
195
+ ContextMenu.Items = Items;
196
+
197
+ export { ContextMenu };
@@ -52,18 +52,22 @@ function processChildElement(
52
52
  const uuid = expo.uuidv4();
53
53
 
54
54
  if (child.type === Button) {
55
+ // @ts-expect-error TODO TS2345: Argument of type unknown is not assignable to parameter of type SubmenuProps
55
56
  return createButtonElement(uuid, child.props, eventHandlersMap);
56
57
  }
57
58
 
58
59
  if (child.type === Switch) {
60
+ // @ts-expect-error TODO TS2345: Argument of type unknown is not assignable to parameter of type SubmenuProps
59
61
  return createSwitchElement(uuid, child.props, eventHandlersMap);
60
62
  }
61
63
 
62
64
  if (child.type === Picker) {
65
+ // @ts-expect-error TODO TS2345: Argument of type unknown is not assignable to parameter of type SubmenuProps
63
66
  return createPickerElement(uuid, child.props, eventHandlersMap);
64
67
  }
65
68
 
66
69
  if (isSubmenuComponent(child)) {
70
+ // @ts-expect-error TODO TS2345: Argument of type unknown is not assignable to parameter of type SubmenuProps
67
71
  return createSubmenuElement(uuid, child.props, eventHandlersMap);
68
72
  }
69
73