@tamagui/context-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.
Files changed (61) hide show
  1. package/dist/cjs/ContextMenu.cjs +182 -0
  2. package/dist/cjs/ContextMenu.js +145 -0
  3. package/dist/cjs/ContextMenu.js.map +6 -0
  4. package/dist/cjs/ContextMenu.native.js +191 -0
  5. package/dist/cjs/ContextMenu.native.js.map +1 -0
  6. package/dist/cjs/createNonNativeContextMenu.cjs +394 -0
  7. package/dist/cjs/createNonNativeContextMenu.js +318 -0
  8. package/dist/cjs/createNonNativeContextMenu.js.map +6 -0
  9. package/dist/cjs/createNonNativeContextMenu.native.js +420 -0
  10. package/dist/cjs/createNonNativeContextMenu.native.js.map +1 -0
  11. package/dist/cjs/index.cjs +39 -0
  12. package/dist/cjs/index.js +32 -0
  13. package/dist/cjs/index.js.map +6 -0
  14. package/dist/cjs/index.native.js +42 -0
  15. package/dist/cjs/index.native.js.map +1 -0
  16. package/dist/esm/ContextMenu.js +127 -0
  17. package/dist/esm/ContextMenu.js.map +6 -0
  18. package/dist/esm/ContextMenu.mjs +148 -0
  19. package/dist/esm/ContextMenu.mjs.map +1 -0
  20. package/dist/esm/ContextMenu.native.js +154 -0
  21. package/dist/esm/ContextMenu.native.js.map +1 -0
  22. package/dist/esm/createNonNativeContextMenu.js +307 -0
  23. package/dist/esm/createNonNativeContextMenu.js.map +6 -0
  24. package/dist/esm/createNonNativeContextMenu.mjs +359 -0
  25. package/dist/esm/createNonNativeContextMenu.mjs.map +1 -0
  26. package/dist/esm/createNonNativeContextMenu.native.js +382 -0
  27. package/dist/esm/createNonNativeContextMenu.native.js.map +1 -0
  28. package/dist/esm/index.js +18 -0
  29. package/dist/esm/index.js.map +6 -0
  30. package/dist/esm/index.mjs +16 -0
  31. package/dist/esm/index.mjs.map +1 -0
  32. package/dist/esm/index.native.js +16 -0
  33. package/dist/esm/index.native.js.map +1 -0
  34. package/dist/jsx/ContextMenu.js +127 -0
  35. package/dist/jsx/ContextMenu.js.map +6 -0
  36. package/dist/jsx/ContextMenu.mjs +148 -0
  37. package/dist/jsx/ContextMenu.mjs.map +1 -0
  38. package/dist/jsx/ContextMenu.native.js +191 -0
  39. package/dist/jsx/ContextMenu.native.js.map +1 -0
  40. package/dist/jsx/createNonNativeContextMenu.js +307 -0
  41. package/dist/jsx/createNonNativeContextMenu.js.map +6 -0
  42. package/dist/jsx/createNonNativeContextMenu.mjs +359 -0
  43. package/dist/jsx/createNonNativeContextMenu.mjs.map +1 -0
  44. package/dist/jsx/createNonNativeContextMenu.native.js +420 -0
  45. package/dist/jsx/createNonNativeContextMenu.native.js.map +1 -0
  46. package/dist/jsx/index.js +18 -0
  47. package/dist/jsx/index.js.map +6 -0
  48. package/dist/jsx/index.mjs +16 -0
  49. package/dist/jsx/index.mjs.map +1 -0
  50. package/dist/jsx/index.native.js +42 -0
  51. package/dist/jsx/index.native.js.map +1 -0
  52. package/package.json +56 -0
  53. package/src/ContextMenu.tsx +187 -0
  54. package/src/createNonNativeContextMenu.tsx +607 -0
  55. package/src/index.tsx +17 -0
  56. package/types/ContextMenu.d.ts +98 -0
  57. package/types/ContextMenu.d.ts.map +1 -0
  58. package/types/createNonNativeContextMenu.d.ts +123 -0
  59. package/types/createNonNativeContextMenu.d.ts.map +1 -0
  60. package/types/index.d.ts +96 -0
  61. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,607 @@
1
+ import type BaseMenuTypes from '@tamagui/create-menu'
2
+ import { createBaseMenu, type CreateBaseMenuProps } from '@tamagui/create-menu'
3
+ import { useControllableState } from '@tamagui/use-controllable-state'
4
+ import {
5
+ composeEventHandlers,
6
+ composeRefs,
7
+ createStyledContext,
8
+ isAndroid,
9
+ isWeb,
10
+ Slot,
11
+ type TamaguiElement,
12
+ View,
13
+ type ViewProps,
14
+ withStaticProperties,
15
+ } from '@tamagui/web'
16
+ import React, { useId } from 'react'
17
+
18
+ type Direction = 'ltr' | 'rtl'
19
+ type Point = { x: number; y: number }
20
+
21
+ export const CONTEXTMENU_CONTEXT = 'ContextMenuContext'
22
+
23
+ /* -------------------------------------------------------------------------------------------------
24
+ * Types
25
+ * -----------------------------------------------------------------------------------------------*/
26
+
27
+ type ScopedProps<P> = P & { scope?: string }
28
+
29
+ type BaseMenu = ReturnType<typeof createBaseMenu>['Menu']
30
+
31
+ type ContextMenuContextValue = {
32
+ triggerId: string
33
+ triggerRef: React.RefObject<HTMLButtonElement>
34
+ contentId: string
35
+ open: boolean
36
+ onOpenChange(open: boolean): void
37
+ modal: boolean
38
+ }
39
+
40
+ interface ContextMenuProps extends BaseMenuTypes.MenuProps {
41
+ children?: React.ReactNode
42
+ onOpenChange?(open: boolean): void
43
+ dir?: Direction
44
+ modal?: boolean
45
+ }
46
+
47
+ interface ContextMenuTriggerProps extends ViewProps {
48
+ disabled?: boolean
49
+ }
50
+
51
+ type ContextMenuPortalProps = React.ComponentPropsWithoutRef<BaseMenu['Portal']>
52
+
53
+ type ContextMenuContentElement = React.ComponentRef<BaseMenu['Content']>
54
+ interface ContextMenuContentProps
55
+ extends Omit<
56
+ React.ComponentPropsWithoutRef<BaseMenu['Content']>,
57
+ 'onEntryFocus' | 'side' | 'sideOffset' | 'align'
58
+ > {}
59
+
60
+ type ContextMenuGroupProps = React.ComponentPropsWithoutRef<BaseMenu['Group']>
61
+ type ContextMenuItemProps = React.ComponentPropsWithoutRef<BaseMenu['Item']>
62
+ type ContextMenuItemImageProps = React.ComponentPropsWithoutRef<BaseMenu['ItemImage']>
63
+ type ContextMenuItemIconProps = React.ComponentPropsWithoutRef<BaseMenu['ItemIcon']>
64
+ type ContextMenuCheckboxItemProps = React.ComponentPropsWithoutRef<
65
+ BaseMenu['CheckboxItem']
66
+ >
67
+ type ContextMenuRadioGroupElement = React.ComponentRef<BaseMenu['RadioGroup']>
68
+ type ContextMenuRadioGroupProps = React.ComponentPropsWithoutRef<BaseMenu['RadioGroup']>
69
+ type ContextMenuRadioItemProps = React.ComponentPropsWithoutRef<BaseMenu['RadioItem']>
70
+ type ContextMenuItemIndicatorProps = React.ComponentPropsWithoutRef<
71
+ BaseMenu['ItemIndicator']
72
+ >
73
+ type ContextMenuSeparatorProps = React.ComponentPropsWithoutRef<BaseMenu['Separator']>
74
+ type ContextMenuArrowProps = React.ComponentPropsWithoutRef<BaseMenu['Arrow']>
75
+
76
+ interface ContextMenuSubProps extends BaseMenuTypes.MenuSubProps {
77
+ children?: React.ReactNode
78
+ open?: boolean
79
+ defaultOpen?: boolean
80
+ onOpenChange?(open: boolean): void
81
+ }
82
+
83
+ type ContextMenuSubTriggerProps = React.ComponentPropsWithoutRef<BaseMenu['SubTrigger']>
84
+ type ContextMenuSubContentElement = React.ComponentRef<BaseMenu['Content']>
85
+ type ContextMenuSubContentProps = React.ComponentPropsWithoutRef<BaseMenu['SubContent']>
86
+
87
+ /* -----------------------------------------------------------------------------------------------*/
88
+
89
+ export function createNonNativeContextMenu(params: CreateBaseMenuProps) {
90
+ const { Menu } = createBaseMenu(params)
91
+
92
+ /* -------------------------------------------------------------------------------------------------
93
+ * ContextMenu
94
+ * -----------------------------------------------------------------------------------------------*/
95
+
96
+ const CONTEXT_MENU_NAME = 'ContextMenu'
97
+
98
+ const { Provider: ContextMenuProvider, useStyledContext: useContextMenuContext } =
99
+ createStyledContext<ContextMenuContextValue>()
100
+
101
+ const ContextMenuComp = (props: ScopedProps<ContextMenuProps>) => {
102
+ const { scope, children, onOpenChange, dir, modal = true, ...rest } = props
103
+ const [open, setOpen] = React.useState(false)
104
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
105
+
106
+ const handleOpenChange = React.useCallback(
107
+ (open: boolean) => {
108
+ setOpen(open)
109
+ onOpenChange?.(open)
110
+ },
111
+ [onOpenChange]
112
+ )
113
+
114
+ return (
115
+ <ContextMenuProvider
116
+ scope={scope}
117
+ triggerId={useId()}
118
+ triggerRef={triggerRef as any}
119
+ contentId={useId()}
120
+ open={open}
121
+ onOpenChange={handleOpenChange}
122
+ modal={modal}
123
+ >
124
+ <Menu
125
+ scope={scope || CONTEXTMENU_CONTEXT}
126
+ dir={dir}
127
+ open={open}
128
+ onOpenChange={handleOpenChange}
129
+ modal={modal}
130
+ {...rest}
131
+ >
132
+ {children}
133
+ </Menu>
134
+ </ContextMenuProvider>
135
+ )
136
+ }
137
+
138
+ ContextMenuComp.displayName = CONTEXT_MENU_NAME
139
+
140
+ /* -------------------------------------------------------------------------------------------------
141
+ * ContextMenuTrigger - The only real difference from Menu: uses onContextMenu instead of onClick
142
+ * -----------------------------------------------------------------------------------------------*/
143
+
144
+ const TRIGGER_NAME = 'ContextMenuTrigger'
145
+
146
+ const ContextMenuTrigger = View.styleable<ScopedProps<ContextMenuTriggerProps>>(
147
+ (props, forwardedRef) => {
148
+ const { scope, style, disabled = false, asChild, children, ...triggerProps } = props
149
+ const context = useContextMenuContext(scope)
150
+ const pointRef = React.useRef<Point>({ x: 0, y: 0 })
151
+ const virtualRef = React.useMemo(
152
+ () => ({
153
+ current: {
154
+ getBoundingClientRect: () =>
155
+ isWeb
156
+ ? DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current })
157
+ : { width: 0, height: 0, top: 0, left: 0, ...pointRef.current },
158
+ ...(!isWeb && {
159
+ measure: (c: any) => c(pointRef.current.x, pointRef.current.y, 0, 0),
160
+ measureInWindow: (c: any) =>
161
+ c(pointRef.current.x, pointRef.current.y, 0, 0),
162
+ }),
163
+ },
164
+ }),
165
+ [pointRef.current.x, pointRef.current.y]
166
+ )
167
+ const longPressTimerRef = React.useRef(0)
168
+ const clearLongPress = React.useCallback(
169
+ () => window.clearTimeout(longPressTimerRef.current),
170
+ []
171
+ )
172
+ const handleOpen = (event: React.MouseEvent | React.PointerEvent) => {
173
+ if (isWeb && (event instanceof MouseEvent || event instanceof PointerEvent)) {
174
+ pointRef.current = { x: event.clientX, y: event.clientY }
175
+ } else {
176
+ pointRef.current = {
177
+ x: (event as any).nativeEvent.pageX,
178
+ y: (event as any).nativeEvent.pageY,
179
+ }
180
+ }
181
+ context.onOpenChange(true)
182
+ }
183
+
184
+ React.useEffect(() => clearLongPress, [clearLongPress])
185
+ React.useEffect(
186
+ () => void (disabled && clearLongPress()),
187
+ [disabled, clearLongPress]
188
+ )
189
+
190
+ const Comp = asChild ? Slot : View
191
+
192
+ return (
193
+ <>
194
+ <Menu.Anchor scope={scope || CONTEXTMENU_CONTEXT} virtualRef={virtualRef} />
195
+ <Comp
196
+ tag="span"
197
+ componentName={TRIGGER_NAME}
198
+ id={context.triggerId}
199
+ data-state={context.open ? 'open' : 'closed'}
200
+ data-disabled={disabled ? '' : undefined}
201
+ {...triggerProps}
202
+ ref={composeRefs(forwardedRef, context.triggerRef)}
203
+ style={isWeb ? { WebkitTouchCallout: 'none', ...(style as Object) } : null}
204
+ {...(isWeb && {
205
+ onContextMenu: disabled
206
+ ? props.onContextMenu
207
+ : composeEventHandlers(props.onContextMenu, (event: any) => {
208
+ clearLongPress()
209
+ handleOpen(event)
210
+ event.preventDefault()
211
+ }),
212
+ onPointerDown: disabled
213
+ ? props.onPointerDown
214
+ : composeEventHandlers(props.onPointerDown, (event: any) => {
215
+ if (event.pointerType === 'mouse') return
216
+ clearLongPress()
217
+ longPressTimerRef.current = window.setTimeout(
218
+ () => handleOpen(event),
219
+ 700
220
+ )
221
+ }),
222
+ onPointerMove: disabled
223
+ ? props.onPointerMove
224
+ : composeEventHandlers(props.onPointerMove, (event: any) => {
225
+ if (event.pointerType === 'mouse') return
226
+ clearLongPress()
227
+ }),
228
+ onPointerCancel: disabled
229
+ ? props.onPointerCancel
230
+ : composeEventHandlers(props.onPointerCancel, (event: any) => {
231
+ if (event.pointerType === 'mouse') return
232
+ clearLongPress()
233
+ }),
234
+ onPointerUp: disabled
235
+ ? props.onPointerUp
236
+ : composeEventHandlers(props.onPointerUp, (event: any) => {
237
+ if (event.pointerType === 'mouse') return
238
+ clearLongPress()
239
+ }),
240
+ })}
241
+ {...(!isWeb && {
242
+ onLongPress: disabled
243
+ ? props.onLongPress
244
+ : composeEventHandlers(props.onLongPress, (event: any) => {
245
+ clearLongPress()
246
+ handleOpen(event)
247
+ event.preventDefault()
248
+ }),
249
+ })}
250
+ >
251
+ {children}
252
+ </Comp>
253
+ </>
254
+ )
255
+ }
256
+ )
257
+
258
+ ContextMenuTrigger.displayName = TRIGGER_NAME
259
+
260
+ /* -------------------------------------------------------------------------------------------------
261
+ * ContextMenuPortal
262
+ * -----------------------------------------------------------------------------------------------*/
263
+
264
+ const PORTAL_NAME = 'ContextMenuPortal'
265
+
266
+ const ContextMenuPortal = (props: ScopedProps<ContextMenuPortalProps>) => {
267
+ const { scope, children, ...portalProps } = props
268
+ const context = isAndroid ? useContextMenuContext(scope) : null
269
+ const content = isAndroid ? (
270
+ <ContextMenuProvider {...(context as any)}>{children}</ContextMenuProvider>
271
+ ) : (
272
+ children
273
+ )
274
+ return (
275
+ <Menu.Portal scope={scope || CONTEXTMENU_CONTEXT} {...portalProps}>
276
+ {content}
277
+ </Menu.Portal>
278
+ )
279
+ }
280
+
281
+ ContextMenuPortal.displayName = PORTAL_NAME
282
+
283
+ /* -------------------------------------------------------------------------------------------------
284
+ * ContextMenuContent
285
+ * -----------------------------------------------------------------------------------------------*/
286
+
287
+ const CONTENT_NAME = 'ContextMenuContent'
288
+
289
+ const ContextMenuContent = React.forwardRef<
290
+ ContextMenuContentElement,
291
+ ScopedProps<ContextMenuContentProps>
292
+ >((props, forwardedRef) => {
293
+ const { scope, ...contentProps } = props
294
+ const context = useContextMenuContext(scope)
295
+ const hasInteractedOutsideRef = React.useRef(false)
296
+
297
+ return (
298
+ <Menu.Content
299
+ id={context.contentId}
300
+ aria-labelledby={context.triggerId}
301
+ scope={scope || CONTEXTMENU_CONTEXT}
302
+ {...contentProps}
303
+ ref={forwardedRef}
304
+ onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
305
+ if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus()
306
+ hasInteractedOutsideRef.current = false
307
+ event.preventDefault()
308
+ })}
309
+ onInteractOutside={composeEventHandlers(props.onInteractOutside, (event) => {
310
+ const originalEvent = event.detail.originalEvent as PointerEvent
311
+ const ctrlLeftClick =
312
+ originalEvent.button === 0 && originalEvent.ctrlKey === true
313
+ const isRightClick = originalEvent.button === 2 || ctrlLeftClick
314
+ if (!context.modal || isRightClick) hasInteractedOutsideRef.current = true
315
+ })}
316
+ />
317
+ )
318
+ })
319
+
320
+ ContextMenuContent.displayName = CONTENT_NAME
321
+
322
+ /* -------------------------------------------------------------------------------------------------
323
+ * ContextMenuItem
324
+ * -----------------------------------------------------------------------------------------------*/
325
+
326
+ const ITEM_NAME = 'ContextMenuItem'
327
+
328
+ const ContextMenuItem = React.forwardRef<
329
+ TamaguiElement,
330
+ ScopedProps<ContextMenuItemProps>
331
+ >((props, forwardedRef) => {
332
+ const { scope, ...itemProps } = props
333
+ return (
334
+ <Menu.Item
335
+ componentName={ITEM_NAME}
336
+ scope={scope || CONTEXTMENU_CONTEXT}
337
+ {...itemProps}
338
+ ref={forwardedRef}
339
+ />
340
+ )
341
+ })
342
+
343
+ ContextMenuItem.displayName = ITEM_NAME
344
+
345
+ /* -------------------------------------------------------------------------------------------------
346
+ * ContextMenuCheckboxItem
347
+ * -----------------------------------------------------------------------------------------------*/
348
+
349
+ const CHECKBOX_ITEM_NAME = 'ContextMenuCheckboxItem'
350
+
351
+ const ContextMenuCheckboxItem = React.forwardRef<
352
+ TamaguiElement,
353
+ ScopedProps<ContextMenuCheckboxItemProps>
354
+ >((props, forwardedRef) => {
355
+ const { scope, ...checkboxItemProps } = props
356
+ return (
357
+ <Menu.CheckboxItem
358
+ componentName={CHECKBOX_ITEM_NAME}
359
+ scope={scope || CONTEXTMENU_CONTEXT}
360
+ {...checkboxItemProps}
361
+ ref={forwardedRef}
362
+ />
363
+ )
364
+ })
365
+
366
+ ContextMenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME
367
+
368
+ /* -------------------------------------------------------------------------------------------------
369
+ * ContextMenuRadioGroup
370
+ * -----------------------------------------------------------------------------------------------*/
371
+
372
+ const RADIO_GROUP_NAME = 'ContextMenuRadioGroup'
373
+
374
+ const ContextMenuRadioGroup = React.forwardRef<
375
+ ContextMenuRadioGroupElement,
376
+ ScopedProps<ContextMenuRadioGroupProps>
377
+ >((props, forwardedRef) => {
378
+ const { scope, ...radioGroupProps } = props
379
+ return (
380
+ <Menu.RadioGroup
381
+ scope={scope || CONTEXTMENU_CONTEXT}
382
+ {...radioGroupProps}
383
+ ref={forwardedRef}
384
+ />
385
+ )
386
+ })
387
+
388
+ ContextMenuRadioGroup.displayName = RADIO_GROUP_NAME
389
+
390
+ /* -------------------------------------------------------------------------------------------------
391
+ * ContextMenuRadioItem
392
+ * -----------------------------------------------------------------------------------------------*/
393
+
394
+ const RADIO_ITEM_NAME = 'ContextMenuRadioItem'
395
+
396
+ const ContextMenuRadioItem = React.forwardRef<
397
+ TamaguiElement,
398
+ ScopedProps<ContextMenuRadioItemProps>
399
+ >((props, forwardedRef) => {
400
+ const { scope, ...radioItemProps } = props
401
+ return (
402
+ <Menu.RadioItem
403
+ componentName={RADIO_ITEM_NAME}
404
+ scope={scope || CONTEXTMENU_CONTEXT}
405
+ {...radioItemProps}
406
+ ref={forwardedRef}
407
+ />
408
+ )
409
+ })
410
+
411
+ ContextMenuRadioItem.displayName = RADIO_ITEM_NAME
412
+
413
+ /* -------------------------------------------------------------------------------------------------
414
+ * ContextMenuItemIndicator
415
+ * -----------------------------------------------------------------------------------------------*/
416
+
417
+ const INDICATOR_NAME = 'ContextMenuItemIndicator'
418
+
419
+ const ContextMenuItemIndicator = Menu.ItemIndicator.styleable<
420
+ ScopedProps<ContextMenuItemIndicatorProps>
421
+ >((props, forwardedRef) => {
422
+ const { scope, ...itemIndicatorProps } = props
423
+ return (
424
+ <Menu.ItemIndicator
425
+ componentName={INDICATOR_NAME}
426
+ scope={scope || CONTEXTMENU_CONTEXT}
427
+ {...itemIndicatorProps}
428
+ ref={forwardedRef}
429
+ />
430
+ )
431
+ })
432
+
433
+ ContextMenuItemIndicator.displayName = INDICATOR_NAME
434
+
435
+ /* -------------------------------------------------------------------------------------------------
436
+ * ContextMenuSub
437
+ * -----------------------------------------------------------------------------------------------*/
438
+
439
+ const SUB_NAME = 'ContextMenuSub'
440
+
441
+ const ContextMenuSub = (props: ScopedProps<ContextMenuSubProps>) => {
442
+ const { scope, children, onOpenChange, open: openProp, defaultOpen, ...rest } = props
443
+ const [open, setOpen] = useControllableState({
444
+ prop: openProp,
445
+ defaultProp: defaultOpen!,
446
+ onChange: onOpenChange,
447
+ })
448
+
449
+ return (
450
+ <Menu.Sub
451
+ scope={scope || CONTEXTMENU_CONTEXT}
452
+ open={open}
453
+ onOpenChange={setOpen}
454
+ {...rest}
455
+ >
456
+ {children}
457
+ </Menu.Sub>
458
+ )
459
+ }
460
+
461
+ ContextMenuSub.displayName = SUB_NAME
462
+
463
+ /* -------------------------------------------------------------------------------------------------
464
+ * ContextMenuSubTrigger
465
+ * -----------------------------------------------------------------------------------------------*/
466
+
467
+ const SUB_TRIGGER_NAME = 'ContextMenuSubTrigger'
468
+
469
+ const ContextMenuSubTrigger = View.styleable<ScopedProps<ContextMenuSubTriggerProps>>(
470
+ (props, forwardedRef) => {
471
+ const { scope, ...subTriggerProps } = props
472
+ return (
473
+ <Menu.SubTrigger
474
+ componentName={SUB_TRIGGER_NAME}
475
+ scope={scope || CONTEXTMENU_CONTEXT}
476
+ {...subTriggerProps}
477
+ ref={forwardedRef}
478
+ />
479
+ )
480
+ }
481
+ )
482
+
483
+ ContextMenuSubTrigger.displayName = SUB_TRIGGER_NAME
484
+
485
+ /* -------------------------------------------------------------------------------------------------
486
+ * ContextMenuSubContent
487
+ * -----------------------------------------------------------------------------------------------*/
488
+
489
+ const SUB_CONTENT_NAME = 'ContextMenuSubContent'
490
+
491
+ const ContextMenuSubContent = React.forwardRef<
492
+ ContextMenuSubContentElement,
493
+ ScopedProps<ContextMenuSubContentProps>
494
+ >((props, forwardedRef) => {
495
+ const { scope, ...subContentProps } = props
496
+ return (
497
+ <Menu.SubContent
498
+ scope={scope || CONTEXTMENU_CONTEXT}
499
+ {...subContentProps}
500
+ ref={forwardedRef}
501
+ style={
502
+ isWeb
503
+ ? {
504
+ ...(props.style as Object),
505
+ ...({
506
+ '--tamagui-context-menu-content-transform-origin':
507
+ 'var(--tamagui-popper-transform-origin)',
508
+ '--tamagui-context-menu-content-available-width':
509
+ 'var(--tamagui-popper-available-width)',
510
+ '--tamagui-context-menu-content-available-height':
511
+ 'var(--tamagui-popper-available-height)',
512
+ '--tamagui-context-menu-trigger-width':
513
+ 'var(--tamagui-popper-anchor-width)',
514
+ '--tamagui-context-menu-trigger-height':
515
+ 'var(--tamagui-popper-anchor-height)',
516
+ } as React.CSSProperties),
517
+ }
518
+ : null
519
+ }
520
+ />
521
+ )
522
+ })
523
+
524
+ ContextMenuSubContent.displayName = SUB_CONTENT_NAME
525
+
526
+ /* -------------------------------------------------------------------------------------------------
527
+ * ContextMenuArrow
528
+ * -----------------------------------------------------------------------------------------------*/
529
+
530
+ const ARROW_NAME = 'ContextMenuArrow'
531
+
532
+ const ContextMenuArrow = React.forwardRef<
533
+ TamaguiElement,
534
+ ScopedProps<ContextMenuArrowProps>
535
+ >((props, forwardedRef) => {
536
+ const { scope, ...arrowProps } = props
537
+ return (
538
+ <Menu.Arrow
539
+ componentName={ARROW_NAME}
540
+ scope={scope || CONTEXTMENU_CONTEXT}
541
+ {...arrowProps}
542
+ ref={forwardedRef}
543
+ />
544
+ )
545
+ })
546
+
547
+ ContextMenuArrow.displayName = ARROW_NAME
548
+
549
+ /* -------------------------------------------------------------------------------------------------
550
+ * Pass-through components (same as Menu)
551
+ * -----------------------------------------------------------------------------------------------*/
552
+
553
+ const ContextMenuGroup = Menu.Group
554
+ const ContextMenuLabel = Menu.Label
555
+ const ContextMenuSeparator = Menu.Separator
556
+ const ContextMenuItemTitle = Menu.ItemTitle
557
+ const ContextMenuItemSubTitle = Menu.ItemSubtitle
558
+ const ContextMenuItemImage = Menu.ItemImage
559
+ const ContextMenuItemIcon = Menu.ItemIcon
560
+ const ContextMenuPreview = () => null // No-op on web
561
+
562
+ /* -----------------------------------------------------------------------------------------------*/
563
+
564
+ return withStaticProperties(ContextMenuComp, {
565
+ Root: ContextMenuComp,
566
+ Trigger: ContextMenuTrigger,
567
+ Portal: ContextMenuPortal,
568
+ Content: ContextMenuContent,
569
+ Group: ContextMenuGroup,
570
+ Label: ContextMenuLabel,
571
+ Item: ContextMenuItem,
572
+ CheckboxItem: ContextMenuCheckboxItem,
573
+ RadioGroup: ContextMenuRadioGroup,
574
+ RadioItem: ContextMenuRadioItem,
575
+ ItemIndicator: ContextMenuItemIndicator,
576
+ Separator: ContextMenuSeparator,
577
+ Arrow: ContextMenuArrow,
578
+ Sub: ContextMenuSub,
579
+ SubTrigger: ContextMenuSubTrigger,
580
+ SubContent: ContextMenuSubContent,
581
+ ItemTitle: ContextMenuItemTitle,
582
+ ItemSubtitle: ContextMenuItemSubTitle,
583
+ ItemIcon: ContextMenuItemIcon,
584
+ ItemImage: ContextMenuItemImage,
585
+ Preview: ContextMenuPreview,
586
+ })
587
+ }
588
+
589
+ export type {
590
+ ContextMenuArrowProps,
591
+ ContextMenuCheckboxItemProps,
592
+ ContextMenuContentProps,
593
+ ContextMenuGroupProps,
594
+ ContextMenuItemIconProps,
595
+ ContextMenuItemImageProps,
596
+ ContextMenuItemIndicatorProps,
597
+ ContextMenuItemProps,
598
+ ContextMenuPortalProps,
599
+ ContextMenuProps,
600
+ ContextMenuRadioGroupProps,
601
+ ContextMenuRadioItemProps,
602
+ ContextMenuSeparatorProps,
603
+ ContextMenuSubContentProps,
604
+ ContextMenuSubProps,
605
+ ContextMenuSubTriggerProps,
606
+ ContextMenuTriggerProps,
607
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,17 @@
1
+ import '@tamagui/polyfill-dev'
2
+
3
+ import { MenuPredefined } from '@tamagui/create-menu'
4
+
5
+ import { createContextMenu } from './ContextMenu'
6
+
7
+ export const ContextMenu = createContextMenu({
8
+ Icon: MenuPredefined.MenuIcon,
9
+ Image: MenuPredefined.MenuImage,
10
+ Indicator: MenuPredefined.MenuIndicator,
11
+ Item: MenuPredefined.MenuItem,
12
+ Label: MenuPredefined.MenuLabel,
13
+ MenuGroup: MenuPredefined.MenuGroup,
14
+ Separator: MenuPredefined.MenuSeparator,
15
+ SubTitle: MenuPredefined.SubTitle,
16
+ Title: MenuPredefined.Title,
17
+ })