Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fix-dropdown-in-dialog-dismiss-3971.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@radix-ui/react-menu': patch
'@radix-ui/react-dropdown-menu': patch
'@radix-ui/react-context-menu': patch
'@radix-ui/react-menubar': patch
---

Defer outside pointer interactions on menu content so nested dialogs are not dismissed when closing a menu.
22 changes: 22 additions & 0 deletions apps/storybook/stories/dropdown-menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,28 @@ export const NestedComposition = () => {
);
};

export const DropdownWithinDialog = () => (
<Dialog.Root defaultOpen>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content className={styles.dialog} style={{ padding: 24 }}>
<Dialog.Title>Dialog with dropdown</Dialog.Title>
<DropdownMenu.Root defaultOpen>
<DropdownMenu.Trigger className={styles.trigger}>Open menu</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className={styles.content} sideOffset={5}>
<DropdownMenu.Item className={styles.item}>Item 1</DropdownMenu.Item>
<DropdownMenu.Item className={styles.item}>Item 2</DropdownMenu.Item>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<p data-testid="dialog-open-indicator">Dialog is still open</p>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);

export const SingleItemAsDialogTrigger = () => {
const dropdownTriggerRef = React.useRef<React.ComponentRef<typeof DropdownMenu.Trigger>>(null);
const dropdownTriggerRef2 = React.useRef<React.ComponentRef<typeof DropdownMenu.Trigger>>(null);
Expand Down
27 changes: 27 additions & 0 deletions packages/react/dismissable-layer/src/dismissable-layer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,31 @@ describe('DismissableLayer', () => {

expect(onDismiss).not.toHaveBeenCalled();
});

// Regression test for https://github.com/radix-ui/primitives/issues/3971
it('does not dismiss a deferred parent when a nested deferred child dismisses first', async () => {
const onParentDismiss = vi.fn();
const onChildDismiss = vi.fn();

render(
<>
<DismissableLayer.Root deferPointerDownOutside onDismiss={onParentDismiss}>
<div data-testid="parent-inside">dialog</div>
<DismissableLayer.Root deferPointerDownOutside onDismiss={onChildDismiss}>
<div data-testid="child-inside">menu</div>
</DismissableLayer.Root>
</DismissableLayer.Root>
</>,
);
await waitForDocumentPointerDownListener();

firePointerMouseClick(screen.getByTestId('parent-inside'));

await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 0));
});

expect(onChildDismiss).toHaveBeenCalledTimes(1);
expect(onParentDismiss).not.toHaveBeenCalled();
});
});
1 change: 1 addition & 0 deletions packages/react/menu/src/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
<DismissableLayer
asChild
disableOutsidePointerEvents={disableOutsidePointerEvents}
deferPointerDownOutside
onEscapeKeyDown={onEscapeKeyDown}
onPointerDownOutside={onPointerDownOutside}
onFocusOutside={onFocusOutside}
Expand Down