From bf3fb17b582bd3ef853816f84a9d623344e5ddd8 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:20:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BF=20fix:=20Keep=20Portaled=20Dropdown?= =?UTF-8?q?=20Menus=20Clickable=20Inside=20Modal=20Dialogs=20(#15026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: keep portaled dropdown menus clickable inside modal dialogs Modal OGDialog layers set pointer-events: none on body and re-enable it only on their own content. A portaled DropdownPopup menu is a body-level sibling of those layers, so it inherited pointer-events: none: hover never reached the items and every click passed through to nothing, which Ariakit treated as an outside interaction and closed the menu. This made the Agent File Search and File Context upload menus dead when SharePoint was enabled, since that flag is what switches them from a plain in-dialog button to the portaled menu. Restore pointer-events: auto on the menu element so it stays clickable regardless of the surrounding modal layers. Fixes #14487 * fix: sort imports in DropdownPopup spec --- .../src/components/DropdownPopup.spec.tsx | 35 +++++++++++++++++++ .../client/src/components/DropdownPopup.tsx | 6 +++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/components/DropdownPopup.spec.tsx 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} >