diff --git a/packages/client/src/components/DropdownPopup.spec.tsx b/packages/client/src/components/DropdownPopup.spec.tsx new file mode 100644 index 0000000000..19ccacd546 --- /dev/null +++ b/packages/client/src/components/DropdownPopup.spec.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import * as Ariakit from '@ariakit/react'; +import { render } from '@testing-library/react'; +import DropdownPopup from './DropdownPopup'; + +describe('DropdownPopup', () => { + it('restores pointer events on portaled menus so they stay clickable inside modal dialogs', () => { + // A modal Radix dialog (OGDialog) sets `pointer-events: none` on body and only + // re-enables it on its own content. A portaled menu is a body-level sibling and + // would inherit `none`, making every item hit-transparent (#14487). + document.body.style.pointerEvents = 'none'; + + render( + + trigger + + } + items={[{ label: 'From Local Computer', onClick: jest.fn() }]} + />, + ); + + const menu = document.getElementById('portal-click-test-menu'); + expect(menu).not.toBeNull(); + expect(menu?.style.pointerEvents).toBe('auto'); + + document.body.style.pointerEvents = ''; + }); +}); diff --git a/packages/client/src/components/DropdownPopup.tsx b/packages/client/src/components/DropdownPopup.tsx index a1319fcfa5..1af4d552c0 100644 --- a/packages/client/src/components/DropdownPopup.tsx +++ b/packages/client/src/components/DropdownPopup.tsx @@ -89,7 +89,11 @@ const Menu: React.FC = ({ finalFocus={finalFocus} unmountOnHide={unmountOnHide} preserveTabOrder={preserveTabOrder} - style={{ zIndex, ...style }} + /* Portaled menus land beside modal OGDialog layers, which set + `pointer-events: none` on body and re-enable it only on their own + content. Without this the menu inherits `none` and its items become + hit-transparent (danny-avila/LibreChat#14487). */ + style={{ zIndex, pointerEvents: 'auto', ...style }} className={cn('popover-ui', className)} {...props} >