From b0ad5796b66e9f86fdb13b009656a98c380cc5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 29 Jul 2026 18:04:23 +0200 Subject: [PATCH] Portal drawer selects onto the viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The swipe Popup keeps a CSS transform that re-roots position:fixed, so Base UI's modal inert cutout missed the locale trigger and blocked hover and clicks. Signed-off-by: Émile Ré --- .../src/components/TopBar/LocaleSelect.tsx | 3 ++- .../src/components/TopBar/TopBarMobileNav.tsx | 11 ++++++----- packages/ui/src/v2/Drawer/DrawerPopup.tsx | 14 ++++++-------- packages/ui/src/v2/Select/SelectPopup.tsx | 6 ++++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx b/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx index baf07eca3..419c74cef 100644 --- a/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx +++ b/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx @@ -40,7 +40,8 @@ interface LocaleSelectProps { // Called after a locale change is requested (e.g. close the mobile drawer). onLocaleChange?: () => void; // Portal target for the menu. Required inside a Drawer/Dialog so the popup - // is not painted under the modal layer (Select defaults to body + z-3). + // stacks in the modal layer (Select defaults to body + z-3). Prefer a + // non-transformed container (e.g. Drawer Viewport, not the swipe Popup). portalContainer?: RefObject; } diff --git a/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx b/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx index 0fa32ab1b..44ea3f653 100644 --- a/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx +++ b/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx @@ -80,9 +80,10 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) { const [signOut, isSigningOut] = useSignOut(); const { displayMode, toggleDisplayMode } = useDisplayMode(); const [open, setOpen] = useState(false); - // Select menus portal to body at z-3 by default; mount them on the drawer - // popup so they stack inside the modal layer (z-5) instead of under it. - const drawerPopupRef = useRef(null); + // Select menus portal to body at z-3 by default (under the drawer at z-5). + // Mount them on the drawer Viewport — not the swipe Popup, which keeps a CSS + // transform and would break Base UI's modal inert cutout over the trigger. + const drawerViewportRef = useRef(null); const identity = useFragment(topBarMobileNavFragment, identityKey); const localizedPath = useLocalizedPath(); @@ -110,7 +111,7 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) { )} /> - + {t("topBar.menuTitle")} {identity == null diff --git a/packages/ui/src/v2/Drawer/DrawerPopup.tsx b/packages/ui/src/v2/Drawer/DrawerPopup.tsx index b1f69f6ff..89d298785 100644 --- a/packages/ui/src/v2/Drawer/DrawerPopup.tsx +++ b/packages/ui/src/v2/Drawer/DrawerPopup.tsx @@ -33,8 +33,10 @@ export type DrawerPopupProps // Portal + dimmed backdrop + edge-aligned popup. Children compose the header / // body / footer regions inside Base UI's Content (swipe-safe text selection). -// `ref` lands on the Popup panel (not the viewport) so nested menus can portal -// into the drawer stacking context. +// `ref` lands on the Viewport (not the swipe Popup): nested Select/Menu portals +// must mount on a non-transformed ancestor. The Popup keeps a CSS transform for +// swipe/enter animation, which would re-root `position: fixed` and misalign +// Base UI's modal inert cutout so the trigger looks unclickable. export function DrawerPopup(props: DrawerPopupProps) { const { className, children, side, ref, ...popupProps } = props; const slots = drawer({ side }); @@ -42,12 +44,8 @@ export function DrawerPopup(props: DrawerPopupProps) { return ( - - + + {children} diff --git a/packages/ui/src/v2/Select/SelectPopup.tsx b/packages/ui/src/v2/Select/SelectPopup.tsx index 356a6a2d0..4223ae103 100644 --- a/packages/ui/src/v2/Select/SelectPopup.tsx +++ b/packages/ui/src/v2/Select/SelectPopup.tsx @@ -27,8 +27,10 @@ export type SelectPopupProps = & Omit, "className"> & { className?: string; - // Mount inside a modal/drawer so the menu stacks above that layer - // (body portal uses z-3, below drawers at z-5). + // Mount inside a modal/drawer stacking context (body portal uses z-3, + // below drawers at z-5). Use a non-transformed ancestor — a CSS + // transform on the container re-roots `position: fixed` and breaks + // Base UI's modal inert cutout over the trigger. container?: ComponentProps["container"]; // Positioner placement passthrough. side?: ComponentProps["side"];