@dxos/react-ui 0.6.13 → 0.6.14-main.69511f5

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 (126) hide show
  1. package/dist/lib/browser/index.mjs +731 -198
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +2925 -0
  5. package/dist/lib/node/index.cjs.map +7 -0
  6. package/dist/lib/node/meta.json +1 -0
  7. package/dist/lib/node-esm/index.mjs +2924 -0
  8. package/dist/lib/node-esm/index.mjs.map +7 -0
  9. package/dist/lib/node-esm/meta.json +1 -0
  10. package/dist/types/src/components/Avatars/Avatar.stories.d.ts +15 -1
  11. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts +6 -1
  13. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts +9 -1
  15. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Buttons/Button.d.ts +1 -1
  17. package/dist/types/src/components/Buttons/Button.d.ts.map +1 -1
  18. package/dist/types/src/components/Buttons/Button.stories.d.ts +12 -17
  19. package/dist/types/src/components/Buttons/Button.stories.d.ts.map +1 -1
  20. package/dist/types/src/components/Buttons/Toggle.stories.d.ts +2 -1
  21. package/dist/types/src/components/Buttons/Toggle.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts +20 -1
  23. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts.map +1 -1
  25. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts +12 -1
  26. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts +11 -1
  28. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Icon/Icon.d.ts +1 -1
  30. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  31. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  32. package/dist/types/src/components/Input/Input.stories.d.ts +16 -1
  33. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  34. package/dist/types/src/components/Lists/List.stories.d.ts +2 -3
  35. package/dist/types/src/components/Lists/List.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Lists/Tree.stories.d.ts +5 -1
  37. package/dist/types/src/components/Lists/Tree.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Lists/Treegrid.stories.d.ts +3 -2
  39. package/dist/types/src/components/Lists/Treegrid.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/Main/Main.stories.d.ts +5 -1
  41. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts +29 -1
  43. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/Menus/DropdownMenu.d.ts +105 -44
  45. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +1 -1
  46. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts +29 -1
  47. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts.map +1 -1
  48. package/dist/types/src/components/Message/Message.stories.d.ts +6 -1
  49. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Popover/Popover.d.ts +87 -21
  51. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  52. package/dist/types/src/components/Popover/Popover.stories.d.ts +20 -1
  53. package/dist/types/src/components/Popover/Popover.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +20 -1
  55. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  57. package/dist/types/src/components/Select/Select.stories.d.ts +10 -11
  58. package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/Status/Status.stories.d.ts +0 -3
  60. package/dist/types/src/components/Status/Status.stories.d.ts.map +1 -1
  61. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +4 -5
  62. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  63. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +4 -4
  64. package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
  65. package/dist/types/src/components/Toast/Toast.stories.d.ts +20 -1
  66. package/dist/types/src/components/Toast/Toast.stories.d.ts.map +1 -1
  67. package/dist/types/src/components/Toolbar/Toolbar.d.ts +4 -2
  68. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  69. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +30 -1
  70. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  71. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +13 -1
  72. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useThemeContext.d.ts.map +1 -1
  74. package/dist/types/src/playground/Controls.stories.d.ts +2 -6
  75. package/dist/types/src/playground/Controls.stories.d.ts.map +1 -1
  76. package/dist/types/src/playground/Surfaces.stories.d.ts +6 -2
  77. package/dist/types/src/playground/Surfaces.stories.d.ts.map +1 -1
  78. package/dist/types/src/playground/Typography.stories.d.ts +1 -1
  79. package/dist/types/src/testing/decorators/index.d.ts +1 -0
  80. package/dist/types/src/testing/decorators/index.d.ts.map +1 -1
  81. package/dist/types/src/testing/decorators/withVariants.d.ts +13 -0
  82. package/dist/types/src/testing/decorators/withVariants.d.ts.map +1 -0
  83. package/package.json +28 -17
  84. package/src/components/Avatars/Avatar.stories.tsx +3 -2
  85. package/src/components/Avatars/AvatarGroup.stories.tsx +3 -2
  86. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +3 -2
  87. package/src/components/Buttons/Button.stories.tsx +34 -63
  88. package/src/components/Buttons/Button.tsx +46 -36
  89. package/src/components/Buttons/Toggle.stories.tsx +3 -2
  90. package/src/components/Buttons/ToggleGroup.stories.tsx +3 -2
  91. package/src/components/DensityProvider/DensityProvider.tsx +1 -1
  92. package/src/components/Dialogs/AlertDialog.stories.tsx +3 -2
  93. package/src/components/Dialogs/Dialog.stories.tsx +3 -2
  94. package/src/components/Icon/Icon.tsx +11 -9
  95. package/src/components/Input/Input.stories.tsx +4 -3
  96. package/src/components/Link/Link.stories.tsx +1 -1
  97. package/src/components/Lists/List.stories.tsx +4 -4
  98. package/src/components/Lists/Tree.stories.tsx +3 -2
  99. package/src/components/Lists/Treegrid.stories.tsx +7 -5
  100. package/src/components/Main/Main.stories.tsx +3 -2
  101. package/src/components/Menus/ContextMenu.stories.tsx +3 -2
  102. package/src/components/Menus/DropdownMenu.stories.tsx +43 -3
  103. package/src/components/Menus/DropdownMenu.tsx +518 -69
  104. package/src/components/Message/Message.stories.tsx +3 -2
  105. package/src/components/Popover/Popover.stories.tsx +27 -3
  106. package/src/components/Popover/Popover.tsx +524 -55
  107. package/src/components/ScrollArea/ScrollArea.stories.tsx +3 -2
  108. package/src/components/Select/Select.stories.tsx +14 -31
  109. package/src/components/Select/Select.tsx +9 -10
  110. package/src/components/Status/Status.stories.tsx +1 -2
  111. package/src/components/Tag/Tag.stories.tsx +1 -1
  112. package/src/components/ThemeProvider/ThemeProvider.tsx +17 -18
  113. package/src/components/Toast/Toast.stories.tsx +3 -2
  114. package/src/components/Toast/Toast.tsx +1 -4
  115. package/src/components/Toolbar/Toolbar.stories.tsx +3 -2
  116. package/src/components/Toolbar/Toolbar.tsx +21 -1
  117. package/src/components/Tooltip/Tooltip.stories.tsx +3 -2
  118. package/src/hooks/useThemeContext.ts +3 -1
  119. package/src/playground/Controls.stories.tsx +7 -10
  120. package/src/playground/Surfaces.stories.tsx +4 -3
  121. package/src/playground/Typography.stories.tsx +2 -2
  122. package/src/testing/decorators/index.ts +1 -0
  123. package/src/testing/decorators/withVariants.tsx +45 -0
  124. package/dist/types/src/playground/helpers.d.ts +0 -6
  125. package/dist/types/src/playground/helpers.d.ts.map +0 -1
  126. package/src/playground/helpers.tsx +0 -32
@@ -1,58 +1,209 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
- import { type Scope, type ScopeHook } from '@radix-ui/react-context';
5
- import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
6
- import { type DropdownMenuContextValue } from '@radix-ui/react-dropdown-menu';
4
+
5
+ // This is based upon `@radix-ui/react-dropdown-menu` fetched 25 Oct 2024 at https://github.com/radix-ui/primitives at commit 06de2d4.
6
+
7
+ import { composeEventHandlers } from '@radix-ui/primitive';
8
+ import { composeRefs } from '@radix-ui/react-compose-refs';
9
+ import { createContextScope } from '@radix-ui/react-context';
10
+ import type { Scope } from '@radix-ui/react-context';
11
+ import { useId } from '@radix-ui/react-id';
12
+ import * as MenuPrimitive from '@radix-ui/react-menu';
13
+ import { createMenuScope } from '@radix-ui/react-menu';
7
14
  import { Primitive } from '@radix-ui/react-primitive';
8
15
  import { Slot } from '@radix-ui/react-slot';
9
- import React, { type ComponentPropsWithRef, forwardRef } from 'react';
16
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
17
+ import React, {
18
+ type ReactNode,
19
+ type FC,
20
+ useRef,
21
+ type ElementRef,
22
+ useCallback,
23
+ type ComponentPropsWithoutRef,
24
+ forwardRef,
25
+ type ComponentPropsWithRef,
26
+ useEffect,
27
+ type MutableRefObject,
28
+ type RefObject,
29
+ } from 'react';
10
30
 
11
31
  import { useThemeContext } from '../../hooks';
12
32
  import { type ThemedClassName } from '../../util';
13
- import { ElevationProvider } from '../ElevationProvider';
14
33
 
15
- type DropdownMenuRootProps = DropdownMenuPrimitive.DropdownMenuProps;
34
+ type Direction = 'ltr' | 'rtl';
35
+
36
+ /* -------------------------------------------------------------------------------------------------
37
+ * DropdownMenu
38
+ * ----------------------------------------------------------------------------------------------- */
39
+
40
+ const DROPDOWN_MENU_NAME = 'DropdownMenu';
16
41
 
17
- const DropdownMenuRoot = DropdownMenuPrimitive.DropdownMenu;
42
+ type ScopedProps<P> = P & { __scopeDropdownMenu?: Scope };
43
+ const [createDropdownMenuContext, createDropdownMenuScope] = createContextScope(DROPDOWN_MENU_NAME, [createMenuScope]);
44
+ const useMenuScope = createMenuScope();
18
45
 
19
- type DropdownMenuTriggerProps = DropdownMenuPrimitive.DropdownMenuTriggerProps;
46
+ type DropdownMenuContextValue = {
47
+ triggerId: string;
48
+ triggerRef: MutableRefObject<HTMLButtonElement | null>;
49
+ contentId: string;
50
+ open: boolean;
51
+ onOpenChange(open: boolean): void;
52
+ onOpenToggle(): void;
53
+ modal: boolean;
54
+ };
20
55
 
21
- const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
56
+ const [DropdownMenuProvider, useDropdownMenuContext] =
57
+ createDropdownMenuContext<DropdownMenuContextValue>(DROPDOWN_MENU_NAME);
22
58
 
23
- type DropdownMenuPortalProps = DropdownMenuPrimitive.DropdownMenuPortalProps;
59
+ interface DropdownMenuRootProps {
60
+ children?: ReactNode;
61
+ dir?: Direction;
62
+ open?: boolean;
63
+ defaultOpen?: boolean;
64
+ onOpenChange?(open: boolean): void;
65
+ modal?: boolean;
66
+ }
24
67
 
25
- const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
68
+ const DropdownMenuRoot: FC<DropdownMenuRootProps> = (props: ScopedProps<DropdownMenuRootProps>) => {
69
+ const { __scopeDropdownMenu, children, dir, open: openProp, defaultOpen, onOpenChange, modal = true } = props;
70
+ const menuScope = useMenuScope(__scopeDropdownMenu);
71
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
72
+ const [open = false, setOpen] = useControllableState({
73
+ prop: openProp,
74
+ defaultProp: defaultOpen,
75
+ onChange: onOpenChange,
76
+ });
26
77
 
27
- type DropdownMenuContentProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuContentProps> & {
28
- constrainBlockSize?: boolean;
78
+ return (
79
+ <DropdownMenuProvider
80
+ scope={__scopeDropdownMenu}
81
+ triggerId={useId()}
82
+ triggerRef={triggerRef as MutableRefObject<HTMLButtonElement | null>}
83
+ contentId={useId()}
84
+ open={open}
85
+ onOpenChange={setOpen}
86
+ onOpenToggle={useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen])}
87
+ modal={modal}
88
+ >
89
+ <MenuPrimitive.Root {...menuScope} open={open} onOpenChange={setOpen} dir={dir} modal={modal}>
90
+ {children}
91
+ </MenuPrimitive.Root>
92
+ </DropdownMenuProvider>
93
+ );
29
94
  };
30
95
 
31
- // @ts-ignore
32
- const useDropdownMenuContext: (
33
- consumerName: string,
34
- scope: Scope<DropdownMenuContextValue | undefined>,
35
- ) => DropdownMenuContextValue = DropdownMenuPrimitive.useDropdownMenuContext;
36
- // @ts-ignore
37
- const useDropdownMenuMenuScope: ScopeHook = DropdownMenuPrimitive.useDropdownMenuMenuScope;
96
+ DropdownMenuRoot.displayName = DROPDOWN_MENU_NAME;
38
97
 
39
- const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
40
- ({ classNames, children, ...props }, forwardedRef) => {
41
- const { tx } = useThemeContext();
98
+ /* -------------------------------------------------------------------------------------------------
99
+ * DropdownMenuTrigger
100
+ * ----------------------------------------------------------------------------------------------- */
101
+
102
+ const TRIGGER_NAME = 'DropdownMenuTrigger';
103
+
104
+ type DropdownMenuTriggerElement = ElementRef<typeof Primitive.button>;
105
+ type PrimitiveButtonProps = ComponentPropsWithoutRef<typeof Primitive.button>;
106
+ interface DropdownMenuTriggerProps extends PrimitiveButtonProps {}
107
+
108
+ const DropdownMenuTrigger = forwardRef<DropdownMenuTriggerElement, DropdownMenuTriggerProps>(
109
+ (props: ScopedProps<DropdownMenuTriggerProps>, forwardedRef) => {
110
+ const { __scopeDropdownMenu, disabled = false, ...triggerProps } = props;
111
+ const context = useDropdownMenuContext(TRIGGER_NAME, __scopeDropdownMenu);
112
+ const menuScope = useMenuScope(__scopeDropdownMenu);
42
113
  return (
43
- <DropdownMenuPrimitive.Content
44
- sideOffset={4}
45
- collisionPadding={8}
46
- {...props}
47
- className={tx('menu.content', 'menu', {}, classNames)}
48
- ref={forwardedRef}
49
- >
50
- <ElevationProvider elevation='chrome'>{children}</ElevationProvider>
51
- </DropdownMenuPrimitive.Content>
114
+ <MenuPrimitive.Anchor asChild {...menuScope}>
115
+ <Primitive.button
116
+ type='button'
117
+ id={context.triggerId}
118
+ aria-haspopup='menu'
119
+ aria-expanded={context.open}
120
+ aria-controls={context.open ? context.contentId : undefined}
121
+ data-state={context.open ? 'open' : 'closed'}
122
+ data-disabled={disabled ? '' : undefined}
123
+ disabled={disabled}
124
+ {...triggerProps}
125
+ ref={composeRefs(forwardedRef, context.triggerRef)}
126
+ onPointerDown={composeEventHandlers(props.onPointerDown, (event) => {
127
+ // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
128
+ // but not when the control key is pressed (avoiding MacOS right click)
129
+ if (!disabled && event.button === 0 && event.ctrlKey === false) {
130
+ context.onOpenToggle();
131
+ // prevent trigger focusing when opening
132
+ // this allows the content to be given focus without competition
133
+ if (!context.open) {
134
+ event.preventDefault();
135
+ }
136
+ }
137
+ })}
138
+ onKeyDown={composeEventHandlers(props.onKeyDown, (event) => {
139
+ if (disabled) {
140
+ return;
141
+ }
142
+ if (['Enter', ' '].includes(event.key)) {
143
+ context.onOpenToggle();
144
+ }
145
+ if (event.key === 'ArrowDown') {
146
+ context.onOpenChange(true);
147
+ }
148
+ // prevent keydown from scrolling window / first focused item to execute
149
+ // that keydown (inadvertently closing the menu)
150
+ if (['Enter', ' ', 'ArrowDown'].includes(event.key)) {
151
+ event.preventDefault();
152
+ }
153
+ })}
154
+ />
155
+ </MenuPrimitive.Anchor>
52
156
  );
53
157
  },
54
158
  );
55
159
 
160
+ DropdownMenuTrigger.displayName = TRIGGER_NAME;
161
+
162
+ /* -------------------------------------------------------------------------------------------------
163
+ * DropdownMenuVirtualTrigger
164
+ * ----------------------------------------------------------------------------------------------- */
165
+
166
+ const VIRTUAL_TRIGGER_NAME = 'DropdownMenuVirtualTrigger';
167
+
168
+ interface DropdownMenuVirtualTriggerProps {
169
+ virtualRef: RefObject<DropdownMenuTriggerElement>;
170
+ }
171
+
172
+ const DropdownMenuVirtualTrigger = (props: ScopedProps<DropdownMenuVirtualTriggerProps>) => {
173
+ const { __scopeDropdownMenu, virtualRef } = props;
174
+ const context = useDropdownMenuContext(VIRTUAL_TRIGGER_NAME, __scopeDropdownMenu);
175
+ const menuScope = useMenuScope(__scopeDropdownMenu);
176
+ useEffect(() => {
177
+ if (virtualRef.current) {
178
+ context.triggerRef.current = virtualRef.current;
179
+ }
180
+ });
181
+ return <MenuPrimitive.Anchor {...menuScope} virtualRef={virtualRef} />;
182
+ };
183
+
184
+ DropdownMenuVirtualTrigger.displayName = VIRTUAL_TRIGGER_NAME;
185
+
186
+ /* -------------------------------------------------------------------------------------------------
187
+ * DropdownMenuPortal
188
+ * ----------------------------------------------------------------------------------------------- */
189
+
190
+ const PORTAL_NAME = 'DropdownMenuPortal';
191
+
192
+ type MenuPortalProps = ComponentPropsWithoutRef<typeof MenuPrimitive.Portal>;
193
+ interface DropdownMenuPortalProps extends MenuPortalProps {}
194
+
195
+ const DropdownMenuPortal: FC<DropdownMenuPortalProps> = (props: ScopedProps<DropdownMenuPortalProps>) => {
196
+ const { __scopeDropdownMenu, ...portalProps } = props;
197
+ const menuScope = useMenuScope(__scopeDropdownMenu);
198
+ return <MenuPrimitive.Portal {...menuScope} {...portalProps} />;
199
+ };
200
+
201
+ DropdownMenuPortal.displayName = PORTAL_NAME;
202
+
203
+ /* -------------------------------------------------------------------------------------------------
204
+ * DropdownMenuViewport
205
+ * ----------------------------------------------------------------------------------------------- */
206
+
56
207
  type DropdownMenuViewportProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>> & {
57
208
  asChild?: boolean;
58
209
  };
@@ -69,37 +220,133 @@ const DropdownMenuViewport = forwardRef<HTMLDivElement, DropdownMenuViewportProp
69
220
  },
70
221
  );
71
222
 
72
- type DropdownMenuArrowProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuArrowProps>;
223
+ /* -------------------------------------------------------------------------------------------------
224
+ * DropdownMenuContent
225
+ * ----------------------------------------------------------------------------------------------- */
73
226
 
74
- const DropdownMenuArrow = forwardRef<SVGSVGElement, DropdownMenuArrowProps>(
75
- ({ classNames, ...props }, forwardedRef) => {
227
+ const CONTENT_NAME = 'DropdownMenuContent';
228
+
229
+ type DropdownMenuContentElement = ElementRef<typeof MenuPrimitive.Content>;
230
+ type MenuContentProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.Content>>;
231
+ interface DropdownMenuContentProps extends Omit<MenuContentProps, 'onEntryFocus'> {}
232
+
233
+ const DropdownMenuContent = forwardRef<DropdownMenuContentElement, DropdownMenuContentProps>(
234
+ (props: ScopedProps<DropdownMenuContentProps>, forwardedRef) => {
235
+ const { __scopeDropdownMenu, classNames, ...contentProps } = props;
76
236
  const { tx } = useThemeContext();
237
+ const context = useDropdownMenuContext(CONTENT_NAME, __scopeDropdownMenu);
238
+ const menuScope = useMenuScope(__scopeDropdownMenu);
239
+ const hasInteractedOutsideRef = useRef(false);
240
+
77
241
  return (
78
- <DropdownMenuPrimitive.Arrow
79
- {...props}
80
- className={tx('menu.arrow', 'menu__arrow', {}, classNames)}
242
+ <MenuPrimitive.Content
243
+ id={context.contentId}
244
+ aria-labelledby={context.triggerId}
245
+ {...menuScope}
246
+ {...contentProps}
81
247
  ref={forwardedRef}
248
+ onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
249
+ if (!hasInteractedOutsideRef.current) {
250
+ context.triggerRef.current?.focus();
251
+ }
252
+ hasInteractedOutsideRef.current = false;
253
+ // Always prevent auto focus because we either focus manually or want user agent focus
254
+ event.preventDefault();
255
+ })}
256
+ onInteractOutside={composeEventHandlers(props.onInteractOutside, (event) => {
257
+ const originalEvent = event.detail.originalEvent as PointerEvent;
258
+ const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true;
259
+ const isRightClick = originalEvent.button === 2 || ctrlLeftClick;
260
+ if (!context.modal || isRightClick) {
261
+ hasInteractedOutsideRef.current = true;
262
+ }
263
+ })}
264
+ className={tx('menu.content', 'menu', {}, classNames)}
265
+ style={{
266
+ ...props.style,
267
+ // re-namespace exposed content custom properties
268
+ ...{
269
+ '--radix-dropdown-menu-content-transform-origin': 'var(--radix-popper-transform-origin)',
270
+ '--radix-dropdown-menu-content-available-width': 'var(--radix-popper-available-width)',
271
+ '--radix-dropdown-menu-content-available-height': 'var(--radix-popper-available-height)',
272
+ '--radix-dropdown-menu-trigger-width': 'var(--radix-popper-anchor-width)',
273
+ '--radix-dropdown-menu-trigger-height': 'var(--radix-popper-anchor-height)',
274
+ },
275
+ }}
82
276
  />
83
277
  );
84
278
  },
85
279
  );
86
280
 
87
- type DropdownMenuGroupProps = DropdownMenuPrimitive.DropdownMenuGroupProps;
281
+ DropdownMenuContent.displayName = CONTENT_NAME;
282
+
283
+ /* -------------------------------------------------------------------------------------------------
284
+ * DropdownMenuGroup
285
+ * ----------------------------------------------------------------------------------------------- */
286
+
287
+ const GROUP_NAME = 'DropdownMenuGroup';
288
+
289
+ type DropdownMenuGroupElement = ElementRef<typeof MenuPrimitive.Group>;
290
+ type MenuGroupProps = ComponentPropsWithoutRef<typeof MenuPrimitive.Group>;
291
+ interface DropdownMenuGroupProps extends MenuGroupProps {}
292
+
293
+ const DropdownMenuGroup = forwardRef<DropdownMenuGroupElement, DropdownMenuGroupProps>(
294
+ (props: ScopedProps<DropdownMenuGroupProps>, forwardedRef) => {
295
+ const { __scopeDropdownMenu, ...groupProps } = props;
296
+ const menuScope = useMenuScope(__scopeDropdownMenu);
297
+ return <MenuPrimitive.Group {...menuScope} {...groupProps} ref={forwardedRef} />;
298
+ },
299
+ );
88
300
 
89
- const DropdownMenuGroup = DropdownMenuPrimitive.Group;
301
+ DropdownMenuGroup.displayName = GROUP_NAME;
90
302
 
91
- type DropdownMenuItemIndicatorProps = DropdownMenuPrimitive.DropdownMenuItemIndicatorProps;
303
+ /* -------------------------------------------------------------------------------------------------
304
+ * DropdownMenuLabel
305
+ * ----------------------------------------------------------------------------------------------- */
92
306
 
93
- const DropdownMenuItemIndicator = DropdownMenuPrimitive.ItemIndicator;
307
+ const LABEL_NAME = 'DropdownMenuLabel';
94
308
 
95
- type DropdownMenuItemProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuItemProps>;
309
+ type DropdownMenuLabelElement = ElementRef<typeof MenuPrimitive.Label>;
310
+ type MenuLabelProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.Label>>;
311
+ interface DropdownMenuLabelProps extends MenuLabelProps {}
96
312
 
97
- const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
98
- ({ classNames, ...props }: DropdownMenuItemProps, forwardedRef) => {
313
+ const DropdownMenuGroupLabel = forwardRef<DropdownMenuLabelElement, DropdownMenuLabelProps>(
314
+ (props: ScopedProps<DropdownMenuLabelProps>, forwardedRef) => {
315
+ const { __scopeDropdownMenu, classNames, ...labelProps } = props;
316
+ const menuScope = useMenuScope(__scopeDropdownMenu);
99
317
  const { tx } = useThemeContext();
100
318
  return (
101
- <DropdownMenuPrimitive.Item
102
- {...props}
319
+ <MenuPrimitive.Label
320
+ {...menuScope}
321
+ {...labelProps}
322
+ className={tx('menu.groupLabel', 'menu__group__label', {}, classNames)}
323
+ ref={forwardedRef}
324
+ />
325
+ );
326
+ },
327
+ );
328
+
329
+ DropdownMenuGroupLabel.displayName = LABEL_NAME;
330
+
331
+ /* -------------------------------------------------------------------------------------------------
332
+ * DropdownMenuItem
333
+ * ----------------------------------------------------------------------------------------------- */
334
+
335
+ const ITEM_NAME = 'DropdownMenuItem';
336
+
337
+ type DropdownMenuItemElement = ElementRef<typeof MenuPrimitive.Item>;
338
+ type MenuItemProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.Item>>;
339
+ interface DropdownMenuItemProps extends MenuItemProps {}
340
+
341
+ const DropdownMenuItem = forwardRef<DropdownMenuItemElement, DropdownMenuItemProps>(
342
+ (props: ScopedProps<DropdownMenuItemProps>, forwardedRef) => {
343
+ const { __scopeDropdownMenu, classNames, ...itemProps } = props;
344
+ const menuScope = useMenuScope(__scopeDropdownMenu);
345
+ const { tx } = useThemeContext();
346
+ return (
347
+ <MenuPrimitive.Item
348
+ {...menuScope}
349
+ {...itemProps}
103
350
  className={tx('menu.item', 'menu__item', {}, classNames)}
104
351
  ref={forwardedRef}
105
352
  />
@@ -107,14 +354,27 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
107
354
  },
108
355
  );
109
356
 
110
- type DropdownMenuCheckboxItemProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuCheckboxItemProps>;
357
+ DropdownMenuItem.displayName = ITEM_NAME;
358
+
359
+ /* -------------------------------------------------------------------------------------------------
360
+ * DropdownMenuCheckboxItem
361
+ * ----------------------------------------------------------------------------------------------- */
111
362
 
112
- const DropdownMenuCheckboxItem = forwardRef<HTMLDivElement, DropdownMenuCheckboxItemProps>(
113
- ({ classNames, ...props }: DropdownMenuItemProps, forwardedRef) => {
363
+ const CHECKBOX_ITEM_NAME = 'DropdownMenuCheckboxItem';
364
+
365
+ type DropdownMenuCheckboxItemElement = ElementRef<typeof MenuPrimitive.CheckboxItem>;
366
+ type MenuCheckboxItemProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.CheckboxItem>>;
367
+ interface DropdownMenuCheckboxItemProps extends MenuCheckboxItemProps {}
368
+
369
+ const DropdownMenuCheckboxItem = forwardRef<DropdownMenuCheckboxItemElement, DropdownMenuCheckboxItemProps>(
370
+ (props: ScopedProps<DropdownMenuCheckboxItemProps>, forwardedRef) => {
371
+ const { __scopeDropdownMenu, classNames, ...checkboxItemProps } = props;
372
+ const menuScope = useMenuScope(__scopeDropdownMenu);
114
373
  const { tx } = useThemeContext();
115
374
  return (
116
- <DropdownMenuPrimitive.CheckboxItem
117
- {...props}
375
+ <MenuPrimitive.CheckboxItem
376
+ {...menuScope}
377
+ {...checkboxItemProps}
118
378
  className={tx('menu.item', 'menu__item--checkbox', {}, classNames)}
119
379
  ref={forwardedRef}
120
380
  />
@@ -122,14 +382,87 @@ const DropdownMenuCheckboxItem = forwardRef<HTMLDivElement, DropdownMenuCheckbox
122
382
  },
123
383
  );
124
384
 
125
- type DropdownMenuSeparatorProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuSeparatorProps>;
385
+ DropdownMenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME;
386
+
387
+ /* -------------------------------------------------------------------------------------------------
388
+ * DropdownMenuRadioGroup
389
+ * ----------------------------------------------------------------------------------------------- */
390
+
391
+ const RADIO_GROUP_NAME = 'DropdownMenuRadioGroup';
126
392
 
127
- const DropdownMenuSeparator = forwardRef<HTMLDivElement, DropdownMenuSeparatorProps>(
128
- ({ classNames, ...props }, forwardedRef) => {
393
+ type DropdownMenuRadioGroupElement = ElementRef<typeof MenuPrimitive.RadioGroup>;
394
+ type MenuRadioGroupProps = ComponentPropsWithoutRef<typeof MenuPrimitive.RadioGroup>;
395
+ interface DropdownMenuRadioGroupProps extends MenuRadioGroupProps {}
396
+
397
+ const DropdownMenuRadioGroup = forwardRef<DropdownMenuRadioGroupElement, DropdownMenuRadioGroupProps>(
398
+ (props: ScopedProps<DropdownMenuRadioGroupProps>, forwardedRef) => {
399
+ const { __scopeDropdownMenu, ...radioGroupProps } = props;
400
+ const menuScope = useMenuScope(__scopeDropdownMenu);
401
+ return <MenuPrimitive.RadioGroup {...menuScope} {...radioGroupProps} ref={forwardedRef} />;
402
+ },
403
+ );
404
+
405
+ DropdownMenuRadioGroup.displayName = RADIO_GROUP_NAME;
406
+
407
+ /* -------------------------------------------------------------------------------------------------
408
+ * DropdownMenuRadioItem
409
+ * ----------------------------------------------------------------------------------------------- */
410
+
411
+ const RADIO_ITEM_NAME = 'DropdownMenuRadioItem';
412
+
413
+ type DropdownMenuRadioItemElement = ElementRef<typeof MenuPrimitive.RadioItem>;
414
+ type MenuRadioItemProps = ComponentPropsWithoutRef<typeof MenuPrimitive.RadioItem>;
415
+ interface DropdownMenuRadioItemProps extends MenuRadioItemProps {}
416
+
417
+ const DropdownMenuRadioItem = forwardRef<DropdownMenuRadioItemElement, DropdownMenuRadioItemProps>(
418
+ (props: ScopedProps<DropdownMenuRadioItemProps>, forwardedRef) => {
419
+ const { __scopeDropdownMenu, ...radioItemProps } = props;
420
+ const menuScope = useMenuScope(__scopeDropdownMenu);
421
+ return <MenuPrimitive.RadioItem {...menuScope} {...radioItemProps} ref={forwardedRef} />;
422
+ },
423
+ );
424
+
425
+ DropdownMenuRadioItem.displayName = RADIO_ITEM_NAME;
426
+
427
+ /* -------------------------------------------------------------------------------------------------
428
+ * DropdownMenuItemIndicator
429
+ * ----------------------------------------------------------------------------------------------- */
430
+
431
+ const INDICATOR_NAME = 'DropdownMenuItemIndicator';
432
+
433
+ type DropdownMenuItemIndicatorElement = ElementRef<typeof MenuPrimitive.ItemIndicator>;
434
+ type MenuItemIndicatorProps = ComponentPropsWithoutRef<typeof MenuPrimitive.ItemIndicator>;
435
+ interface DropdownMenuItemIndicatorProps extends MenuItemIndicatorProps {}
436
+
437
+ const DropdownMenuItemIndicator = forwardRef<DropdownMenuItemIndicatorElement, DropdownMenuItemIndicatorProps>(
438
+ (props: ScopedProps<DropdownMenuItemIndicatorProps>, forwardedRef) => {
439
+ const { __scopeDropdownMenu, ...itemIndicatorProps } = props;
440
+ const menuScope = useMenuScope(__scopeDropdownMenu);
441
+ return <MenuPrimitive.ItemIndicator {...menuScope} {...itemIndicatorProps} ref={forwardedRef} />;
442
+ },
443
+ );
444
+
445
+ DropdownMenuItemIndicator.displayName = INDICATOR_NAME;
446
+
447
+ /* -------------------------------------------------------------------------------------------------
448
+ * DropdownMenuSeparator
449
+ * ----------------------------------------------------------------------------------------------- */
450
+
451
+ const SEPARATOR_NAME = 'DropdownMenuSeparator';
452
+
453
+ type DropdownMenuSeparatorElement = ElementRef<typeof MenuPrimitive.Separator>;
454
+ type MenuSeparatorProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.Separator>>;
455
+ interface DropdownMenuSeparatorProps extends MenuSeparatorProps {}
456
+
457
+ const DropdownMenuSeparator = forwardRef<DropdownMenuSeparatorElement, DropdownMenuSeparatorProps>(
458
+ (props: ScopedProps<DropdownMenuSeparatorProps>, forwardedRef) => {
459
+ const { __scopeDropdownMenu, classNames, ...separatorProps } = props;
460
+ const menuScope = useMenuScope(__scopeDropdownMenu);
129
461
  const { tx } = useThemeContext();
130
462
  return (
131
- <DropdownMenuPrimitive.Separator
132
- {...props}
463
+ <MenuPrimitive.Separator
464
+ {...menuScope}
465
+ {...separatorProps}
133
466
  className={tx('menu.separator', 'menu__item', {}, classNames)}
134
467
  ref={forwardedRef}
135
468
  />
@@ -137,49 +470,165 @@ const DropdownMenuSeparator = forwardRef<HTMLDivElement, DropdownMenuSeparatorPr
137
470
  },
138
471
  );
139
472
 
140
- type DropdownMenuGroupLabelProps = ThemedClassName<DropdownMenuPrimitive.DropdownMenuLabelProps>;
473
+ DropdownMenuSeparator.displayName = SEPARATOR_NAME;
474
+
475
+ /* -------------------------------------------------------------------------------------------------
476
+ * DropdownMenuArrow
477
+ * ----------------------------------------------------------------------------------------------- */
478
+
479
+ const ARROW_NAME = 'DropdownMenuArrow';
480
+
481
+ type DropdownMenuArrowElement = ElementRef<typeof MenuPrimitive.Arrow>;
482
+ type MenuArrowProps = ThemedClassName<ComponentPropsWithoutRef<typeof MenuPrimitive.Arrow>>;
483
+ interface DropdownMenuArrowProps extends MenuArrowProps {}
141
484
 
142
- const DropdownMenuGroupLabel = forwardRef<HTMLDivElement, DropdownMenuGroupLabelProps>(
143
- ({ classNames, ...props }, forwardedRef) => {
485
+ const DropdownMenuArrow = forwardRef<DropdownMenuArrowElement, DropdownMenuArrowProps>(
486
+ (props: ScopedProps<DropdownMenuArrowProps>, forwardedRef) => {
487
+ const { __scopeDropdownMenu, classNames, ...arrowProps } = props;
488
+ const menuScope = useMenuScope(__scopeDropdownMenu);
144
489
  const { tx } = useThemeContext();
145
490
  return (
146
- <DropdownMenuPrimitive.Label
147
- {...props}
148
- className={tx('menu.groupLabel', 'menu__group__label', {}, classNames)}
491
+ <MenuPrimitive.Arrow
492
+ {...menuScope}
493
+ {...arrowProps}
494
+ className={tx('menu.arrow', 'menu__arrow', {}, classNames)}
149
495
  ref={forwardedRef}
150
496
  />
151
497
  );
152
498
  },
153
499
  );
154
500
 
501
+ DropdownMenuArrow.displayName = ARROW_NAME;
502
+
503
+ /* -------------------------------------------------------------------------------------------------
504
+ * DropdownMenuSub
505
+ * ----------------------------------------------------------------------------------------------- */
506
+
507
+ interface DropdownMenuSubProps {
508
+ children?: ReactNode;
509
+ open?: boolean;
510
+ defaultOpen?: boolean;
511
+ onOpenChange?(open: boolean): void;
512
+ }
513
+
514
+ const DropdownMenuSub: FC<DropdownMenuSubProps> = (props: ScopedProps<DropdownMenuSubProps>) => {
515
+ const { __scopeDropdownMenu, children, open: openProp, onOpenChange, defaultOpen } = props;
516
+ const menuScope = useMenuScope(__scopeDropdownMenu);
517
+ const [open = false, setOpen] = useControllableState({
518
+ prop: openProp,
519
+ defaultProp: defaultOpen,
520
+ onChange: onOpenChange,
521
+ });
522
+
523
+ return (
524
+ <MenuPrimitive.Sub {...menuScope} open={open} onOpenChange={setOpen}>
525
+ {children}
526
+ </MenuPrimitive.Sub>
527
+ );
528
+ };
529
+
530
+ /* -------------------------------------------------------------------------------------------------
531
+ * DropdownMenuSubTrigger
532
+ * ----------------------------------------------------------------------------------------------- */
533
+
534
+ const SUB_TRIGGER_NAME = 'DropdownMenuSubTrigger';
535
+
536
+ type DropdownMenuSubTriggerElement = ElementRef<typeof MenuPrimitive.SubTrigger>;
537
+ type MenuSubTriggerProps = ComponentPropsWithoutRef<typeof MenuPrimitive.SubTrigger>;
538
+ interface DropdownMenuSubTriggerProps extends MenuSubTriggerProps {}
539
+
540
+ const DropdownMenuSubTrigger = forwardRef<DropdownMenuSubTriggerElement, DropdownMenuSubTriggerProps>(
541
+ (props: ScopedProps<DropdownMenuSubTriggerProps>, forwardedRef) => {
542
+ const { __scopeDropdownMenu, ...subTriggerProps } = props;
543
+ const menuScope = useMenuScope(__scopeDropdownMenu);
544
+ return <MenuPrimitive.SubTrigger {...menuScope} {...subTriggerProps} ref={forwardedRef} />;
545
+ },
546
+ );
547
+
548
+ DropdownMenuSubTrigger.displayName = SUB_TRIGGER_NAME;
549
+
550
+ /* -------------------------------------------------------------------------------------------------
551
+ * DropdownMenuSubContent
552
+ * ----------------------------------------------------------------------------------------------- */
553
+
554
+ const SUB_CONTENT_NAME = 'DropdownMenuSubContent';
555
+
556
+ type DropdownMenuSubContentElement = ElementRef<typeof MenuPrimitive.Content>;
557
+ type MenuSubContentProps = ComponentPropsWithoutRef<typeof MenuPrimitive.SubContent>;
558
+ interface DropdownMenuSubContentProps extends MenuSubContentProps {}
559
+
560
+ const DropdownMenuSubContent = forwardRef<DropdownMenuSubContentElement, DropdownMenuSubContentProps>(
561
+ (props: ScopedProps<DropdownMenuSubContentProps>, forwardedRef) => {
562
+ const { __scopeDropdownMenu, ...subContentProps } = props;
563
+ const menuScope = useMenuScope(__scopeDropdownMenu);
564
+
565
+ return (
566
+ <MenuPrimitive.SubContent
567
+ {...menuScope}
568
+ {...subContentProps}
569
+ ref={forwardedRef}
570
+ style={{
571
+ ...props.style,
572
+ // re-namespace exposed content custom properties
573
+ ...{
574
+ '--radix-dropdown-menu-content-transform-origin': 'var(--radix-popper-transform-origin)',
575
+ '--radix-dropdown-menu-content-available-width': 'var(--radix-popper-available-width)',
576
+ '--radix-dropdown-menu-content-available-height': 'var(--radix-popper-available-height)',
577
+ '--radix-dropdown-menu-trigger-width': 'var(--radix-popper-anchor-width)',
578
+ '--radix-dropdown-menu-trigger-height': 'var(--radix-popper-anchor-height)',
579
+ },
580
+ }}
581
+ />
582
+ );
583
+ },
584
+ );
585
+
586
+ DropdownMenuSubContent.displayName = SUB_CONTENT_NAME;
587
+
588
+ /* ----------------------------------------------------------------------------------------------- */
589
+
155
590
  export const DropdownMenu = {
156
591
  Root: DropdownMenuRoot,
157
592
  Trigger: DropdownMenuTrigger,
593
+ VirtualTrigger: DropdownMenuVirtualTrigger,
158
594
  Portal: DropdownMenuPortal,
159
595
  Content: DropdownMenuContent,
160
596
  Viewport: DropdownMenuViewport,
161
- Arrow: DropdownMenuArrow,
162
597
  Group: DropdownMenuGroup,
598
+ GroupLabel: DropdownMenuGroupLabel,
163
599
  Item: DropdownMenuItem,
164
600
  CheckboxItem: DropdownMenuCheckboxItem,
601
+ RadioGroup: DropdownMenuRadioGroup,
602
+ RadioItem: DropdownMenuRadioItem,
165
603
  ItemIndicator: DropdownMenuItemIndicator,
166
604
  Separator: DropdownMenuSeparator,
167
- GroupLabel: DropdownMenuGroupLabel,
605
+ Arrow: DropdownMenuArrow,
606
+ Sub: DropdownMenuSub,
607
+ SubTrigger: DropdownMenuSubTrigger,
608
+ SubContent: DropdownMenuSubContent,
168
609
  };
169
610
 
170
- export { useDropdownMenuContext, useDropdownMenuMenuScope };
611
+ const useDropdownMenuMenuScope = useMenuScope;
612
+
613
+ export { createDropdownMenuScope, useDropdownMenuContext, useDropdownMenuMenuScope };
171
614
 
172
615
  export type {
173
616
  DropdownMenuRootProps,
174
617
  DropdownMenuTriggerProps,
618
+ DropdownMenuVirtualTriggerProps,
175
619
  DropdownMenuPortalProps,
176
620
  DropdownMenuContentProps,
177
621
  DropdownMenuViewportProps,
178
- DropdownMenuArrowProps,
179
622
  DropdownMenuGroupProps,
623
+ DropdownMenuLabelProps,
180
624
  DropdownMenuItemProps,
181
625
  DropdownMenuCheckboxItemProps,
626
+ DropdownMenuRadioGroupProps,
627
+ DropdownMenuRadioItemProps,
182
628
  DropdownMenuItemIndicatorProps,
183
629
  DropdownMenuSeparatorProps,
184
- DropdownMenuGroupLabelProps,
630
+ DropdownMenuArrowProps,
631
+ DropdownMenuSubProps,
632
+ DropdownMenuSubTriggerProps,
633
+ DropdownMenuSubContentProps,
185
634
  };