@tamagui/menu 2.0.0-rc.4 → 2.0.0-rc.40

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 (60) hide show
  1. package/dist/cjs/Menu.cjs +156 -140
  2. package/dist/cjs/Menu.native.js +182 -164
  3. package/dist/cjs/Menu.native.js.map +1 -1
  4. package/dist/cjs/createNonNativeMenu.cjs +346 -330
  5. package/dist/cjs/createNonNativeMenu.native.js +359 -352
  6. package/dist/cjs/createNonNativeMenu.native.js.map +1 -1
  7. package/dist/cjs/index.cjs +15 -13
  8. package/dist/cjs/index.native.js +26 -24
  9. package/dist/cjs/index.native.js.map +1 -1
  10. package/dist/esm/Menu.mjs +129 -115
  11. package/dist/esm/Menu.mjs.map +1 -1
  12. package/dist/esm/Menu.native.js +155 -139
  13. package/dist/esm/Menu.native.js.map +1 -1
  14. package/dist/esm/createNonNativeMenu.mjs +317 -303
  15. package/dist/esm/createNonNativeMenu.mjs.map +1 -1
  16. package/dist/esm/createNonNativeMenu.native.js +330 -325
  17. package/dist/esm/createNonNativeMenu.native.js.map +1 -1
  18. package/dist/esm/index.js +2 -5
  19. package/dist/esm/index.js.map +1 -6
  20. package/dist/esm/index.mjs +0 -1
  21. package/dist/esm/index.mjs.map +1 -1
  22. package/dist/esm/index.native.js +0 -1
  23. package/dist/esm/index.native.js.map +1 -1
  24. package/dist/jsx/Menu.mjs +129 -115
  25. package/dist/jsx/Menu.mjs.map +1 -1
  26. package/dist/jsx/Menu.native.js +182 -164
  27. package/dist/jsx/Menu.native.js.map +1 -1
  28. package/dist/jsx/createNonNativeMenu.mjs +317 -303
  29. package/dist/jsx/createNonNativeMenu.mjs.map +1 -1
  30. package/dist/jsx/createNonNativeMenu.native.js +359 -352
  31. package/dist/jsx/createNonNativeMenu.native.js.map +1 -1
  32. package/dist/jsx/index.js +2 -5
  33. package/dist/jsx/index.js.map +1 -6
  34. package/dist/jsx/index.mjs +0 -1
  35. package/dist/jsx/index.mjs.map +1 -1
  36. package/dist/jsx/index.native.js +26 -24
  37. package/dist/jsx/index.native.js.map +1 -1
  38. package/package.json +12 -14
  39. package/src/Menu.tsx +11 -3
  40. package/src/createNonNativeMenu.tsx +220 -290
  41. package/types/Menu.d.ts +81 -46
  42. package/types/Menu.d.ts.map +1 -1
  43. package/types/createNonNativeMenu.d.ts +116 -68
  44. package/types/createNonNativeMenu.d.ts.map +1 -1
  45. package/types/index.d.ts +81 -46
  46. package/types/index.d.ts.map +1 -1
  47. package/dist/cjs/Menu.js +0 -137
  48. package/dist/cjs/Menu.js.map +0 -6
  49. package/dist/cjs/createNonNativeMenu.js +0 -334
  50. package/dist/cjs/createNonNativeMenu.js.map +0 -6
  51. package/dist/cjs/index.js +0 -32
  52. package/dist/cjs/index.js.map +0 -6
  53. package/dist/esm/Menu.js +0 -119
  54. package/dist/esm/Menu.js.map +0 -6
  55. package/dist/esm/createNonNativeMenu.js +0 -325
  56. package/dist/esm/createNonNativeMenu.js.map +0 -6
  57. package/dist/jsx/Menu.js +0 -119
  58. package/dist/jsx/Menu.js.map +0 -6
  59. package/dist/jsx/createNonNativeMenu.js +0 -325
  60. package/dist/jsx/createNonNativeMenu.js.map +0 -6
@@ -1,5 +1,23 @@
1
- import type BaseMenuTypes from '@tamagui/create-menu'
2
- import { createBaseMenu, type CreateBaseMenuProps } from '@tamagui/create-menu'
1
+ import type * as BaseMenuTypes from '@tamagui/create-menu'
2
+ import {
3
+ type MenuArrowProps as BaseMenuArrowProps,
4
+ type MenuCheckboxItemProps as BaseMenuCheckboxItemProps,
5
+ type MenuContentProps as BaseMenuContentProps,
6
+ type MenuGroupProps as BaseMenuGroupProps,
7
+ type MenuItemIndicatorProps as BaseMenuItemIndicatorProps,
8
+ type MenuItemProps as BaseMenuItemProps,
9
+ type MenuLabelProps as BaseMenuLabelProps,
10
+ type MenuPortalProps as BaseMenuPortalProps,
11
+ type MenuRadioGroupProps as BaseMenuRadioGroupProps,
12
+ type MenuRadioItemProps as BaseMenuRadioItemProps,
13
+ type MenuSeparatorProps as BaseMenuSeparatorProps,
14
+ type MenuSubContentProps as BaseMenuSubContentProps,
15
+ type MenuSubTriggerProps as BaseMenuSubTriggerProps,
16
+ createBaseMenu,
17
+ type CreateBaseMenuProps,
18
+ } from '@tamagui/create-menu'
19
+ import { usePopperContextSlow } from '@tamagui/popper'
20
+ import { ScrollView, type ScrollViewProps } from '@tamagui/scroll-view'
3
21
  import { useControllableState } from '@tamagui/use-controllable-state'
4
22
  import {
5
23
  composeEventHandlers,
@@ -8,7 +26,9 @@ import {
8
26
  isAndroid,
9
27
  isWeb,
10
28
  Slot,
29
+ styled,
11
30
  type TamaguiElement,
31
+ useEvent,
12
32
  useIsTouchDevice,
13
33
  View,
14
34
  type ViewProps,
@@ -27,14 +47,61 @@ export const DROPDOWN_MENU_CONTEXT = 'MenuContext'
27
47
 
28
48
  type ScopedProps<P> = P & { scope?: string }
29
49
 
50
+ type MenuTriggerStateSetter = React.Dispatch<React.SetStateAction<boolean>>
51
+
30
52
  type MenuContextValue = {
31
53
  triggerId: string
32
54
  triggerRef: React.RefObject<TamaguiElement | null>
33
55
  contentId: string
34
- open: boolean
56
+ openRef: React.RefObject<boolean>
35
57
  onOpenChange(open: boolean): void
36
58
  onOpenToggle(): void
37
59
  modal: boolean
60
+ setActiveTrigger(id: string | null): void
61
+ registerTrigger(id: string, setOpen: MenuTriggerStateSetter): void
62
+ unregisterTrigger(id: string): void
63
+ }
64
+
65
+ function useMenuTriggerSetup(open: boolean) {
66
+ const triggerStateSettersRef = React.useRef(new Map<string, MenuTriggerStateSetter>())
67
+ const activeTriggerIdRef = React.useRef<string | null>(null)
68
+
69
+ const setActiveTrigger = useEvent((id: string | null) => {
70
+ const prevId = activeTriggerIdRef.current
71
+ if (prevId === id) return
72
+ if (prevId) {
73
+ triggerStateSettersRef.current.get(prevId)?.(false)
74
+ }
75
+ activeTriggerIdRef.current = id
76
+ if (id && open) {
77
+ triggerStateSettersRef.current.get(id)?.(true)
78
+ }
79
+ })
80
+
81
+ const registerTrigger = useEvent((id: string, setOpenState: MenuTriggerStateSetter) => {
82
+ triggerStateSettersRef.current.set(id, setOpenState)
83
+ setOpenState(activeTriggerIdRef.current === id && open)
84
+ })
85
+
86
+ const unregisterTrigger = useEvent((id: string) => {
87
+ triggerStateSettersRef.current.delete(id)
88
+ if (activeTriggerIdRef.current === id) {
89
+ activeTriggerIdRef.current = null
90
+ }
91
+ })
92
+
93
+ React.useEffect(() => {
94
+ if (!open) {
95
+ setActiveTrigger(null)
96
+ return
97
+ }
98
+ const activeId = activeTriggerIdRef.current
99
+ if (activeId) {
100
+ triggerStateSettersRef.current.get(activeId)?.(true)
101
+ }
102
+ }, [open, setActiveTrigger])
103
+
104
+ return { setActiveTrigger, registerTrigger, unregisterTrigger }
38
105
  }
39
106
 
40
107
  interface MenuProps extends BaseMenuTypes.MenuProps {
@@ -46,8 +113,6 @@ interface MenuProps extends BaseMenuTypes.MenuProps {
46
113
  modal?: boolean
47
114
  }
48
115
 
49
- type BaseMenu = ReturnType<typeof createBaseMenu>['Menu']
50
-
51
116
  /* -------------------------------------------------------------------------------------------------
52
117
  * MenuTrigger
53
118
  * -----------------------------------------------------------------------------------------------*/
@@ -60,54 +125,51 @@ interface MenuTriggerProps extends ViewProps {
60
125
  * MenuPortal
61
126
  * -----------------------------------------------------------------------------------------------*/
62
127
 
63
- type MenuPortalProps = React.ComponentPropsWithoutRef<BaseMenu['Portal']>
128
+ type MenuPortalProps = BaseMenuPortalProps
64
129
 
65
130
  /* -------------------------------------------------------------------------------------------------
66
131
  * MenuContent
67
132
  * -----------------------------------------------------------------------------------------------*/
68
133
 
69
- type MenuContentElement = React.ElementRef<BaseMenu['Content']>
70
- interface MenuContentProps extends Omit<
71
- React.ComponentPropsWithoutRef<BaseMenu['Content']>,
72
- 'onEntryFocus'
73
- > {}
134
+ type MenuContentElement = TamaguiElement
135
+ interface MenuContentProps extends Omit<BaseMenuContentProps, 'onEntryFocus'> {}
74
136
 
75
137
  /* -------------------------------------------------------------------------------------------------
76
138
  * MenuGroup
77
139
  * -----------------------------------------------------------------------------------------------*/
78
140
 
79
- type MenuGroupProps = React.ComponentPropsWithoutRef<BaseMenu['Group']>
141
+ type MenuGroupProps = BaseMenuGroupProps
80
142
 
81
143
  /* -------------------------------------------------------------------------------------------------
82
144
  * MenuLabel
83
145
  * -----------------------------------------------------------------------------------------------*/
84
146
 
85
- type MenuLabelProps = React.ComponentPropsWithoutRef<BaseMenu['Label']>
147
+ type MenuLabelProps = BaseMenuLabelProps
86
148
 
87
149
  /* -------------------------------------------------------------------------------------------------
88
150
  * MenuItem
89
151
  * -----------------------------------------------------------------------------------------------*/
90
152
 
91
- type MenuItemProps = React.ComponentPropsWithoutRef<BaseMenu['Item']>
153
+ type MenuItemProps = BaseMenuItemProps
92
154
 
93
- type MenuCheckboxItemProps = React.ComponentPropsWithoutRef<BaseMenu['CheckboxItem']>
155
+ type MenuCheckboxItemProps = BaseMenuCheckboxItemProps
94
156
 
95
- type MenuRadioGroupElement = React.ElementRef<BaseMenu['RadioGroup']>
96
- type MenuRadioGroupProps = React.ComponentPropsWithoutRef<BaseMenu['RadioGroup']>
97
- type MenuRadioItemProps = React.ComponentPropsWithoutRef<BaseMenu['RadioItem']>
98
- type MenuItemIndicatorProps = React.ComponentPropsWithoutRef<BaseMenu['ItemIndicator']>
157
+ type MenuRadioGroupElement = TamaguiElement
158
+ type MenuRadioGroupProps = BaseMenuRadioGroupProps
159
+ type MenuRadioItemProps = BaseMenuRadioItemProps
160
+ type MenuItemIndicatorProps = BaseMenuItemIndicatorProps
99
161
 
100
162
  /* -------------------------------------------------------------------------------------------------
101
163
  * MenuSeparator
102
164
  * -----------------------------------------------------------------------------------------------*/
103
165
 
104
- type MenuSeparatorProps = React.ComponentPropsWithoutRef<BaseMenu['Separator']>
166
+ type MenuSeparatorProps = BaseMenuSeparatorProps
105
167
 
106
168
  /* -------------------------------------------------------------------------------------------------
107
169
  * MenuArrow
108
170
  * -----------------------------------------------------------------------------------------------*/
109
171
 
110
- type MenuArrowProps = React.ComponentPropsWithoutRef<BaseMenu['Arrow']>
172
+ type MenuArrowProps = BaseMenuArrowProps
111
173
 
112
174
  /* -------------------------------------------------------------------------------------------------
113
175
  * MenuSub
@@ -124,14 +186,20 @@ type MenuSubProps = BaseMenuTypes.MenuSubProps & {
124
186
  * MenuSubTrigger
125
187
  * -----------------------------------------------------------------------------------------------*/
126
188
 
127
- type MenuSubTriggerProps = React.ComponentPropsWithoutRef<BaseMenu['SubTrigger']>
189
+ type MenuSubTriggerProps = BaseMenuSubTriggerProps
128
190
 
129
191
  /* -------------------------------------------------------------------------------------------------
130
192
  * MenuSubContent
131
193
  * -----------------------------------------------------------------------------------------------*/
132
194
 
133
- type MenuSubContentElement = React.ElementRef<BaseMenu['Content']>
134
- type MenuSubContentProps = React.ComponentPropsWithoutRef<BaseMenu['SubContent']>
195
+ type MenuSubContentElement = TamaguiElement
196
+ type MenuSubContentProps = BaseMenuSubContentProps
197
+
198
+ /* -------------------------------------------------------------------------------------------------
199
+ * MenuScrollView
200
+ * -----------------------------------------------------------------------------------------------*/
201
+
202
+ type MenuScrollViewProps = ScrollViewProps
135
203
 
136
204
  /* -----------------------------------------------------------------------------------------------*/
137
205
 
@@ -164,21 +232,30 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
164
232
  defaultProp: defaultOpen!,
165
233
  onChange: onOpenChange,
166
234
  })
235
+ const openRef = React.useRef(open)
236
+ openRef.current = open
237
+ const { setActiveTrigger, registerTrigger, unregisterTrigger } =
238
+ useMenuTriggerSetup(open)
167
239
 
168
240
  return (
169
241
  <MenuProvider
170
242
  scope={scope}
171
243
  triggerId={useId()}
172
- // TODO
173
244
  triggerRef={triggerRef}
174
245
  contentId={useId()}
175
- open={open}
176
- onOpenChange={setOpen}
246
+ openRef={openRef}
247
+ onOpenChange={React.useCallback(
248
+ (nextOpen: boolean) => setOpen(nextOpen),
249
+ [setOpen]
250
+ )}
177
251
  onOpenToggle={React.useCallback(
178
252
  () => setOpen((prevOpen) => !prevOpen),
179
253
  [setOpen]
180
254
  )}
181
255
  modal={modal}
256
+ setActiveTrigger={setActiveTrigger}
257
+ registerTrigger={registerTrigger}
258
+ unregisterTrigger={unregisterTrigger}
182
259
  >
183
260
  <Menu
184
261
  scope={scope || DROPDOWN_MENU_CONTEXT}
@@ -215,8 +292,35 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
215
292
  ...triggerProps
216
293
  } = props
217
294
  const context = useMenuContext(scope)
295
+ const popperCtx = usePopperContextSlow(scope || DROPDOWN_MENU_CONTEXT)
218
296
  const Comp = asChild ? Slot : View
219
297
  const isTouchDevice = useIsTouchDevice()
298
+ const triggerElRef = React.useRef<TamaguiElement>(null)
299
+
300
+ // multi-trigger: per-trigger open state
301
+ const triggerId = React.useId()
302
+ const [triggerOpen, setTriggerOpen] = React.useState(false)
303
+
304
+ // extract stable refs so re-registration doesn't happen when context object changes
305
+ const { registerTrigger, unregisterTrigger } = context
306
+ React.useEffect(() => {
307
+ registerTrigger(triggerId, setTriggerOpen)
308
+ return () => unregisterTrigger(triggerId)
309
+ }, [registerTrigger, unregisterTrigger, triggerId])
310
+
311
+ // activate this trigger: set popper reference and update shared triggerRef for close-auto-focus
312
+ const activateSelf = React.useCallback(() => {
313
+ context.setActiveTrigger(triggerId)
314
+ const el = triggerElRef.current
315
+ if (el) {
316
+ // update shared ref so close-auto-focus returns to the active trigger
317
+ context.triggerRef.current = el
318
+ if (el instanceof HTMLElement) {
319
+ popperCtx.refs?.setReference(el)
320
+ requestAnimationFrame(() => popperCtx.update?.())
321
+ }
322
+ }
323
+ }, [context, triggerId, popperCtx])
220
324
 
221
325
  // Use onClick for touch devices to avoid race condition with Dismissable
222
326
  // Use onPointerDown for mouse for faster feedback
@@ -232,12 +336,12 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
232
336
  role="button"
233
337
  id={context.triggerId}
234
338
  aria-haspopup="menu"
235
- aria-expanded={context.open}
236
- aria-controls={context.open ? context.contentId : undefined}
237
- data-state={context.open ? 'open' : 'closed'}
339
+ aria-expanded={triggerOpen}
340
+ aria-controls={triggerOpen ? context.contentId : undefined}
341
+ data-state={triggerOpen ? 'open' : 'closed'}
238
342
  data-disabled={disabled ? '' : undefined}
239
343
  aria-disabled={disabled || undefined}
240
- ref={composeRefs(forwardedRef, context.triggerRef)}
344
+ ref={composeRefs(forwardedRef, context.triggerRef, triggerElRef)}
241
345
  {...{
242
346
  [pressEvent]: composeEventHandlers(
243
347
  //@ts-ignore
@@ -253,10 +357,15 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
253
357
  event.ctrlKey === true
254
358
  )
255
359
  return
360
+ if (context.openRef.current) {
361
+ context.setActiveTrigger(null)
362
+ } else {
363
+ activateSelf()
364
+ }
256
365
  context.onOpenToggle()
257
366
  // prevent trigger focusing when opening
258
367
  // this allows the content to be given focus without competition
259
- if (!context.open) event.preventDefault()
368
+ if (!context.openRef.current) event.preventDefault()
260
369
  }
261
370
  }
262
371
  ),
@@ -264,8 +373,18 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
264
373
  {...(isWeb && {
265
374
  onKeyDown: composeEventHandlers(onKeydown, (event) => {
266
375
  if (disabled) return
267
- if (['Enter', ' '].includes(event.key)) context.onOpenToggle()
268
- if (event.key === 'ArrowDown') context.onOpenChange(true)
376
+ if (['Enter', ' '].includes(event.key)) {
377
+ if (context.openRef.current) {
378
+ context.setActiveTrigger(null)
379
+ } else {
380
+ activateSelf()
381
+ }
382
+ context.onOpenToggle()
383
+ }
384
+ if (event.key === 'ArrowDown') {
385
+ activateSelf()
386
+ context.onOpenChange(true)
387
+ }
269
388
  // prevent keydown from scrolling window / first focused item to execute
270
389
  // that keydown (inadvertently closing the menu)
271
390
  if (['Enter', ' ', 'ArrowDown'].includes(event.key))
@@ -328,7 +447,15 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
328
447
  {...contentProps}
329
448
  ref={forwardedRef}
330
449
  onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
331
- if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus()
450
+ if (!hasInteractedOutsideRef.current) {
451
+ // delay to let React render new components and run their autoFocus effects
452
+ requestAnimationFrame(() => {
453
+ const activeEl = document.activeElement
454
+ if (!activeEl || activeEl === document.body) {
455
+ context.triggerRef.current?.focus()
456
+ }
457
+ })
458
+ }
332
459
  hasInteractedOutsideRef.current = false
333
460
  // Always prevent auto focus because we either focus manually or want user agent focus
334
461
  event.preventDefault()
@@ -340,7 +467,24 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
340
467
  const isRightClick = originalEvent.button === 2 || ctrlLeftClick
341
468
  if (!context.modal || isRightClick) hasInteractedOutsideRef.current = true
342
469
  })}
343
- {...(props.style as object)}
470
+ style={
471
+ isWeb
472
+ ? {
473
+ ...(props.style as object),
474
+ ...({
475
+ '--tamagui-menu-content-transform-origin':
476
+ 'var(--tamagui-popper-transform-origin)',
477
+ '--tamagui-menu-content-available-width':
478
+ 'var(--tamagui-popper-available-width)',
479
+ '--tamagui-menu-content-available-height':
480
+ 'var(--tamagui-popper-available-height)',
481
+ '--tamagui-menu-trigger-width': 'var(--tamagui-popper-anchor-width)',
482
+ '--tamagui-menu-trigger-height':
483
+ 'var(--tamagui-popper-anchor-height)',
484
+ } as React.CSSProperties),
485
+ }
486
+ : props.style
487
+ }
344
488
  />
345
489
  )
346
490
  }
@@ -348,214 +492,12 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
348
492
 
349
493
  MenuContent.displayName = CONTENT_NAME
350
494
 
351
- /* -------------------------------------------------------------------------------------------------
352
- * MenuGroup
353
- * -----------------------------------------------------------------------------------------------*/
354
-
355
- const GROUP_NAME = 'MenuGroup'
356
-
357
- const MenuGroup = Menu.Group
358
-
359
- MenuGroup.displayName = GROUP_NAME
360
-
361
- /* -------------------------------------------------------------------------------------------------
362
- * MenuLabel
363
- * -----------------------------------------------------------------------------------------------*/
364
-
365
- const LABEL_NAME = 'MenuLabel'
366
-
367
- const MenuLabel = Menu.Label
368
-
369
- MenuLabel.displayName = LABEL_NAME
370
-
371
- /* -------------------------------------------------------------------------------------------------
372
- * MenuItem
373
- * -----------------------------------------------------------------------------------------------*/
374
-
375
- const ITEM_NAME = 'MenuItem'
376
-
377
- const MenuItemFrame = Menu.Item
378
-
379
- const MenuItem = React.forwardRef<TamaguiElement, ScopedProps<MenuItemProps>>(
380
- (props, forwardedRef) => {
381
- const { scope, ...itemProps } = props
382
- return (
383
- <MenuItemFrame
384
- componentName={ITEM_NAME}
385
- scope={scope || DROPDOWN_MENU_CONTEXT}
386
- {...itemProps}
387
- ref={forwardedRef}
388
- />
389
- )
390
- }
391
- )
392
-
393
- MenuItem.displayName = ITEM_NAME
394
-
395
- /* -------------------------------------------------------------------------------------------------
396
- * MenuItemTitle
397
- * -----------------------------------------------------------------------------------------------*/
398
-
399
- const ITEM_TITLE_NAME = 'MenuItemTitle'
400
- const MenuItemTitle = Menu.ItemTitle
401
- MenuItemTitle.displayName = ITEM_TITLE_NAME
402
-
403
- /* -------------------------------------------------------------------------------------------------
404
- * MenuItemSubTitle
405
- * -----------------------------------------------------------------------------------------------*/
406
-
407
- const ITEM_SUB_TITLE_NAME = 'MenuItemSubTitle'
408
- const MenuItemSubTitle = Menu.ItemSubtitle
409
- MenuItemSubTitle.displayName = ITEM_SUB_TITLE_NAME
410
-
411
- /* -------------------------------------------------------------------------------------------------
412
- * MenuItemImage
413
- * -----------------------------------------------------------------------------------------------*/
414
-
415
- const ITEM_IMAGE_NAME = 'MenuItemImage'
416
- const MenuItemImage = Menu.ItemImage
417
- MenuItemImage.displayName = ITEM_IMAGE_NAME
418
-
419
- /* -------------------------------------------------------------------------------------------------
420
- * MenuItemIcon
421
- * -----------------------------------------------------------------------------------------------*/
422
-
423
- const ITEM_ICON_NAME = 'MenuItemIcon'
424
- const MenuItemIcon = Menu.ItemIcon
425
- MenuItemIcon.displayName = ITEM_ICON_NAME
426
- /* -------------------------------------------------------------------------------------------------
427
- * MenuCheckboxItem
428
- * -----------------------------------------------------------------------------------------------*/
429
-
430
- const CHECKBOX_ITEM_NAME = 'MenuCheckboxItem'
431
-
432
- const MenuCheckboxItemFrame = Menu.CheckboxItem
433
-
434
- const MenuCheckboxItem = React.forwardRef<
435
- TamaguiElement,
436
- ScopedProps<MenuCheckboxItemProps>
437
- >((props, forwardedRef) => {
438
- const { scope, ...checkboxItemProps } = props
439
- return (
440
- <MenuCheckboxItemFrame
441
- componentName={CHECKBOX_ITEM_NAME}
442
- scope={scope || DROPDOWN_MENU_CONTEXT}
443
- {...checkboxItemProps}
444
- ref={forwardedRef}
445
- />
446
- )
447
- })
448
-
449
- MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME
450
-
451
- /* -------------------------------------------------------------------------------------------------
452
- * MenuRadioGroup
453
- * -----------------------------------------------------------------------------------------------*/
454
-
455
- const RADIO_GROUP_NAME = 'MenuRadioGroup'
456
-
457
- const MenuRadioGroup = React.forwardRef<
458
- MenuRadioGroupElement,
459
- ScopedProps<MenuRadioGroupProps>
460
- >((props, forwardedRef) => {
461
- const { scope, ...radioGroupProps } = props
462
- return (
463
- <Menu.RadioGroup
464
- scope={scope || DROPDOWN_MENU_CONTEXT}
465
- {...radioGroupProps}
466
- ref={forwardedRef}
467
- />
468
- )
469
- })
470
-
471
- MenuRadioGroup.displayName = RADIO_GROUP_NAME
472
-
473
- /* -------------------------------------------------------------------------------------------------
474
- * MenuRadioItem
475
- * -----------------------------------------------------------------------------------------------*/
476
-
477
- const RADIO_ITEM_NAME = 'MenuRadioItem'
478
-
479
- const MenuRadioItemFrame = Menu.RadioItem
480
-
481
- const MenuRadioItem = React.forwardRef<TamaguiElement, ScopedProps<MenuRadioItemProps>>(
482
- (props, forwardedRef) => {
483
- const { scope, ...radioItemProps } = props
484
- return (
485
- // @ts-ignore explanation: deeply nested types typescript limitation
486
- <MenuRadioItemFrame
487
- componentName={RADIO_ITEM_NAME}
488
- scope={scope || DROPDOWN_MENU_CONTEXT}
489
- {...radioItemProps}
490
- ref={forwardedRef}
491
- />
492
- )
493
- }
494
- )
495
-
496
- MenuRadioItem.displayName = RADIO_ITEM_NAME
497
-
498
- /* -------------------------------------------------------------------------------------------------
499
- * MenuItemIndicator
500
- * -----------------------------------------------------------------------------------------------*/
501
-
502
- const INDICATOR_NAME = 'MenuItemIndicator'
503
-
504
- const MenuItemIndicatorFrame = Menu.ItemIndicator
505
-
506
- const MenuItemIndicator = MenuItemIndicatorFrame.styleable<
507
- ScopedProps<MenuItemIndicatorProps>
508
- >((props, forwardedRef) => {
509
- const { scope, ...itemIndicatorProps } = props
510
- return (
511
- <MenuItemIndicatorFrame
512
- componentName={INDICATOR_NAME}
513
- scope={scope || DROPDOWN_MENU_CONTEXT}
514
- {...itemIndicatorProps}
515
- ref={forwardedRef}
516
- />
517
- )
518
- })
519
-
520
- MenuItemIndicator.displayName = INDICATOR_NAME
521
-
522
- /* -------------------------------------------------------------------------------------------------
523
- * MenuSeparator
524
- * -----------------------------------------------------------------------------------------------*/
525
-
526
- const SEPARATOR_NAME = 'MenuSeparator'
527
-
528
- const MenuSeparator = Menu.Separator
529
-
530
- MenuSeparator.displayName = SEPARATOR_NAME
531
-
532
- /* -------------------------------------------------------------------------------------------------
533
- * MenuArrow
534
- * -----------------------------------------------------------------------------------------------*/
535
-
536
- const ARROW_NAME = 'MenuArrow'
537
-
538
- const MenuArrow = React.forwardRef<TamaguiElement, ScopedProps<MenuArrowProps>>(
539
- (props, forwardedRef) => {
540
- const { scope, ...arrowProps } = props
541
- return (
542
- <Menu.Arrow
543
- componentName={ARROW_NAME}
544
- scope={scope || DROPDOWN_MENU_CONTEXT}
545
- {...arrowProps}
546
- ref={forwardedRef}
547
- />
548
- )
549
- }
550
- )
551
-
552
- MenuArrow.displayName = ARROW_NAME
553
-
554
495
  /* -------------------------------------------------------------------------------------------------
555
496
  * MenuSub
556
497
  * -----------------------------------------------------------------------------------------------*/
557
498
 
558
499
  const DROPDOWN_MENU_SUB_NAME = 'MenuSub'
500
+
559
501
  const MenuSub = (props: ScopedProps<MenuSubProps>) => {
560
502
  const { scope, children, open: openProp, onOpenChange, defaultOpen, ...rest } = props
561
503
  const [open = false, setOpen] = useControllableState({
@@ -578,29 +520,6 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
578
520
 
579
521
  MenuSub.displayName = DROPDOWN_MENU_SUB_NAME
580
522
 
581
- /* -------------------------------------------------------------------------------------------------
582
- * MenuSubTrigger
583
- * -----------------------------------------------------------------------------------------------*/
584
-
585
- const SUB_TRIGGER_NAME = 'MenuSubTrigger'
586
-
587
- const MenuSubTrigger = View.styleable<ScopedProps<MenuSubTriggerProps>>(
588
- (props, forwardedRef) => {
589
- // TODO: having asChild will create a problem, find a fix for that
590
- const { scope, asChild, ...subTriggerProps } = props
591
- return (
592
- <Menu.SubTrigger
593
- componentName={SUB_TRIGGER_NAME}
594
- scope={scope || DROPDOWN_MENU_CONTEXT}
595
- {...subTriggerProps}
596
- ref={forwardedRef}
597
- />
598
- )
599
- }
600
- )
601
-
602
- MenuSubTrigger.displayName = SUB_TRIGGER_NAME
603
-
604
523
  /* -------------------------------------------------------------------------------------------------
605
524
  * MenuSubContent
606
525
  * -----------------------------------------------------------------------------------------------*/
@@ -622,8 +541,6 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
622
541
  isWeb
623
542
  ? {
624
543
  ...(props.style as object),
625
- // re-namespace exposed content custom properties
626
- // TODO: find a better way to do this, or maybe not do it at all
627
544
  ...({
628
545
  '--tamagui-menu-content-transform-origin':
629
546
  'var(--tamagui-popper-transform-origin)',
@@ -643,34 +560,45 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
643
560
 
644
561
  MenuSubContent.displayName = SUB_CONTENT_NAME
645
562
 
563
+ /* -------------------------------------------------------------------------------------------------
564
+ * MenuScrollView
565
+ * -----------------------------------------------------------------------------------------------*/
566
+
567
+ const MenuScrollView = styled(ScrollView, {
568
+ flexShrink: 1,
569
+ alignSelf: 'stretch',
570
+ showsHorizontalScrollIndicator: false,
571
+ showsVerticalScrollIndicator: false,
572
+
573
+ '$platform-web': {
574
+ maxHeight: 'var(--tamagui-menu-content-available-height)',
575
+ },
576
+ })
577
+
646
578
  /* -----------------------------------------------------------------------------------------------*/
647
579
 
648
- const Root = MenuComp
649
- const Trigger = MenuTrigger
650
- const Portal = MenuPortal
651
- const Content = MenuContent
652
- const Group = MenuGroup
653
- const Label = MenuLabel
654
- const Item = MenuItem
655
- const CheckboxItem = MenuCheckboxItem
656
- const RadioGroup = MenuRadioGroup
657
- const RadioItem = MenuRadioItem
658
- const ItemIndicator = MenuItemIndicator
659
- const Separator = MenuSeparator
660
- const Arrow = MenuArrow
661
- const Sub = MenuSub
662
- const SubTrigger = MenuSubTrigger
663
- const SubContent = MenuSubContent
664
- const ItemTitle = MenuItemTitle
665
- const ItemSubtitle = MenuItemSubTitle
666
- const ItemImage = MenuItemImage
667
- const ItemIcon = MenuItemIcon
580
+ // direct pass-through from base menu (preserves styleable)
581
+ const Group = Menu.Group
582
+ const Label = Menu.Label
583
+ const Item = Menu.Item
584
+ const CheckboxItem = Menu.CheckboxItem
585
+ const RadioGroup = Menu.RadioGroup
586
+ const RadioItem = Menu.RadioItem
587
+ const ItemIndicator = Menu.ItemIndicator
588
+ const Separator = Menu.Separator
589
+ const Arrow = Menu.Arrow
590
+ const SubTrigger = Menu.SubTrigger
591
+
592
+ const ItemTitle = Menu.ItemTitle
593
+ const ItemSubtitle = Menu.ItemSubtitle
594
+ const ItemImage = Menu.ItemImage
595
+ const ItemIcon = Menu.ItemIcon
668
596
 
669
597
  return withStaticProperties(MenuComp, {
670
- Root,
671
- Trigger,
672
- Portal,
673
- Content,
598
+ Root: MenuComp,
599
+ Trigger: MenuTrigger,
600
+ Portal: MenuPortal,
601
+ Content: MenuContent,
674
602
  Group,
675
603
  Label,
676
604
  Item,
@@ -680,13 +608,14 @@ export function createNonNativeMenu(params: CreateBaseMenuProps) {
680
608
  ItemIndicator,
681
609
  Separator,
682
610
  Arrow,
683
- Sub,
611
+ Sub: MenuSub,
684
612
  SubTrigger,
685
- SubContent,
613
+ SubContent: MenuSubContent,
686
614
  ItemTitle,
687
615
  ItemSubtitle,
688
616
  ItemImage,
689
617
  ItemIcon,
618
+ ScrollView: MenuScrollView,
690
619
  })
691
620
  }
692
621
 
@@ -702,6 +631,7 @@ export type {
702
631
  MenuProps,
703
632
  MenuRadioGroupProps,
704
633
  MenuRadioItemProps,
634
+ MenuScrollViewProps,
705
635
  MenuSubContentProps,
706
636
  MenuSubProps,
707
637
  MenuSubTriggerProps,