fix(usage): drop the tinted block and stop the modal opening with a focus ring

Two things reported on the reformatted consent modal.

The green box is gone. Setting "what is never included" apart as a
tinted panel broke the rhythm of the sections and read as an arbitrary
highlight rather than emphasis. All six sections are uniform now; the
icon and heading are enough to tell them apart.

The green bars across the disclosure were a focus ring, not a border.
showModal() focuses the first focusable descendant, which since the
reformat was the scrollable region I had given tabIndex={0} — so its
inset ring was drawn for every user the moment the dialog opened, and
because the dialog clips its sides a full-width inset ring appears as
two coloured bars. Focus now goes to the dialog itself, which is also
the better screen-reader behaviour: the title is announced on open, and
the region's ring appears only when someone deliberately tabs to it. It
is a thinner, softer ring for that case. The dialog suppresses its own
ring, since that focus is programmatic rather than keyboard navigation.

The collector shown in the transport sentence was never wrong: it
interpolates the configured collector, and the screenshots showing
http://127.0.0.1:9 were taken on a rig deliberately pointed at a dead
loopback port so they could not reach production. Re-checked with
USAGE_COLLECTOR_URL unset: the sentence reads
https://usage.picpeak.app and both links resolve there.

Refs #1110
This commit is contained in:
Paul Nothaft
2026-09-05 22:49:21 +02:00
parent a9e51d8fd7
commit 75ef137b7d
@@ -26,10 +26,9 @@ const DISCLOSURE: {
key: string;
heading: string;
Icon: ComponentType<{ className?: string }>;
tone?: 'positive';
}[] = [
{ key: 'fields', heading: 'sectionFields', Icon: ListChecks },
{ key: 'excluded', heading: 'sectionExcluded', Icon: ShieldOff, tone: 'positive' },
{ key: 'excluded', heading: 'sectionExcluded', Icon: ShieldOff },
{ key: 'transport', heading: 'sectionTransport', Icon: Send },
{ key: 'visibility', heading: 'sectionVisibility', Icon: Globe },
{ key: 'deletion', heading: 'sectionDeletion', Icon: Trash2 },
@@ -52,11 +51,19 @@ function ConsentDialog({
const [checked, setChecked] = useState(false);
useEffect(() => {
ref.current?.showModal();
// showModal() focuses the first focusable descendant, which is the scroll
// region below — so its focus ring was drawn for everyone the moment the
// dialog opened, and because the dialog clips its sides an inset ring
// reads as two coloured bars across the disclosure rather than a ring.
// Focusing the dialog puts the ring back where it belongs: only when
// someone deliberately tabs to the region.
ref.current?.focus();
}, []);
return (
<dialog
ref={ref}
onCancel={close}
tabIndex={-1}
aria-labelledby="usage-consent-title"
// Column layout with its own scroll region, so the title stays put and
// the actions never scroll out of reach on a short screen.
@@ -65,7 +72,7 @@ function ConsentDialog({
// does not follow dark mode, so it stayed white while the dark: text
// variants below turned near-white. neutral-800 is what `.card`
// resolves to in dark, which is what the rest of the admin UI uses.
className="w-full max-w-2xl max-h-[85vh] flex flex-col overflow-hidden rounded-xl p-0 bg-white dark:bg-neutral-800 shadow-xl backdrop:bg-black/50"
className="w-full max-w-2xl max-h-[85vh] flex flex-col overflow-hidden rounded-xl p-0 bg-white dark:bg-neutral-800 shadow-xl backdrop:bg-black/50 focus:outline-none"
>
<header className="flex items-start gap-3 px-6 pt-6 pb-4">
<span className="mt-0.5 flex h-9 w-9 flex-none items-center justify-center rounded-full bg-primary-50 dark:bg-primary-900/30">
@@ -92,17 +99,10 @@ function ConsentDialog({
tabIndex={0}
role="group"
aria-label={t('productUsage.consentTitle') as string}
className="flex-1 overflow-y-auto border-y border-neutral-200 dark:border-neutral-700 px-6 py-4 space-y-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500"
className="flex-1 overflow-y-auto border-y border-neutral-200 dark:border-neutral-700 px-6 py-4 space-y-4 focus:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary-400"
>
{DISCLOSURE.map(({ key, heading, Icon, tone }) => (
<section
key={key}
className={
tone === 'positive'
? 'rounded-lg border border-primary-200 dark:border-primary-800 bg-primary-50/60 dark:bg-primary-900/20 p-3'
: undefined
}
>
{DISCLOSURE.map(({ key, heading, Icon }) => (
<section key={key}>
<h3 className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400">
<Icon className="h-3.5 w-3.5" />
{t(`productUsage.${heading}`)}