Fix mobile locale select under drawer
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é <emile@probo.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# GraphQL API origin. Default matches other apps (probod on :8080). For local
|
# GraphQL API origin (compliance-portal HTTPS origin; <slug> is the portal
|
||||||
# Vite development, set this to http://localhost:5174 so the browser hits the
|
# slug). For local Vite development, set this to http://localhost:5174 so the
|
||||||
# Vite origin and /graphql is proxied (see vite.config.ts).
|
# browser hits the Vite origin and /graphql is proxied (see vite.config.ts).
|
||||||
VITE_API_URL=http://localhost:8080
|
VITE_API_URL=https://<slug>.probopage.localhost
|
||||||
|
|
||||||
# Vite /graphql proxy target (compliance-portal HTTPS origin; <slug> is the
|
# Vite /graphql proxy target (compliance-portal HTTPS origin; <slug> is the
|
||||||
# portal slug). Required for local Vite development — uncomment and set the
|
# portal slug). Required for local Vite development — uncomment and set the
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { Select } from "@probo/ui/src/v2/Select/Select";
|
|||||||
import { SelectItem } from "@probo/ui/src/v2/Select/SelectItem";
|
import { SelectItem } from "@probo/ui/src/v2/Select/SelectItem";
|
||||||
import { SelectPopup } from "@probo/ui/src/v2/Select/SelectPopup";
|
import { SelectPopup } from "@probo/ui/src/v2/Select/SelectPopup";
|
||||||
import { SelectTrigger } from "@probo/ui/src/v2/Select/SelectTrigger";
|
import { SelectTrigger } from "@probo/ui/src/v2/Select/SelectTrigger";
|
||||||
|
import type { RefObject } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -38,6 +39,9 @@ interface LocaleSelectProps {
|
|||||||
persist?: boolean;
|
persist?: boolean;
|
||||||
// Called after a locale change is requested (e.g. close the mobile drawer).
|
// Called after a locale change is requested (e.g. close the mobile drawer).
|
||||||
onLocaleChange?: () => void;
|
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<HTMLElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact locale control for the top bar (guest and mobile). Uses the v2 Select
|
// Compact locale control for the top bar (guest and mobile). Uses the v2 Select
|
||||||
@@ -45,6 +49,7 @@ interface LocaleSelectProps {
|
|||||||
export function LocaleSelect({
|
export function LocaleSelect({
|
||||||
persist = false,
|
persist = false,
|
||||||
onLocaleChange,
|
onLocaleChange,
|
||||||
|
portalContainer,
|
||||||
}: LocaleSelectProps) {
|
}: LocaleSelectProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
@@ -75,7 +80,7 @@ export function LocaleSelect({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectPopup>
|
<SelectPopup container={portalContainer}>
|
||||||
{URL_LOCALES.map(code => (
|
{URL_LOCALES.map(code => (
|
||||||
<SelectItem key={code} value={code}>
|
<SelectItem key={code} value={code}>
|
||||||
{URL_LOCALE_LABELS[code]}
|
{URL_LOCALE_LABELS[code]}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { DrawerTitle } from "@probo/ui/src/v2/Drawer/DrawerTitle";
|
|||||||
import { DrawerTrigger } from "@probo/ui/src/v2/Drawer/DrawerTrigger";
|
import { DrawerTrigger } from "@probo/ui/src/v2/Drawer/DrawerTrigger";
|
||||||
import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton";
|
import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton";
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||||
import { useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
@@ -76,6 +76,9 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
const { openSubscribe, isSubscribed, unsubscribe, isUnsubscribing } = useSubscribeDialog();
|
const { openSubscribe, isSubscribed, unsubscribe, isUnsubscribing } = useSubscribeDialog();
|
||||||
const [signOut, isSigningOut] = useSignOut();
|
const [signOut, isSigningOut] = useSignOut();
|
||||||
const [open, setOpen] = useState(false);
|
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<HTMLDivElement>(null);
|
||||||
const identity = useFragment(topBarMobileNavFragment, identityKey);
|
const identity = useFragment(topBarMobileNavFragment, identityKey);
|
||||||
const localizedPath = useLocalizedPath();
|
const localizedPath = useLocalizedPath();
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DrawerPopup side="right">
|
<DrawerPopup side="right" ref={drawerPopupRef}>
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle>{t("topBar.menuTitle")}</DrawerTitle>
|
<DrawerTitle>{t("topBar.menuTitle")}</DrawerTitle>
|
||||||
<DrawerClose
|
<DrawerClose
|
||||||
@@ -147,6 +150,7 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
<LocaleSelect
|
<LocaleSelect
|
||||||
persist={identity != null}
|
persist={identity != null}
|
||||||
onLocaleChange={close}
|
onLocaleChange={close}
|
||||||
|
portalContainer={drawerPopupRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{identity == null
|
{identity == null
|
||||||
|
|||||||
@@ -33,15 +33,21 @@ export type DrawerPopupProps
|
|||||||
|
|
||||||
// Portal + dimmed backdrop + edge-aligned popup. Children compose the header /
|
// Portal + dimmed backdrop + edge-aligned popup. Children compose the header /
|
||||||
// body / footer regions inside Base UI's Content (swipe-safe text selection).
|
// 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) {
|
export function DrawerPopup(props: DrawerPopupProps) {
|
||||||
const { className, children, side, ...popupProps } = props;
|
const { className, children, side, ref, ...popupProps } = props;
|
||||||
const slots = drawer({ side });
|
const slots = drawer({ side });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDrawer.Portal>
|
<BaseDrawer.Portal>
|
||||||
<BaseDrawer.Backdrop className={slots.backdrop()} />
|
<BaseDrawer.Backdrop className={slots.backdrop()} />
|
||||||
<BaseDrawer.Viewport className={slots.viewport()}>
|
<BaseDrawer.Viewport className={slots.viewport()}>
|
||||||
<BaseDrawer.Popup className={slots.popup({ className })} {...popupProps}>
|
<BaseDrawer.Popup
|
||||||
|
ref={ref}
|
||||||
|
className={slots.popup({ className })}
|
||||||
|
{...popupProps}
|
||||||
|
>
|
||||||
<BaseDrawer.Content className={slots.content()}>
|
<BaseDrawer.Content className={slots.content()}>
|
||||||
{children}
|
{children}
|
||||||
</BaseDrawer.Content>
|
</BaseDrawer.Content>
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export type SelectPopupProps
|
|||||||
= & Omit<ComponentProps<typeof BaseSelect.Popup>, "className">
|
= & Omit<ComponentProps<typeof BaseSelect.Popup>, "className">
|
||||||
& {
|
& {
|
||||||
className?: string;
|
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<typeof BaseSelect.Portal>["container"];
|
||||||
// Positioner placement passthrough.
|
// Positioner placement passthrough.
|
||||||
side?: ComponentProps<typeof BaseSelect.Positioner>["side"];
|
side?: ComponentProps<typeof BaseSelect.Positioner>["side"];
|
||||||
align?: ComponentProps<typeof BaseSelect.Positioner>["align"];
|
align?: ComponentProps<typeof BaseSelect.Positioner>["align"];
|
||||||
@@ -36,13 +39,13 @@ export type SelectPopupProps
|
|||||||
// Portal + positioner + styled popup holding the select items.
|
// Portal + positioner + styled popup holding the select items.
|
||||||
export function SelectPopup(props: SelectPopupProps) {
|
export function SelectPopup(props: SelectPopupProps) {
|
||||||
const {
|
const {
|
||||||
className, children,
|
className, children, container,
|
||||||
side = "bottom", align = "start", sideOffset = 4,
|
side = "bottom", align = "start", sideOffset = 4,
|
||||||
...popupProps
|
...popupProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseSelect.Portal>
|
<BaseSelect.Portal container={container}>
|
||||||
{/* z-3 on the Positioner so the portaled root wins over in-page z-1. */}
|
{/* z-3 on the Positioner so the portaled root wins over in-page z-1. */}
|
||||||
<BaseSelect.Positioner className="z-3" side={side} align={align} sideOffset={sideOffset}>
|
<BaseSelect.Positioner className="z-3" side={side} align={align} sideOffset={sideOffset}>
|
||||||
<BaseSelect.Popup className={selectPopup({ className })} {...popupProps}>
|
<BaseSelect.Popup className={selectPopup({ className })} {...popupProps}>
|
||||||
|
|||||||
Reference in New Issue
Block a user