Polish the locale mismatch banner layout

Use a full-bleed bar with clearer actions and a stacked
mobile layout so long language labels stay usable.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-21 16:04:57 +02:00
parent acd41b72f3
commit 199289b42b

View File

@@ -20,8 +20,8 @@
import { GlobeIcon, XIcon } from "@phosphor-icons/react"; import { GlobeIcon, XIcon } from "@phosphor-icons/react";
import { Button } from "@probo/ui/src/v2/Button/Button"; import { Button } from "@probo/ui/src/v2/Button/Button";
import { Callout } from "@probo/ui/src/v2/Callout/Callout";
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 { useState } from "react"; import { 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";
@@ -46,8 +46,8 @@ interface LocaleMismatchCalloutProps {
identityKey: LocaleMismatchCallout_identity$key; identityKey: LocaleMismatchCallout_identity$key;
} }
// Soft notice when the URL locale differs from the signed-in identity preference. // Full-bleed notice when the URL locale differs from the signed-in identity
// Dismissed state is React-only (no localStorage/cookies). // preference. Dismissed state is React-only (no localStorage/cookies).
export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProps) { export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const identity = useFragment(localeMismatchCalloutFragment, identityKey); const identity = useFragment(localeMismatchCalloutFragment, identityKey);
@@ -75,18 +75,34 @@ export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProp
}; };
return ( return (
<div className="border-b border-sand-6 bg-sand-2 px-4 py-3"> <aside
<Callout size={1} variant="soft" color="gold" icon={<GlobeIcon weight="fill" />}> className="flex w-full items-center gap-3 bg-gold-3 px-8 py-2.5 max-md:flex-col max-md:items-stretch max-md:gap-3 max-md:px-4 max-md:py-3"
<div className="flex flex-wrap items-center gap-3"> role="status"
<p className="min-w-0 flex-1 text-2 text-sand-12"> >
<div className="flex min-w-0 flex-1 items-start gap-2">
<GlobeIcon weight="fill" className="mt-0.5 size-4 shrink-0 text-gold-11" aria-hidden />
<Text size={2} color="neutral" highContrast className="min-w-0 flex-1">
{t("locale.mismatch.message", { language: urlLabel })} {t("locale.mismatch.message", { language: urlLabel })}
</p> </Text>
<div className="flex flex-wrap items-center gap-2"> <IconButton
size={1}
variant="ghost"
color="neutral"
aria-label={t("locale.mismatch.dismiss")}
disabled={busy}
className="md:hidden"
onClick={() => setDismissed(true)}
>
<XIcon />
</IconButton>
</div>
<div className="flex shrink-0 items-center gap-2 max-md:flex-col max-md:items-stretch">
<Button <Button
size={1} size={1}
variant="soft" variant="solid"
color="neutral" color="gold"
disabled={busy} disabled={busy}
className="max-md:w-full"
onClick={switchToSaved} onClick={switchToSaved}
> >
{t("locale.mismatch.switchToMine", { language: savedLabel })} {t("locale.mismatch.switchToMine", { language: savedLabel })}
@@ -97,6 +113,7 @@ export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProp
color="neutral" color="neutral"
highContrast highContrast
disabled={busy} disabled={busy}
className="max-md:w-full"
onClick={adoptUrlLocale} onClick={adoptUrlLocale}
> >
{t("locale.mismatch.useThis", { language: urlLabel })} {t("locale.mismatch.useThis", { language: urlLabel })}
@@ -107,13 +124,12 @@ export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProp
color="neutral" color="neutral"
aria-label={t("locale.mismatch.dismiss")} aria-label={t("locale.mismatch.dismiss")}
disabled={busy} disabled={busy}
className="max-md:hidden"
onClick={() => setDismissed(true)} onClick={() => setDismissed(true)}
> >
<XIcon /> <XIcon />
</IconButton> </IconButton>
</div> </div>
</div> </aside>
</Callout>
</div>
); );
} }