@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
@@ -28,8 +28,9 @@ const StoryMessage = ({ valence, title, body }: StoryMessageProps) => (
28
28
  );
29
29
 
30
30
  export default {
31
- title: 'react-ui/Message',
32
- component: StoryMessage,
31
+ title: 'ui/react-ui-core/Message',
32
+ component: Message,
33
+ render: StoryMessage,
33
34
  decorators: [withTheme],
34
35
  parameters: { chromatic: { disableSnapshot: false } },
35
36
  };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import React, { type PropsWithChildren, type ReactNode } from 'react';
7
+ import React, { type PropsWithChildren, type ReactNode, useRef, useState } from 'react';
8
8
 
9
9
  import { faker } from '@dxos/random';
10
10
 
@@ -29,8 +29,9 @@ const StorybookPopover = ({ openTrigger, children }: PropsWithChildren<{ openTri
29
29
  };
30
30
 
31
31
  export default {
32
- title: 'react-ui/Popover',
33
- component: StorybookPopover,
32
+ title: 'ui/react-ui-core/Popover',
33
+ component: Popover,
34
+ render: StorybookPopover,
34
35
  decorators: [withTheme],
35
36
  parameters: { chromatic: { disableSnapshot: false } },
36
37
  };
@@ -41,3 +42,26 @@ export const Default = {
41
42
  children: faker.lorem.paragraphs(3),
42
43
  },
43
44
  };
45
+
46
+ export const VirtualTrigger = {
47
+ render: () => {
48
+ const [open, setOpen] = useState(true);
49
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
50
+ return (
51
+ <>
52
+ <Button onClick={() => setOpen(true)} ref={buttonRef}>
53
+ Open popover
54
+ </Button>
55
+ <Popover.Root open={open} onOpenChange={setOpen}>
56
+ <Popover.VirtualTrigger virtualRef={buttonRef} />
57
+ <Popover.Content>
58
+ <Popover.Viewport>
59
+ <p className='pli-2 plb-1 min-is-[18rem] max-is-[38rem]'>{faker.lorem.paragraphs(3)}</p>
60
+ </Popover.Viewport>
61
+ <Popover.Arrow />
62
+ </Popover.Content>
63
+ </Popover.Root>
64
+ </>
65
+ );
66
+ },
67
+ };
@@ -2,82 +2,543 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import {
6
- Root as PopoverRootPrimitive,
7
- type PopoverProps as PopoverRootPrimitiveProps,
8
- type PopoverContentProps as PopoverContentPrimitiveProps,
9
- PopoverContent as PopoverContentPrimitive,
10
- type PopoverTriggerProps as PopoverTriggerPrimitiveProps,
11
- PopoverTrigger as PopoverTriggerPrimitive,
12
- type PopoverAnchorProps as PopoverAnchorPrimitiveProps,
13
- PopoverAnchor as PopoverAnchorPrimitive,
14
- type PopoverPortalProps as PopoverPortalPrimitiveProps,
15
- PopoverPortal as PopoverPortalPrimitive,
16
- type PopoverArrowProps as PopoverArrowPrimitiveProps,
17
- PopoverArrow as PopoverArrowPrimitive,
18
- type PopoverCloseProps as PopoverClosePrimitiveProps,
19
- PopoverClose as PopoverClosePrimitive,
20
- } from '@radix-ui/react-popover';
5
+ // This is based upon `@radix-ui/react-popover` fetched 25 Oct 2024 at https://github.com/radix-ui/primitives at commit 374c7d7.
6
+
7
+ import { composeEventHandlers } from '@radix-ui/primitive';
8
+ import { useComposedRefs } 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 { DismissableLayer } from '@radix-ui/react-dismissable-layer';
12
+ import { useFocusGuards } from '@radix-ui/react-focus-guards';
13
+ import { FocusScope } from '@radix-ui/react-focus-scope';
14
+ import { useId } from '@radix-ui/react-id';
15
+ import * as PopperPrimitive from '@radix-ui/react-popper';
16
+ import { createPopperScope } from '@radix-ui/react-popper';
17
+ import { Portal as PortalPrimitive } from '@radix-ui/react-portal';
18
+ import { Presence } from '@radix-ui/react-presence';
21
19
  import { Primitive } from '@radix-ui/react-primitive';
22
20
  import { Slot } from '@radix-ui/react-slot';
23
- import React, { type ComponentPropsWithRef, forwardRef, type FunctionComponent } from 'react';
21
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
22
+ import { hideOthers } from 'aria-hidden';
23
+ import React, {
24
+ type ComponentPropsWithRef,
25
+ forwardRef,
26
+ type ElementRef,
27
+ type RefObject,
28
+ type ReactNode,
29
+ useRef,
30
+ useCallback,
31
+ type ComponentPropsWithoutRef,
32
+ type FC,
33
+ useState,
34
+ useEffect,
35
+ type MutableRefObject,
36
+ } from 'react';
37
+ import { RemoveScroll } from 'react-remove-scroll';
24
38
 
25
39
  import { useThemeContext } from '../../hooks';
26
40
  import { type ThemedClassName } from '../../util';
27
- import { ElevationProvider } from '../ElevationProvider';
28
41
 
29
- type PopoverRootProps = PopoverRootPrimitiveProps;
42
+ /* -------------------------------------------------------------------------------------------------
43
+ * Popover
44
+ * ----------------------------------------------------------------------------------------------- */
45
+
46
+ const POPOVER_NAME = 'Popover';
47
+
48
+ type ScopedProps<P> = P & { __scopePopover?: Scope };
49
+ const [createPopoverContext, createPopoverScope] = createContextScope(POPOVER_NAME, [createPopperScope]);
50
+ const usePopperScope = createPopperScope();
51
+
52
+ type PopoverContextValue = {
53
+ triggerRef: MutableRefObject<HTMLButtonElement>;
54
+ contentId: string;
55
+ open: boolean;
56
+ onOpenChange(open: boolean): void;
57
+ onOpenToggle(): void;
58
+ hasCustomAnchor: boolean;
59
+ onCustomAnchorAdd(): void;
60
+ onCustomAnchorRemove(): void;
61
+ modal: boolean;
62
+ };
63
+
64
+ const [PopoverProvider, usePopoverContext] = createPopoverContext<PopoverContextValue>(POPOVER_NAME);
65
+
66
+ interface PopoverRootProps {
67
+ children?: ReactNode;
68
+ open?: boolean;
69
+ defaultOpen?: boolean;
70
+ onOpenChange?: (open: boolean) => void;
71
+ modal?: boolean;
72
+ }
73
+
74
+ const PopoverRoot: FC<PopoverRootProps> = (props: ScopedProps<PopoverRootProps>) => {
75
+ const { __scopePopover, children, open: openProp, defaultOpen, onOpenChange, modal = false } = props;
76
+ const popperScope = usePopperScope(__scopePopover);
77
+ const triggerRef = useRef<HTMLButtonElement>(null);
78
+ const [hasCustomAnchor, setHasCustomAnchor] = useState(false);
79
+ const [open = false, setOpen] = useControllableState({
80
+ prop: openProp,
81
+ defaultProp: defaultOpen,
82
+ onChange: onOpenChange,
83
+ });
84
+
85
+ return (
86
+ <PopperPrimitive.Root {...popperScope}>
87
+ <PopoverProvider
88
+ scope={__scopePopover}
89
+ contentId={useId()}
90
+ triggerRef={triggerRef as MutableRefObject<HTMLButtonElement>}
91
+ open={open}
92
+ onOpenChange={setOpen}
93
+ onOpenToggle={useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen])}
94
+ hasCustomAnchor={hasCustomAnchor}
95
+ onCustomAnchorAdd={useCallback(() => setHasCustomAnchor(true), [])}
96
+ onCustomAnchorRemove={useCallback(() => setHasCustomAnchor(false), [])}
97
+ modal={modal}
98
+ >
99
+ {children}
100
+ </PopoverProvider>
101
+ </PopperPrimitive.Root>
102
+ );
103
+ };
104
+
105
+ PopoverRoot.displayName = POPOVER_NAME;
30
106
 
31
- const PopoverRoot: FunctionComponent<PopoverRootProps> = PopoverRootPrimitive;
107
+ /* -------------------------------------------------------------------------------------------------
108
+ * PopoverAnchor
109
+ * ----------------------------------------------------------------------------------------------- */
32
110
 
33
- type PopoverPortalProps = PopoverPortalPrimitiveProps;
111
+ const ANCHOR_NAME = 'PopoverAnchor';
34
112
 
35
- const PopoverPortal = PopoverPortalPrimitive;
113
+ type PopoverAnchorElement = ElementRef<typeof PopperPrimitive.Anchor>;
114
+ type PopperAnchorProps = ComponentPropsWithoutRef<typeof PopperPrimitive.Anchor>;
115
+ interface PopoverAnchorProps extends PopperAnchorProps {}
36
116
 
37
- type PopoverTriggerProps = PopoverTriggerPrimitiveProps;
117
+ const PopoverAnchor = forwardRef<PopoverAnchorElement, PopoverAnchorProps>(
118
+ (props: ScopedProps<PopoverAnchorProps>, forwardedRef) => {
119
+ const { __scopePopover, ...anchorProps } = props;
120
+ const context = usePopoverContext(ANCHOR_NAME, __scopePopover);
121
+ const popperScope = usePopperScope(__scopePopover);
122
+ const { onCustomAnchorAdd, onCustomAnchorRemove } = context;
38
123
 
39
- const PopoverTrigger = PopoverTriggerPrimitive;
124
+ useEffect(() => {
125
+ onCustomAnchorAdd();
126
+ return () => onCustomAnchorRemove();
127
+ }, [onCustomAnchorAdd, onCustomAnchorRemove]);
40
128
 
41
- type PopoverAnchorProps = PopoverAnchorPrimitiveProps;
129
+ return <PopperPrimitive.Anchor {...popperScope} {...anchorProps} ref={forwardedRef} />;
130
+ },
131
+ );
132
+
133
+ PopoverAnchor.displayName = ANCHOR_NAME;
134
+
135
+ /* -------------------------------------------------------------------------------------------------
136
+ * PopoverTrigger
137
+ * ----------------------------------------------------------------------------------------------- */
42
138
 
43
- const PopoverAnchor = PopoverAnchorPrimitive;
139
+ const TRIGGER_NAME = 'PopoverTrigger';
44
140
 
45
- type PopoverCloseProps = PopoverClosePrimitiveProps;
141
+ type PopoverTriggerElement = ElementRef<typeof Primitive.button>;
142
+ type PrimitiveButtonProps = ComponentPropsWithoutRef<typeof Primitive.button>;
143
+ interface PopoverTriggerProps extends PrimitiveButtonProps {}
46
144
 
47
- const PopoverClose = PopoverClosePrimitive;
145
+ const PopoverTrigger = forwardRef<PopoverTriggerElement, PopoverTriggerProps>(
146
+ (props: ScopedProps<PopoverTriggerProps>, forwardedRef) => {
147
+ const { __scopePopover, ...triggerProps } = props;
148
+ const context = usePopoverContext(TRIGGER_NAME, __scopePopover);
149
+ const popperScope = usePopperScope(__scopePopover);
150
+ const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef);
48
151
 
49
- type PopoverArrowProps = ThemedClassName<PopoverArrowPrimitiveProps>;
152
+ const trigger = (
153
+ <Primitive.button
154
+ type='button'
155
+ aria-haspopup='dialog'
156
+ aria-expanded={context.open}
157
+ aria-controls={context.contentId}
158
+ data-state={getState(context.open)}
159
+ {...triggerProps}
160
+ ref={composedTriggerRef}
161
+ onClick={composeEventHandlers(props.onClick, context.onOpenToggle)}
162
+ />
163
+ );
164
+
165
+ return context.hasCustomAnchor ? (
166
+ trigger
167
+ ) : (
168
+ <PopperPrimitive.Anchor asChild {...popperScope}>
169
+ {trigger}
170
+ </PopperPrimitive.Anchor>
171
+ );
172
+ },
173
+ );
174
+
175
+ PopoverTrigger.displayName = TRIGGER_NAME;
176
+
177
+ /* -------------------------------------------------------------------------------------------------
178
+ * PopoverVirtualTrigger
179
+ * ----------------------------------------------------------------------------------------------- */
180
+
181
+ const VIRTUAL_TRIGGER_NAME = 'PopoverVirtualTrigger';
182
+
183
+ interface PopoverVirtualTriggerProps {
184
+ virtualRef: RefObject<PopoverTriggerElement>;
185
+ }
186
+
187
+ const PopoverVirtualTrigger = (props: ScopedProps<PopoverVirtualTriggerProps>) => {
188
+ const { __scopePopover, virtualRef } = props;
189
+ const context = usePopoverContext(VIRTUAL_TRIGGER_NAME, __scopePopover);
190
+ const popperScope = usePopperScope(__scopePopover);
191
+ useEffect(() => {
192
+ if (virtualRef.current) {
193
+ context.triggerRef.current = virtualRef.current;
194
+ }
195
+ });
196
+ return <PopperPrimitive.Anchor {...popperScope} virtualRef={virtualRef} />;
197
+ };
50
198
 
51
- const PopoverArrow = forwardRef<SVGSVGElement, PopoverArrowProps>(({ classNames, ...props }, forwardedRef) => {
52
- const { tx } = useThemeContext();
199
+ PopoverVirtualTrigger.displayName = VIRTUAL_TRIGGER_NAME;
200
+
201
+ /* -------------------------------------------------------------------------------------------------
202
+ * PopoverPortal
203
+ * ----------------------------------------------------------------------------------------------- */
204
+
205
+ const PORTAL_NAME = 'PopoverPortal';
206
+
207
+ type PortalContextValue = { forceMount?: true };
208
+ const [PortalProvider, usePortalContext] = createPopoverContext<PortalContextValue>(PORTAL_NAME, {
209
+ forceMount: undefined,
210
+ });
211
+
212
+ type PortalProps = ComponentPropsWithoutRef<typeof PortalPrimitive>;
213
+ interface PopoverPortalProps {
214
+ children?: ReactNode;
215
+ /**
216
+ * Specify a container element to portal the content into.
217
+ */
218
+ container?: PortalProps['container'];
219
+ /**
220
+ * Used to force mounting when more control is needed. Useful when
221
+ * controlling animation with React animation libraries.
222
+ */
223
+ forceMount?: true;
224
+ }
225
+
226
+ const PopoverPortal: FC<PopoverPortalProps> = (props: ScopedProps<PopoverPortalProps>) => {
227
+ const { __scopePopover, forceMount, children, container } = props;
228
+ const context = usePopoverContext(PORTAL_NAME, __scopePopover);
53
229
  return (
54
- <PopoverArrowPrimitive
55
- {...props}
56
- className={tx('popover.arrow', 'popover__arrow', {}, classNames)}
57
- ref={forwardedRef}
58
- />
230
+ <PortalProvider scope={__scopePopover} forceMount={forceMount}>
231
+ <Presence present={forceMount || context.open}>
232
+ <PortalPrimitive asChild container={container}>
233
+ {children}
234
+ </PortalPrimitive>
235
+ </Presence>
236
+ </PortalProvider>
59
237
  );
60
- });
238
+ };
61
239
 
62
- type PopoverContentProps = ThemedClassName<PopoverContentPrimitiveProps>;
240
+ PopoverPortal.displayName = PORTAL_NAME;
241
+
242
+ /* -------------------------------------------------------------------------------------------------
243
+ * PopoverContent
244
+ * ----------------------------------------------------------------------------------------------- */
245
+
246
+ const CONTENT_NAME = 'PopoverContent';
247
+
248
+ interface PopoverContentProps extends PopoverContentTypeProps {
249
+ /**
250
+ * Used to force mounting when more control is needed. Useful when
251
+ * controlling animation with React animation libraries.
252
+ */
253
+ forceMount?: true;
254
+ }
255
+
256
+ const PopoverContent = forwardRef<PopoverContentTypeElement, PopoverContentProps>(
257
+ (props: ScopedProps<PopoverContentProps>, forwardedRef) => {
258
+ const portalContext = usePortalContext(CONTENT_NAME, props.__scopePopover);
259
+ const { forceMount = portalContext.forceMount, ...contentProps } = props;
260
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
261
+ return (
262
+ <Presence present={forceMount || context.open}>
263
+ {context.modal ? (
264
+ <PopoverContentModal {...contentProps} ref={forwardedRef} />
265
+ ) : (
266
+ <PopoverContentNonModal {...contentProps} ref={forwardedRef} />
267
+ )}
268
+ </Presence>
269
+ );
270
+ },
271
+ );
272
+
273
+ PopoverContent.displayName = CONTENT_NAME;
274
+
275
+ /* ----------------------------------------------------------------------------------------------- */
276
+
277
+ type PopoverContentTypeElement = PopoverContentImplElement;
278
+ interface PopoverContentTypeProps extends Omit<PopoverContentImplProps, 'trapFocus' | 'disableOutsidePointerEvents'> {}
279
+
280
+ const PopoverContentModal = forwardRef<PopoverContentTypeElement, PopoverContentTypeProps>(
281
+ (props: ScopedProps<PopoverContentTypeProps>, forwardedRef) => {
282
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
283
+ const contentRef = useRef<HTMLDivElement>(null);
284
+ const composedRefs = useComposedRefs(forwardedRef, contentRef);
285
+ const isRightClickOutsideRef = useRef(false);
286
+
287
+ // aria-hide everything except the content (better supported equivalent to setting aria-modal)
288
+ useEffect(() => {
289
+ const content = contentRef.current;
290
+ if (content) {
291
+ return hideOthers(content);
292
+ }
293
+ }, []);
294
+
295
+ return (
296
+ <RemoveScroll as={Slot} allowPinchZoom>
297
+ <PopoverContentImpl
298
+ {...props}
299
+ ref={composedRefs}
300
+ // we make sure we're not trapping once it's been closed
301
+ // (closed !== unmounted when animating out)
302
+ trapFocus={context.open}
303
+ disableOutsidePointerEvents
304
+ onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
305
+ event.preventDefault();
306
+ if (!isRightClickOutsideRef.current) {
307
+ context.triggerRef.current?.focus();
308
+ }
309
+ })}
310
+ onPointerDownOutside={composeEventHandlers(
311
+ props.onPointerDownOutside,
312
+ (event) => {
313
+ const originalEvent = event.detail.originalEvent;
314
+ const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true;
315
+ const isRightClick = originalEvent.button === 2 || ctrlLeftClick;
316
+
317
+ isRightClickOutsideRef.current = isRightClick;
318
+ },
319
+ { checkForDefaultPrevented: false },
320
+ )}
321
+ // When focus is trapped, a `focusout` event may still happen.
322
+ // We make sure we don't trigger our `onDismiss` in such case.
323
+ onFocusOutside={composeEventHandlers(props.onFocusOutside, (event) => event.preventDefault(), {
324
+ checkForDefaultPrevented: false,
325
+ })}
326
+ />
327
+ </RemoveScroll>
328
+ );
329
+ },
330
+ );
331
+
332
+ const PopoverContentNonModal = forwardRef<PopoverContentTypeElement, PopoverContentTypeProps>(
333
+ (props: ScopedProps<PopoverContentTypeProps>, forwardedRef) => {
334
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
335
+ const hasInteractedOutsideRef = useRef(false);
336
+ const hasPointerDownOutsideRef = useRef(false);
63
337
 
64
- const PopoverContent = forwardRef<HTMLDivElement, PopoverContentProps>(
65
- ({ classNames, children, ...props }, forwardedRef) => {
66
- const { tx } = useThemeContext();
67
338
  return (
68
- <PopoverContentPrimitive
69
- sideOffset={4}
70
- collisionPadding={8}
339
+ <PopoverContentImpl
71
340
  {...props}
72
- className={tx('popover.content', 'popover', {}, classNames)}
73
341
  ref={forwardedRef}
342
+ trapFocus={false}
343
+ disableOutsidePointerEvents={false}
344
+ onCloseAutoFocus={(event) => {
345
+ props.onCloseAutoFocus?.(event);
346
+
347
+ if (!event.defaultPrevented) {
348
+ if (!hasInteractedOutsideRef.current) {
349
+ context.triggerRef.current?.focus();
350
+ }
351
+ // Always prevent auto focus because we either focus manually or want user agent focus
352
+ event.preventDefault();
353
+ }
354
+
355
+ hasInteractedOutsideRef.current = false;
356
+ hasPointerDownOutsideRef.current = false;
357
+ }}
358
+ onInteractOutside={(event) => {
359
+ props.onInteractOutside?.(event);
360
+
361
+ if (!event.defaultPrevented) {
362
+ hasInteractedOutsideRef.current = true;
363
+ if (event.detail.originalEvent.type === 'pointerdown') {
364
+ hasPointerDownOutsideRef.current = true;
365
+ }
366
+ }
367
+
368
+ // Prevent dismissing when clicking the trigger.
369
+ // As the trigger is already setup to close, without doing so would
370
+ // cause it to close and immediately open.
371
+ const target = event.target as HTMLElement;
372
+ const targetIsTrigger = context.triggerRef.current?.contains(target);
373
+ if (targetIsTrigger) {
374
+ event.preventDefault();
375
+ }
376
+
377
+ // On Safari if the trigger is inside a container with tabIndex={0}, when clicked
378
+ // we will get the pointer down outside event on the trigger, but then a subsequent
379
+ // focus outside event on the container, we ignore any focus outside event when we've
380
+ // already had a pointer down outside event.
381
+ if (event.detail.originalEvent.type === 'focusin' && hasPointerDownOutsideRef.current) {
382
+ event.preventDefault();
383
+ }
384
+ }}
385
+ />
386
+ );
387
+ },
388
+ );
389
+
390
+ /* ----------------------------------------------------------------------------------------------- */
391
+
392
+ type PopoverContentImplElement = ElementRef<typeof PopperPrimitive.Content>;
393
+ type FocusScopeProps = ComponentPropsWithoutRef<typeof FocusScope>;
394
+ type DismissableLayerProps = ComponentPropsWithoutRef<typeof DismissableLayer>;
395
+ type PopperContentProps = ThemedClassName<ComponentPropsWithoutRef<typeof PopperPrimitive.Content>>;
396
+ interface PopoverContentImplProps
397
+ extends Omit<PopperContentProps, 'onPlaced'>,
398
+ Omit<DismissableLayerProps, 'onDismiss'> {
399
+ /**
400
+ * Whether focus should be trapped within the `Popover`
401
+ * (default: false)
402
+ */
403
+ trapFocus?: FocusScopeProps['trapped'];
404
+
405
+ /**
406
+ * Event handler called when auto-focusing on open.
407
+ * Can be prevented.
408
+ */
409
+ onOpenAutoFocus?: FocusScopeProps['onMountAutoFocus'];
410
+
411
+ /**
412
+ * Event handler called when auto-focusing on close.
413
+ * Can be prevented.
414
+ */
415
+ onCloseAutoFocus?: FocusScopeProps['onUnmountAutoFocus'];
416
+ }
417
+
418
+ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentImplProps>(
419
+ (props: ScopedProps<PopoverContentImplProps>, forwardedRef) => {
420
+ const {
421
+ __scopePopover,
422
+ trapFocus,
423
+ onOpenAutoFocus,
424
+ onCloseAutoFocus,
425
+ disableOutsidePointerEvents,
426
+ onEscapeKeyDown,
427
+ onPointerDownOutside,
428
+ onFocusOutside,
429
+ onInteractOutside,
430
+ classNames,
431
+ ...contentProps
432
+ } = props;
433
+ const context = usePopoverContext(CONTENT_NAME, __scopePopover);
434
+ const popperScope = usePopperScope(__scopePopover);
435
+ const { tx } = useThemeContext();
436
+
437
+ // Make sure the whole tree has focus guards as our `Popover` may be
438
+ // the last element in the DOM (because of the `Portal`)
439
+ useFocusGuards();
440
+
441
+ return (
442
+ <FocusScope
443
+ asChild
444
+ loop
445
+ trapped={trapFocus}
446
+ onMountAutoFocus={onOpenAutoFocus}
447
+ onUnmountAutoFocus={onCloseAutoFocus}
74
448
  >
75
- <ElevationProvider elevation='chrome'>{children}</ElevationProvider>
76
- </PopoverContentPrimitive>
449
+ <DismissableLayer
450
+ asChild
451
+ disableOutsidePointerEvents={disableOutsidePointerEvents}
452
+ onInteractOutside={onInteractOutside}
453
+ onEscapeKeyDown={onEscapeKeyDown}
454
+ onPointerDownOutside={onPointerDownOutside}
455
+ onFocusOutside={onFocusOutside}
456
+ onDismiss={() => context.onOpenChange(false)}
457
+ >
458
+ <PopperPrimitive.Content
459
+ data-state={getState(context.open)}
460
+ role='dialog'
461
+ id={context.contentId}
462
+ {...popperScope}
463
+ {...contentProps}
464
+ className={tx('popover.content', 'popover', {}, classNames)}
465
+ ref={forwardedRef}
466
+ style={{
467
+ ...contentProps.style,
468
+ // re-namespace exposed content custom properties
469
+ ...{
470
+ '--radix-popover-content-transform-origin': 'var(--radix-popper-transform-origin)',
471
+ '--radix-popover-content-available-width': 'var(--radix-popper-available-width)',
472
+ '--radix-popover-content-available-height': 'var(--radix-popper-available-height)',
473
+ '--radix-popover-trigger-width': 'var(--radix-popper-anchor-width)',
474
+ '--radix-popover-trigger-height': 'var(--radix-popper-anchor-height)',
475
+ },
476
+ }}
477
+ />
478
+ </DismissableLayer>
479
+ </FocusScope>
480
+ );
481
+ },
482
+ );
483
+
484
+ /* -------------------------------------------------------------------------------------------------
485
+ * PopoverClose
486
+ * ----------------------------------------------------------------------------------------------- */
487
+
488
+ const CLOSE_NAME = 'PopoverClose';
489
+
490
+ type PopoverCloseElement = ElementRef<typeof Primitive.button>;
491
+ interface PopoverCloseProps extends PrimitiveButtonProps {}
492
+
493
+ const PopoverClose = forwardRef<PopoverCloseElement, PopoverCloseProps>(
494
+ (props: ScopedProps<PopoverCloseProps>, forwardedRef) => {
495
+ const { __scopePopover, ...closeProps } = props;
496
+ const context = usePopoverContext(CLOSE_NAME, __scopePopover);
497
+ return (
498
+ <Primitive.button
499
+ type='button'
500
+ {...closeProps}
501
+ ref={forwardedRef}
502
+ onClick={composeEventHandlers(props.onClick, () => context.onOpenChange(false))}
503
+ />
504
+ );
505
+ },
506
+ );
507
+
508
+ PopoverClose.displayName = CLOSE_NAME;
509
+
510
+ /* -------------------------------------------------------------------------------------------------
511
+ * PopoverArrow
512
+ * ----------------------------------------------------------------------------------------------- */
513
+
514
+ const ARROW_NAME = 'PopoverArrow';
515
+
516
+ type PopoverArrowElement = ElementRef<typeof PopperPrimitive.Arrow>;
517
+ type PopperArrowProps = ThemedClassName<ComponentPropsWithoutRef<typeof PopperPrimitive.Arrow>>;
518
+ interface PopoverArrowProps extends PopperArrowProps {}
519
+
520
+ const PopoverArrow = forwardRef<PopoverArrowElement, PopoverArrowProps>(
521
+ (props: ScopedProps<PopoverArrowProps>, forwardedRef) => {
522
+ const { __scopePopover, classNames, ...arrowProps } = props;
523
+ const popperScope = usePopperScope(__scopePopover);
524
+ const { tx } = useThemeContext();
525
+ return (
526
+ <PopperPrimitive.Arrow
527
+ {...popperScope}
528
+ {...arrowProps}
529
+ className={tx('popover.arrow', 'popover__arrow', {}, classNames)}
530
+ ref={forwardedRef}
531
+ />
77
532
  );
78
533
  },
79
534
  );
80
535
 
536
+ PopoverArrow.displayName = ARROW_NAME;
537
+
538
+ /* -------------------------------------------------------------------------------------------------
539
+ * PopoverViewport
540
+ * ----------------------------------------------------------------------------------------------- */
541
+
81
542
  type PopoverViewportProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>> & {
82
543
  asChild?: boolean;
83
544
  constrainInline?: boolean;
@@ -100,24 +561,32 @@ const PopoverViewport = forwardRef<HTMLDivElement, PopoverViewportProps>(
100
561
  },
101
562
  );
102
563
 
564
+ /* ----------------------------------------------------------------------------------------------- */
565
+
566
+ const getState = (open: boolean) => (open ? 'open' : 'closed');
567
+
103
568
  export const Popover = {
104
569
  Root: PopoverRoot,
105
- Portal: PopoverPortal,
106
- Trigger: PopoverTrigger,
107
570
  Anchor: PopoverAnchor,
108
- Arrow: PopoverArrow,
109
- Close: PopoverClose,
571
+ Trigger: PopoverTrigger,
572
+ VirtualTrigger: PopoverVirtualTrigger,
573
+ Portal: PopoverPortal,
110
574
  Content: PopoverContent,
575
+ Close: PopoverClose,
576
+ Arrow: PopoverArrow,
111
577
  Viewport: PopoverViewport,
112
578
  };
113
579
 
580
+ export { createPopoverScope };
581
+
114
582
  export type {
115
583
  PopoverRootProps,
116
- PopoverPortalProps,
117
- PopoverTriggerProps,
118
584
  PopoverAnchorProps,
119
- PopoverArrowProps,
120
- PopoverCloseProps,
585
+ PopoverTriggerProps,
586
+ PopoverVirtualTriggerProps,
587
+ PopoverPortalProps,
121
588
  PopoverContentProps,
589
+ PopoverCloseProps,
590
+ PopoverArrowProps,
122
591
  PopoverViewportProps,
123
592
  };