@tamagui/create-menu 2.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cjs/MenuPredefined.cjs +182 -0
- package/dist/cjs/MenuPredefined.js +162 -0
- package/dist/cjs/MenuPredefined.js.map +6 -0
- package/dist/cjs/MenuPredefined.native.js +185 -0
- package/dist/cjs/MenuPredefined.native.js.map +1 -0
- package/dist/cjs/createBaseMenu.cjs +927 -0
- package/dist/cjs/createBaseMenu.js +724 -0
- package/dist/cjs/createBaseMenu.js.map +6 -0
- package/dist/cjs/createBaseMenu.native.js +1105 -0
- package/dist/cjs/createBaseMenu.native.js.map +1 -0
- package/dist/cjs/createNativeMenu/createNativeMenu.cjs +224 -0
- package/dist/cjs/createNativeMenu/createNativeMenu.js +172 -0
- package/dist/cjs/createNativeMenu/createNativeMenu.js.map +6 -0
- package/dist/cjs/createNativeMenu/createNativeMenu.native.js +287 -0
- package/dist/cjs/createNativeMenu/createNativeMenu.native.js.map +1 -0
- package/dist/cjs/createNativeMenu/createNativeMenuTypes.cjs +16 -0
- package/dist/cjs/createNativeMenu/createNativeMenuTypes.js +14 -0
- package/dist/cjs/createNativeMenu/createNativeMenuTypes.js.map +6 -0
- package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js +19 -0
- package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
- package/dist/cjs/createNativeMenu/index.cjs +19 -0
- package/dist/cjs/createNativeMenu/index.js +16 -0
- package/dist/cjs/createNativeMenu/index.js.map +6 -0
- package/dist/cjs/createNativeMenu/index.native.js +22 -0
- package/dist/cjs/createNativeMenu/index.native.js.map +1 -0
- package/dist/cjs/createNativeMenu/utils.cjs +68 -0
- package/dist/cjs/createNativeMenu/utils.js +66 -0
- package/dist/cjs/createNativeMenu/utils.js.map +6 -0
- package/dist/cjs/createNativeMenu/utils.native.js +94 -0
- package/dist/cjs/createNativeMenu/utils.native.js.map +1 -0
- package/dist/cjs/createNativeMenu/withNativeMenu.cjs +37 -0
- package/dist/cjs/createNativeMenu/withNativeMenu.js +30 -0
- package/dist/cjs/createNativeMenu/withNativeMenu.js.map +6 -0
- package/dist/cjs/createNativeMenu/withNativeMenu.native.js +43 -0
- package/dist/cjs/createNativeMenu/withNativeMenu.native.js.map +1 -0
- package/dist/cjs/index.cjs +30 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/index.native.js +33 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/esm/MenuPredefined.js +147 -0
- package/dist/esm/MenuPredefined.js.map +6 -0
- package/dist/esm/MenuPredefined.mjs +159 -0
- package/dist/esm/MenuPredefined.mjs.map +1 -0
- package/dist/esm/MenuPredefined.native.js +159 -0
- package/dist/esm/MenuPredefined.native.js.map +1 -0
- package/dist/esm/createBaseMenu.js +729 -0
- package/dist/esm/createBaseMenu.js.map +6 -0
- package/dist/esm/createBaseMenu.mjs +893 -0
- package/dist/esm/createBaseMenu.mjs.map +1 -0
- package/dist/esm/createBaseMenu.native.js +1068 -0
- package/dist/esm/createBaseMenu.native.js.map +1 -0
- package/dist/esm/createNativeMenu/createNativeMenu.js +150 -0
- package/dist/esm/createNativeMenu/createNativeMenu.js.map +6 -0
- package/dist/esm/createNativeMenu/createNativeMenu.mjs +190 -0
- package/dist/esm/createNativeMenu/createNativeMenu.mjs.map +1 -0
- package/dist/esm/createNativeMenu/createNativeMenu.native.js +250 -0
- package/dist/esm/createNativeMenu/createNativeMenu.native.js.map +1 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.js +1 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.js.map +6 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs +2 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs.map +1 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js +2 -0
- package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
- package/dist/esm/createNativeMenu/index.js +3 -0
- package/dist/esm/createNativeMenu/index.js.map +6 -0
- package/dist/esm/createNativeMenu/index.mjs +3 -0
- package/dist/esm/createNativeMenu/index.mjs.map +1 -0
- package/dist/esm/createNativeMenu/index.native.js +3 -0
- package/dist/esm/createNativeMenu/index.native.js.map +1 -0
- package/dist/esm/createNativeMenu/utils.js +47 -0
- package/dist/esm/createNativeMenu/utils.js.map +6 -0
- package/dist/esm/createNativeMenu/utils.mjs +29 -0
- package/dist/esm/createNativeMenu/utils.mjs.map +1 -0
- package/dist/esm/createNativeMenu/utils.native.js +52 -0
- package/dist/esm/createNativeMenu/utils.native.js.map +1 -0
- package/dist/esm/createNativeMenu/withNativeMenu.js +15 -0
- package/dist/esm/createNativeMenu/withNativeMenu.js.map +6 -0
- package/dist/esm/createNativeMenu/withNativeMenu.mjs +14 -0
- package/dist/esm/createNativeMenu/withNativeMenu.mjs.map +1 -0
- package/dist/esm/createNativeMenu/withNativeMenu.native.js +17 -0
- package/dist/esm/createNativeMenu/withNativeMenu.native.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +6 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +6 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/jsx/MenuPredefined.js +147 -0
- package/dist/jsx/MenuPredefined.js.map +6 -0
- package/dist/jsx/MenuPredefined.mjs +159 -0
- package/dist/jsx/MenuPredefined.mjs.map +1 -0
- package/dist/jsx/MenuPredefined.native.js +185 -0
- package/dist/jsx/MenuPredefined.native.js.map +1 -0
- package/dist/jsx/createBaseMenu.js +729 -0
- package/dist/jsx/createBaseMenu.js.map +6 -0
- package/dist/jsx/createBaseMenu.mjs +893 -0
- package/dist/jsx/createBaseMenu.mjs.map +1 -0
- package/dist/jsx/createBaseMenu.native.js +1105 -0
- package/dist/jsx/createBaseMenu.native.js.map +1 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.js +150 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.js.map +6 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.mjs +190 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.mjs.map +1 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.native.js +287 -0
- package/dist/jsx/createNativeMenu/createNativeMenu.native.js.map +1 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.js +1 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.js.map +6 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs +2 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs.map +1 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js +19 -0
- package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
- package/dist/jsx/createNativeMenu/index.js +3 -0
- package/dist/jsx/createNativeMenu/index.js.map +6 -0
- package/dist/jsx/createNativeMenu/index.mjs +3 -0
- package/dist/jsx/createNativeMenu/index.mjs.map +1 -0
- package/dist/jsx/createNativeMenu/index.native.js +22 -0
- package/dist/jsx/createNativeMenu/index.native.js.map +1 -0
- package/dist/jsx/createNativeMenu/utils.js +47 -0
- package/dist/jsx/createNativeMenu/utils.js.map +6 -0
- package/dist/jsx/createNativeMenu/utils.mjs +29 -0
- package/dist/jsx/createNativeMenu/utils.mjs.map +1 -0
- package/dist/jsx/createNativeMenu/utils.native.js +94 -0
- package/dist/jsx/createNativeMenu/utils.native.js.map +1 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.js +15 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.js.map +6 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.mjs +14 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.mjs.map +1 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.native.js +43 -0
- package/dist/jsx/createNativeMenu/withNativeMenu.native.js.map +1 -0
- package/dist/jsx/index.js +8 -0
- package/dist/jsx/index.js.map +6 -0
- package/dist/jsx/index.mjs +6 -0
- package/dist/jsx/index.mjs.map +1 -0
- package/dist/jsx/index.native.js +33 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/package.json +80 -0
- package/src/MenuPredefined.tsx +195 -0
- package/src/createBaseMenu.tsx +1703 -0
- package/src/createNativeMenu/createNativeMenu.tsx +372 -0
- package/src/createNativeMenu/createNativeMenuTypes.ts +214 -0
- package/src/createNativeMenu/index.tsx +7 -0
- package/src/createNativeMenu/utils.tsx +150 -0
- package/src/createNativeMenu/withNativeMenu.tsx +38 -0
- package/src/index.tsx +9 -0
- package/types/MenuPredefined.d.ts +28 -0
- package/types/MenuPredefined.d.ts.map +1 -0
- package/types/createBaseMenu.d.ts +207 -0
- package/types/createBaseMenu.d.ts.map +1 -0
- package/types/createNativeMenu/createNativeMenu.d.ts +42 -0
- package/types/createNativeMenu/createNativeMenu.d.ts.map +1 -0
- package/types/createNativeMenu/createNativeMenuTypes.d.ts +188 -0
- package/types/createNativeMenu/createNativeMenuTypes.d.ts.map +1 -0
- package/types/createNativeMenu/index.d.ts +4 -0
- package/types/createNativeMenu/index.d.ts.map +1 -0
- package/types/createNativeMenu/utils.d.ts +38 -0
- package/types/createNativeMenu/utils.d.ts.map +1 -0
- package/types/createNativeMenu/withNativeMenu.d.ts +9 -0
- package/types/createNativeMenu/withNativeMenu.d.ts.map +1 -0
- package/types/index.d.ts +6 -0
- package/types/index.d.ts.map +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createNativeMenu - provides native menu implementation for React Native
|
|
3
|
+
*
|
|
4
|
+
* On Web: Returns empty stub components (withNativeMenu will use the web components instead)
|
|
5
|
+
* On Native: Uses Zeego for native menus (Credit to nandorojo/Zeego)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isWeb, withStaticProperties, isIos } from '@tamagui/web'
|
|
9
|
+
import type { FC } from 'react'
|
|
10
|
+
import React from 'react'
|
|
11
|
+
import type {
|
|
12
|
+
ContextMenuPreviewProps,
|
|
13
|
+
MenuArrowProps,
|
|
14
|
+
MenuCheckboxItemProps,
|
|
15
|
+
MenuContentProps,
|
|
16
|
+
MenuGroupProps,
|
|
17
|
+
MenuItemIconProps,
|
|
18
|
+
MenuItemImageProps,
|
|
19
|
+
MenuItemIndicatorProps,
|
|
20
|
+
MenuItemProps,
|
|
21
|
+
MenuItemSubtitleProps,
|
|
22
|
+
MenuItemTitleProps,
|
|
23
|
+
MenuLabelProps,
|
|
24
|
+
MenuProps,
|
|
25
|
+
MenuSeparatorProps,
|
|
26
|
+
MenuSubContentProps,
|
|
27
|
+
MenuSubProps,
|
|
28
|
+
MenuSubTriggerProps,
|
|
29
|
+
MenuTriggerProps,
|
|
30
|
+
} from './createNativeMenuTypes'
|
|
31
|
+
|
|
32
|
+
export type NativeMenuComponents = {
|
|
33
|
+
Menu: FC<MenuProps> & {
|
|
34
|
+
Trigger: FC<MenuTriggerProps>
|
|
35
|
+
Content: FC<MenuContentProps>
|
|
36
|
+
Item: FC<MenuItemProps>
|
|
37
|
+
ItemTitle: FC<MenuItemTitleProps>
|
|
38
|
+
ItemSubtitle: FC<MenuItemSubtitleProps>
|
|
39
|
+
SubTrigger: FC<MenuSubTriggerProps>
|
|
40
|
+
Group: FC<MenuGroupProps>
|
|
41
|
+
ItemIcon: FC<MenuItemIconProps>
|
|
42
|
+
Separator: FC<MenuSeparatorProps>
|
|
43
|
+
CheckboxItem: FC<MenuCheckboxItemProps>
|
|
44
|
+
ItemIndicator: FC<MenuItemIndicatorProps>
|
|
45
|
+
ItemImage: FC<MenuItemImageProps>
|
|
46
|
+
Label: FC<MenuLabelProps>
|
|
47
|
+
Arrow: FC<MenuArrowProps>
|
|
48
|
+
Sub: FC<MenuSubProps>
|
|
49
|
+
SubContent: FC<MenuSubContentProps>
|
|
50
|
+
Preview: FC<ContextMenuPreviewProps>
|
|
51
|
+
Portal: FC<{ children: React.ReactNode }>
|
|
52
|
+
RadioGroup: FC<{ children: React.ReactNode }>
|
|
53
|
+
RadioItem: FC<{ children: React.ReactNode }>
|
|
54
|
+
Auxiliary: FC<any>
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const createNativeMenu = (
|
|
59
|
+
MenuType: 'ContextMenu' | 'Menu'
|
|
60
|
+
): NativeMenuComponents => {
|
|
61
|
+
// On web, return empty stubs - withNativeMenu will use the web components passed to it
|
|
62
|
+
if (isWeb) {
|
|
63
|
+
const Menu = {} as FC<MenuProps>
|
|
64
|
+
const Trigger = {} as FC<MenuTriggerProps>
|
|
65
|
+
const Content = {} as FC<MenuContentProps>
|
|
66
|
+
const Preview = {} as FC<ContextMenuPreviewProps>
|
|
67
|
+
const Item = {} as FC<MenuItemProps>
|
|
68
|
+
const ItemIcon = {} as FC<MenuItemIconProps>
|
|
69
|
+
const ItemImage = {} as FC<MenuItemImageProps>
|
|
70
|
+
const SubTrigger = {} as FC<MenuSubTriggerProps>
|
|
71
|
+
const ItemTitle = {} as FC<MenuItemTitleProps>
|
|
72
|
+
const ItemSubtitle = {} as FC<MenuItemSubtitleProps>
|
|
73
|
+
const Group = {} as FC<MenuGroupProps>
|
|
74
|
+
const Separator = {} as FC<MenuSeparatorProps>
|
|
75
|
+
const CheckboxItem = {} as FC<MenuCheckboxItemProps>
|
|
76
|
+
const ItemIndicator = {} as FC<MenuItemIndicatorProps>
|
|
77
|
+
const Label = {} as FC<MenuLabelProps>
|
|
78
|
+
const Arrow = {} as FC<MenuArrowProps>
|
|
79
|
+
const Sub = {} as FC<MenuSubProps>
|
|
80
|
+
const SubContent = {} as FC<MenuSubContentProps>
|
|
81
|
+
const Portal = {} as FC<{ children: React.ReactNode }>
|
|
82
|
+
const RadioGroup = {} as FC<{ children: React.ReactNode }>
|
|
83
|
+
const RadioItem = {} as FC<{ children: React.ReactNode }>
|
|
84
|
+
const Auxiliary = {} as FC<any>
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
Menu: withStaticProperties(Menu, {
|
|
88
|
+
Trigger,
|
|
89
|
+
Content,
|
|
90
|
+
Item,
|
|
91
|
+
ItemTitle,
|
|
92
|
+
ItemSubtitle,
|
|
93
|
+
SubTrigger,
|
|
94
|
+
Group,
|
|
95
|
+
ItemIcon,
|
|
96
|
+
Separator,
|
|
97
|
+
CheckboxItem,
|
|
98
|
+
ItemIndicator,
|
|
99
|
+
ItemImage,
|
|
100
|
+
Label,
|
|
101
|
+
Arrow,
|
|
102
|
+
Sub,
|
|
103
|
+
SubContent,
|
|
104
|
+
Preview,
|
|
105
|
+
Portal,
|
|
106
|
+
RadioGroup,
|
|
107
|
+
RadioItem,
|
|
108
|
+
Auxiliary,
|
|
109
|
+
}),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ===========================================
|
|
114
|
+
// Native implementation using Zeego
|
|
115
|
+
// ===========================================
|
|
116
|
+
|
|
117
|
+
const ZeegoDropdownMenu = require('zeego/dropdown-menu')
|
|
118
|
+
const ZeegoContextMenu = require('zeego/context-menu')
|
|
119
|
+
|
|
120
|
+
const isContextMenu = MenuType === 'ContextMenu'
|
|
121
|
+
const ZeegoMenu = isContextMenu ? ZeegoContextMenu : ZeegoDropdownMenu
|
|
122
|
+
|
|
123
|
+
// Map displayName patterns to Zeego components
|
|
124
|
+
const COMPONENT_MAP: Record<string, any> = {
|
|
125
|
+
SubContent: ZeegoMenu.SubContent,
|
|
126
|
+
Content: ZeegoMenu.Content,
|
|
127
|
+
Sub: ZeegoMenu.Sub,
|
|
128
|
+
Group: ZeegoMenu.Group,
|
|
129
|
+
SubTrigger: ZeegoMenu.SubTrigger,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Components that need children transformation (containers)
|
|
133
|
+
const CONTAINER_TYPES = ['SubContent', 'Content', 'Sub', 'Group']
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get component type from displayName (handles styled wrappers)
|
|
137
|
+
*/
|
|
138
|
+
const getComponentType = (displayName: string): string | null => {
|
|
139
|
+
// Check in specific order (SubContent before Content, SubTrigger before Trigger)
|
|
140
|
+
for (const type of [
|
|
141
|
+
'SubContent',
|
|
142
|
+
'SubTrigger',
|
|
143
|
+
'Content',
|
|
144
|
+
'Sub',
|
|
145
|
+
'Group',
|
|
146
|
+
'CheckboxItem',
|
|
147
|
+
]) {
|
|
148
|
+
if (displayName === type || displayName.includes(`(${type})`)) {
|
|
149
|
+
return type
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if component looks like a menu Item (has onSelect/textValue but isn't a special component)
|
|
157
|
+
*/
|
|
158
|
+
const isItemLike = (props: Record<string, any>, displayName: string): boolean => {
|
|
159
|
+
// If it matches a known component type, it's not a generic Item
|
|
160
|
+
if (getComponentType(displayName)) {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
return 'onSelect' in props || 'textValue' in props
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if displayName matches Portal
|
|
168
|
+
*/
|
|
169
|
+
const isPortal = (displayName: string): boolean => {
|
|
170
|
+
return displayName === 'Portal' || displayName.includes('Portal')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Transform children tree for Zeego compatibility:
|
|
175
|
+
* - Flatten Portal wrappers
|
|
176
|
+
* - Recurse into containers (Content, Sub, Group, SubContent)
|
|
177
|
+
* - Convert styled Items to Zeego Items
|
|
178
|
+
* - Reverse children on iOS only for DropdownMenu at Content/SubContent level
|
|
179
|
+
*/
|
|
180
|
+
const transformForZeego = (
|
|
181
|
+
children: React.ReactNode,
|
|
182
|
+
shouldReverseOnIos = false
|
|
183
|
+
): React.ReactNode => {
|
|
184
|
+
const result: React.ReactNode[] = []
|
|
185
|
+
|
|
186
|
+
React.Children.forEach(children, (child) => {
|
|
187
|
+
if (!React.isValidElement(child)) {
|
|
188
|
+
result.push(child)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const displayName = (child.type as any)?.displayName || ''
|
|
193
|
+
const props = child.props as Record<string, any>
|
|
194
|
+
|
|
195
|
+
// Flatten Portal
|
|
196
|
+
if (isPortal(displayName)) {
|
|
197
|
+
const portalChildren = transformForZeego(props.children, false)
|
|
198
|
+
React.Children.forEach(portalChildren, (c) => result.push(c))
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle known component types (containers, SubTrigger, CheckboxItem)
|
|
203
|
+
const componentType = getComponentType(displayName)
|
|
204
|
+
|
|
205
|
+
// normalizing checked/value props
|
|
206
|
+
if (componentType === 'CheckboxItem') {
|
|
207
|
+
const { checked, onCheckedChange, value, onValueChange, children, ...rest } =
|
|
208
|
+
props
|
|
209
|
+
|
|
210
|
+
const finalValue = value ?? (checked ? 'on' : 'off')
|
|
211
|
+
const finalOnValueChange =
|
|
212
|
+
onValueChange ??
|
|
213
|
+
(onCheckedChange && ((v: string) => onCheckedChange(v === 'on')))
|
|
214
|
+
|
|
215
|
+
const cleanChildren = React.Children.map(children, (child) => {
|
|
216
|
+
if (!React.isValidElement(child)) return child
|
|
217
|
+
|
|
218
|
+
const childDisplayName = (child.type as any)?.displayName || ''
|
|
219
|
+
// If it's an ItemIndicator, remove it (return null) so we don't double render the checkmark
|
|
220
|
+
if (childDisplayName.includes('ItemIndicator')) {
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
return child
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
result.push(
|
|
227
|
+
React.createElement(
|
|
228
|
+
ZeegoMenu.CheckboxItem,
|
|
229
|
+
{
|
|
230
|
+
...rest,
|
|
231
|
+
key: child.key,
|
|
232
|
+
value: finalValue,
|
|
233
|
+
onValueChange: finalOnValueChange,
|
|
234
|
+
},
|
|
235
|
+
cleanChildren
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (componentType) {
|
|
242
|
+
const { children: childChildren, ...restProps } = props
|
|
243
|
+
const isContainer = CONTAINER_TYPES.includes(componentType)
|
|
244
|
+
// Only reverse children of Content and SubContent (not Group or Sub)
|
|
245
|
+
const shouldReverseChildren =
|
|
246
|
+
componentType === 'Content' || componentType === 'SubContent'
|
|
247
|
+
result.push(
|
|
248
|
+
React.createElement(
|
|
249
|
+
COMPONENT_MAP[componentType],
|
|
250
|
+
{ ...restProps, key: child.key },
|
|
251
|
+
isContainer
|
|
252
|
+
? transformForZeego(childChildren, shouldReverseChildren)
|
|
253
|
+
: childChildren
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Convert Item-like components to Zeego Items
|
|
260
|
+
if (isItemLike(props, displayName)) {
|
|
261
|
+
const { children: itemChildren, ...itemProps } = props
|
|
262
|
+
result.push(
|
|
263
|
+
React.createElement(
|
|
264
|
+
ZeegoMenu.Item,
|
|
265
|
+
{ ...itemProps, key: child.key },
|
|
266
|
+
itemChildren
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Pass through everything else
|
|
273
|
+
result.push(child)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// iOS DropdownMenu (not ContextMenu) displays menu items in reverse order
|
|
277
|
+
// Only reverse for Menu component, not ContextMenu
|
|
278
|
+
if (isIos && shouldReverseOnIos && !isContextMenu) {
|
|
279
|
+
result.reverse()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return result
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ===========================================
|
|
286
|
+
// Component definitions (typed wrappers around Zeego)
|
|
287
|
+
// ===========================================
|
|
288
|
+
|
|
289
|
+
// Direct Zeego pass-throughs with proper types
|
|
290
|
+
const Trigger: FC<MenuTriggerProps> = ZeegoMenu.Trigger
|
|
291
|
+
const Content: FC<MenuContentProps> = ZeegoMenu.Content
|
|
292
|
+
const Item: FC<MenuItemProps> = ZeegoMenu.Item
|
|
293
|
+
const ItemTitle: FC<MenuItemTitleProps> = ZeegoMenu.ItemTitle
|
|
294
|
+
const ItemSubtitle: FC<MenuItemSubtitleProps> = ZeegoMenu.ItemSubtitle
|
|
295
|
+
const ItemIcon: FC<MenuItemIconProps> = ZeegoMenu.ItemIcon
|
|
296
|
+
const ItemImage: FC<MenuItemImageProps> = ZeegoMenu.ItemImage
|
|
297
|
+
const ItemIndicator: FC<MenuItemIndicatorProps> = ZeegoMenu.ItemIndicator
|
|
298
|
+
const Group: FC<MenuGroupProps> = ZeegoMenu.Group
|
|
299
|
+
const Label: FC<MenuLabelProps> = ZeegoMenu.Label
|
|
300
|
+
const Separator: FC<MenuSeparatorProps> = ZeegoMenu.Separator
|
|
301
|
+
const Sub: FC<MenuSubProps> = ZeegoMenu.Sub
|
|
302
|
+
const SubTrigger: FC<MenuSubTriggerProps> = ZeegoMenu.SubTrigger
|
|
303
|
+
const SubContent: FC<MenuSubContentProps> = ZeegoMenu.SubContent
|
|
304
|
+
|
|
305
|
+
// Custom components
|
|
306
|
+
const Portal: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
|
|
307
|
+
Portal.displayName = 'Portal'
|
|
308
|
+
|
|
309
|
+
const Arrow: FC<MenuArrowProps> = () => null
|
|
310
|
+
Arrow.displayName = 'Arrow'
|
|
311
|
+
|
|
312
|
+
const RadioGroup: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
|
|
313
|
+
RadioGroup.displayName = `${MenuType}RadioGroup`
|
|
314
|
+
|
|
315
|
+
const RadioItem: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
|
|
316
|
+
RadioItem.displayName = `${MenuType}RadioItem`
|
|
317
|
+
// CheckboxItem wrapper to normalize checked/value props
|
|
318
|
+
const CheckboxItem: FC<MenuCheckboxItemProps> = (props) => null
|
|
319
|
+
CheckboxItem.displayName = 'CheckboxItem'
|
|
320
|
+
|
|
321
|
+
// Context menu specific
|
|
322
|
+
const Preview: FC<ContextMenuPreviewProps> = isContextMenu
|
|
323
|
+
? ZeegoContextMenu.Preview
|
|
324
|
+
: () => null
|
|
325
|
+
Preview.displayName = `${MenuType}Preview`
|
|
326
|
+
|
|
327
|
+
const Auxiliary: FC<ContextMenuPreviewProps> = isContextMenu
|
|
328
|
+
? ZeegoContextMenu.Auxiliary
|
|
329
|
+
: () => null
|
|
330
|
+
Auxiliary.displayName = `${MenuType}Auxiliary`
|
|
331
|
+
|
|
332
|
+
// Main Menu component
|
|
333
|
+
const Menu: FC<MenuProps> = ({ children, onOpenChange, onOpenWillChange }) => {
|
|
334
|
+
const rootProps: Record<string, unknown> = { onOpenChange }
|
|
335
|
+
if (isContextMenu && onOpenWillChange) {
|
|
336
|
+
rootProps.onOpenWillChange = onOpenWillChange
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return <ZeegoMenu.Root {...rootProps}>{transformForZeego(children)}</ZeegoMenu.Root>
|
|
340
|
+
}
|
|
341
|
+
Menu.displayName = MenuType
|
|
342
|
+
|
|
343
|
+
// ===========================================
|
|
344
|
+
// Export
|
|
345
|
+
// ===========================================
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
Menu: withStaticProperties(Menu, {
|
|
349
|
+
Trigger,
|
|
350
|
+
Content,
|
|
351
|
+
Item,
|
|
352
|
+
ItemTitle,
|
|
353
|
+
ItemSubtitle,
|
|
354
|
+
ItemIcon,
|
|
355
|
+
ItemImage,
|
|
356
|
+
ItemIndicator,
|
|
357
|
+
Group,
|
|
358
|
+
Label,
|
|
359
|
+
Separator,
|
|
360
|
+
Sub,
|
|
361
|
+
SubTrigger,
|
|
362
|
+
SubContent,
|
|
363
|
+
CheckboxItem,
|
|
364
|
+
Portal,
|
|
365
|
+
RadioGroup,
|
|
366
|
+
RadioItem,
|
|
367
|
+
Arrow,
|
|
368
|
+
Preview,
|
|
369
|
+
Auxiliary,
|
|
370
|
+
}),
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { ImageProps } from 'react-native'
|
|
2
|
+
import type { SFSymbol } from 'sf-symbols-typescript'
|
|
3
|
+
|
|
4
|
+
type ImageOptions = {
|
|
5
|
+
tint?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type MenuProps = {
|
|
9
|
+
children: React.ReactNode
|
|
10
|
+
native?: boolean
|
|
11
|
+
onOpenChange?: (isOpen: boolean) => void
|
|
12
|
+
/**
|
|
13
|
+
* Callback function indicating that the menu intends to open or close. Passes a `willOpen` boolean argument indicating whether it is opening or closing.
|
|
14
|
+
* Unlike `onOpenChange`, this is called before the animation begins.
|
|
15
|
+
* @platform `ios`
|
|
16
|
+
*/
|
|
17
|
+
onOpenWillChange?: (willOpen: boolean) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Props for the auxiliary view that can be shown alongside a context menu on iOS
|
|
22
|
+
* @platform ios
|
|
23
|
+
*/
|
|
24
|
+
export type ContextMenuAuxiliaryProps = {
|
|
25
|
+
height?: number
|
|
26
|
+
width?: number
|
|
27
|
+
anchorPosition?: 'top' | 'bottom' | 'automatic'
|
|
28
|
+
children: React.ReactNode | ((options: { dismissMenu: () => void }) => React.ReactNode)
|
|
29
|
+
onDidShow?: () => void
|
|
30
|
+
marginWithScreenEdge?: number
|
|
31
|
+
onWillShow?: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type MenuTriggerProps = {
|
|
35
|
+
children: React.ReactElement
|
|
36
|
+
asChild?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Determine whether the menu should open on `press` or `longPress`. Defaults to `press` for `Menu` and `longPress` for `ContextMenu`.
|
|
39
|
+
*
|
|
40
|
+
* Only applies for `ios` and `android`.
|
|
41
|
+
*/
|
|
42
|
+
action?: 'press' | 'longPress'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type MenuContentProps = {
|
|
46
|
+
children: React.ReactNode
|
|
47
|
+
}
|
|
48
|
+
export type ContextMenuContentProps = MenuContentProps
|
|
49
|
+
|
|
50
|
+
export type MenuGroupProps = {
|
|
51
|
+
children: React.ReactNode
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type MenuItemProps = {
|
|
55
|
+
children: React.ReactNode
|
|
56
|
+
/**
|
|
57
|
+
* If you want to pass a React text node to `<ItemTitle />`, then you need to use this prop. This gets used on iOS and Android.
|
|
58
|
+
*/
|
|
59
|
+
textValue?: string
|
|
60
|
+
/**
|
|
61
|
+
* Callback when the item is selected
|
|
62
|
+
*/
|
|
63
|
+
onSelect?: (event?: Event) => void
|
|
64
|
+
} & {
|
|
65
|
+
disabled?: boolean
|
|
66
|
+
hidden?: boolean
|
|
67
|
+
destructive?: boolean
|
|
68
|
+
key: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MenuItemCommonProps {
|
|
72
|
+
/**
|
|
73
|
+
* React elements to render as fallback icon (typically for web)
|
|
74
|
+
*/
|
|
75
|
+
children?: React.ReactNode
|
|
76
|
+
/**
|
|
77
|
+
* The name of an iOS-only SF Symbol. For a full list, see https://developer.apple.com/sf-symbols/.
|
|
78
|
+
* @deprecated Please use the `name` inside of the `ios` prop instead.
|
|
79
|
+
* @platform ios
|
|
80
|
+
*/
|
|
81
|
+
iosIconName?: string
|
|
82
|
+
/**
|
|
83
|
+
* Icon configuration to be used on iOS. You can pass a SF Symbol icon using the `name` prop.
|
|
84
|
+
* Additionally, you can configure the SF Symbol's features like weight, scale, color etc. by passing
|
|
85
|
+
* the corresponding props. Note that some of those features require iOS 15+.
|
|
86
|
+
*
|
|
87
|
+
* @platform ios
|
|
88
|
+
*/
|
|
89
|
+
ios?: {
|
|
90
|
+
name: SFSymbol
|
|
91
|
+
weight?:
|
|
92
|
+
| 'ultraLight'
|
|
93
|
+
| 'thin'
|
|
94
|
+
| 'light'
|
|
95
|
+
| 'regular'
|
|
96
|
+
| 'medium'
|
|
97
|
+
| 'semibold'
|
|
98
|
+
| 'bold'
|
|
99
|
+
| 'heavy'
|
|
100
|
+
| 'black'
|
|
101
|
+
scale?: 'small' | 'medium' | 'large'
|
|
102
|
+
hierarchicalColor?: string
|
|
103
|
+
paletteColors?: string[]
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The name of an android-only resource drawable. For a full list, see https://developer.android.com/reference/android/R.drawable.html.
|
|
107
|
+
*
|
|
108
|
+
* @platform android
|
|
109
|
+
*/
|
|
110
|
+
androidIconName?: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type MenuItemIconProps = MenuItemCommonProps
|
|
114
|
+
|
|
115
|
+
export type MenuItemImageProps = MenuItemCommonProps & {
|
|
116
|
+
/**
|
|
117
|
+
* `source={require('path/to/image')}`
|
|
118
|
+
*/
|
|
119
|
+
source: ImageProps['source']
|
|
120
|
+
ios?: {
|
|
121
|
+
style?: ImageOptions
|
|
122
|
+
lazy?: boolean
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type MenuArrowProps = {}
|
|
127
|
+
|
|
128
|
+
export type MenuSubTriggerProps = MenuItemProps & {
|
|
129
|
+
key: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type MenuSubProps = {
|
|
133
|
+
children?: React.ReactNode
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type MenuSubContentProps = {
|
|
137
|
+
children: React.ReactNode
|
|
138
|
+
}
|
|
139
|
+
export type ContextMenuSubContentProps = ContextMenuContentProps
|
|
140
|
+
|
|
141
|
+
export type MenuItemTitleProps = {
|
|
142
|
+
children: string | React.ReactNode
|
|
143
|
+
}
|
|
144
|
+
export type MenuItemSubtitleProps = {
|
|
145
|
+
children: string
|
|
146
|
+
}
|
|
147
|
+
export type MenuSeparatorProps = {}
|
|
148
|
+
export type MenuCheckboxItemProps = Omit<MenuItemProps, 'onSelect'> & {
|
|
149
|
+
/**
|
|
150
|
+
* The controlled checked state of the checkbox item.
|
|
151
|
+
* Use this with `onCheckedChange` for the web-style API.
|
|
152
|
+
*/
|
|
153
|
+
checked?: boolean
|
|
154
|
+
/**
|
|
155
|
+
* Callback when the checked state changes.
|
|
156
|
+
* Use this with `checked` for the web-style API.
|
|
157
|
+
*/
|
|
158
|
+
onCheckedChange?: (checked: boolean) => void
|
|
159
|
+
/**
|
|
160
|
+
* The controlled value state for native platforms.
|
|
161
|
+
* Use this with `onValueChange` for the native-style API.
|
|
162
|
+
* @platform ios, android
|
|
163
|
+
*/
|
|
164
|
+
value?: 'mixed' | 'on' | 'off' | boolean
|
|
165
|
+
/**
|
|
166
|
+
* Callback when the value changes on native platforms.
|
|
167
|
+
* Use this with `value` for the native-style API.
|
|
168
|
+
* @platform ios, android
|
|
169
|
+
*/
|
|
170
|
+
onValueChange?: (
|
|
171
|
+
state: 'mixed' | 'on' | 'off',
|
|
172
|
+
prevState: 'mixed' | 'on' | 'off'
|
|
173
|
+
) => void
|
|
174
|
+
key: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type MenuItemIndicatorProps = {
|
|
178
|
+
children?: React.ReactNode
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type MenuLabelProps = {
|
|
182
|
+
children: string
|
|
183
|
+
/**
|
|
184
|
+
* If you want to pass a React text node to `<Lable />`, then you need to use this prop. This gets used on iOS and Android.
|
|
185
|
+
*/
|
|
186
|
+
textValue?: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type ContextMenuPreviewProps = {
|
|
190
|
+
children: React.ReactNode | (() => React.ReactNode)
|
|
191
|
+
/**
|
|
192
|
+
* Size of the preview
|
|
193
|
+
* @platform ios
|
|
194
|
+
*/
|
|
195
|
+
size?: {
|
|
196
|
+
width?: number
|
|
197
|
+
height?: number
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Called when the preview is pressed
|
|
201
|
+
* @platform ios
|
|
202
|
+
*/
|
|
203
|
+
onPress?: () => void
|
|
204
|
+
/**
|
|
205
|
+
* Background color of the preview
|
|
206
|
+
* @platform ios
|
|
207
|
+
*/
|
|
208
|
+
backgroundColor?: string
|
|
209
|
+
/**
|
|
210
|
+
* Border radius of the preview
|
|
211
|
+
* @platform ios
|
|
212
|
+
*/
|
|
213
|
+
borderRadius?: number
|
|
214
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './createNativeMenu'
|
|
2
|
+
export * from './withNativeMenu'
|
|
3
|
+
// only export these types to fix a ts issue for ContextMenu
|
|
4
|
+
export type {
|
|
5
|
+
MenuItemIconProps as NativeMenuItemIconProps,
|
|
6
|
+
MenuSubTriggerProps as NativeMenuSubTriggerProps,
|
|
7
|
+
} from './createNativeMenuTypes'
|