From a9a476a34332ecee306799d64f471245e9a7b289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 22 Jul 2026 12:32:01 +0200 Subject: [PATCH] Fix mobile locale select under drawer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Select menus portal at z-3 while drawers sit at z-5, so the popup opened behind the drawer. Portal into the drawer panel instead. Point .env.example VITE_API_URL at the slug subdomain with a local Vite note. Signed-off-by: Émile Ré --- apps/compliance-portal/.env.example | 8 ++++---- .../src/components/TopBar/LocaleSelect.tsx | 7 ++++++- .../src/components/TopBar/TopBarMobileNav.tsx | 8 ++++++-- packages/ui/src/v2/Drawer/DrawerPopup.tsx | 10 ++++++++-- packages/ui/src/v2/Select/SelectPopup.tsx | 7 +++++-- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/apps/compliance-portal/.env.example b/apps/compliance-portal/.env.example index 8f0decaf1..ffe788ada 100644 --- a/apps/compliance-portal/.env.example +++ b/apps/compliance-portal/.env.example @@ -1,7 +1,7 @@ -# GraphQL API origin. Default matches other apps (probod on :8080). For local -# Vite development, set this to http://localhost:5174 so the browser hits the -# Vite origin and /graphql is proxied (see vite.config.ts). -VITE_API_URL=http://localhost:8080 +# GraphQL API origin (compliance-portal HTTPS origin; is the portal +# slug). For local Vite development, set this to http://localhost:5174 so the +# browser hits the Vite origin and /graphql is proxied (see vite.config.ts). +VITE_API_URL=https://.probopage.localhost # Vite /graphql proxy target (compliance-portal HTTPS origin; is the # portal slug). Required for local Vite development — uncomment and set the diff --git a/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx b/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx index 12a58dca1..440571fa1 100644 --- a/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx +++ b/apps/compliance-portal/src/components/TopBar/LocaleSelect.tsx @@ -23,6 +23,7 @@ import { Select } from "@probo/ui/src/v2/Select/Select"; import { SelectItem } from "@probo/ui/src/v2/Select/SelectItem"; import { SelectPopup } from "@probo/ui/src/v2/Select/SelectPopup"; import { SelectTrigger } from "@probo/ui/src/v2/Select/SelectTrigger"; +import type { RefObject } from "react"; import { useTranslation } from "react-i18next"; import { @@ -38,6 +39,9 @@ interface LocaleSelectProps { persist?: boolean; // 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). + portalContainer?: RefObject; } // Compact locale control for the top bar (guest and mobile). Uses the v2 Select @@ -45,6 +49,7 @@ interface LocaleSelectProps { export function LocaleSelect({ persist = false, onLocaleChange, + portalContainer, }: LocaleSelectProps) { const { t } = useTranslation(); const locale = useLocale(); @@ -75,7 +80,7 @@ export function LocaleSelect({ )} - + {URL_LOCALES.map(code => ( {URL_LOCALE_LABELS[code]} diff --git a/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx b/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx index d03106e0c..63ed46a48 100644 --- a/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx +++ b/apps/compliance-portal/src/components/TopBar/TopBarMobileNav.tsx @@ -38,7 +38,7 @@ import { DrawerTitle } from "@probo/ui/src/v2/Drawer/DrawerTitle"; import { DrawerTrigger } from "@probo/ui/src/v2/Drawer/DrawerTrigger"; import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton"; import { Text } from "@probo/ui/src/v2/typography/Text"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { graphql, useFragment } from "react-relay"; import { useLocation } from "react-router"; @@ -76,6 +76,9 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) { const { openSubscribe, isSubscribed, unsubscribe, isUnsubscribing } = useSubscribeDialog(); const [signOut, isSigningOut] = useSignOut(); 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); const identity = useFragment(topBarMobileNavFragment, identityKey); const localizedPath = useLocalizedPath(); @@ -103,7 +106,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 76858979e..b1f69f6ff 100644 --- a/packages/ui/src/v2/Drawer/DrawerPopup.tsx +++ b/packages/ui/src/v2/Drawer/DrawerPopup.tsx @@ -33,15 +33,21 @@ 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. export function DrawerPopup(props: DrawerPopupProps) { - const { className, children, side, ...popupProps } = props; + const { className, children, side, ref, ...popupProps } = props; const slots = drawer({ side }); return ( - + {children} diff --git a/packages/ui/src/v2/Select/SelectPopup.tsx b/packages/ui/src/v2/Select/SelectPopup.tsx index 7c8b2a1a1..356a6a2d0 100644 --- a/packages/ui/src/v2/Select/SelectPopup.tsx +++ b/packages/ui/src/v2/Select/SelectPopup.tsx @@ -27,6 +27,9 @@ 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). + container?: ComponentProps["container"]; // Positioner placement passthrough. side?: ComponentProps["side"]; align?: ComponentProps["align"]; @@ -36,13 +39,13 @@ export type SelectPopupProps // Portal + positioner + styled popup holding the select items. export function SelectPopup(props: SelectPopupProps) { const { - className, children, + className, children, container, side = "bottom", align = "start", sideOffset = 4, ...popupProps } = props; return ( - + {/* z-3 on the Positioner so the portaled root wins over in-page z-1. */}