- Add fixed slab/slab-text/slab-muted tokens so utility strip and footer never invert to near-white in dark mode (root token split: ink is themed text, slab is fixed dark surface) - btn-ink uses fixed #111827 so confirm dialogs stay dark-on-dark readable - Toast error surface switched to slab; success uses text-white (not text-ink-on) - StatusBadge active and SimulationStatusBadge review_required/done use text-white instead of text-canvas/text-ink-on (prevents near-black text on colored pill in dark mode) - Modal backdrops (MitreMatrixModal, ConfirmDialog) switched to .modal-backdrop class (fixed rgba(0,0,0,0.6)) instead of bg-ink/60 which turned near-white - Card shadow lifted in dark mode via .dark .card-product override - MitreMatrixModal panel uses shadow-floating-dark in dark mode - UsersAdminPage form: items-start + explicit label-height spacer on button column for pixel-perfect baseline alignment (AC-17.3 structural fix) 92/92 tests passing, typecheck and lint clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
import { useToast } from '@/hooks/useToast';
|
||
|
||
/**
|
||
* Stack of toast notifications anchored bottom-right.
|
||
* Pure DESIGN.md surfaces: rounded-xl, soft-lift, ink slab for errors.
|
||
*/
|
||
export function ToastViewport(): JSX.Element {
|
||
const { toasts, dismiss } = useToast();
|
||
return (
|
||
<div
|
||
aria-live="polite"
|
||
aria-atomic="true"
|
||
className="fixed bottom-xl right-xl z-50 flex flex-col gap-sm w-[320px] pointer-events-none"
|
||
>
|
||
{toasts.map((t) => {
|
||
const isError = t.kind === 'error';
|
||
const isSuccess = t.kind === 'success';
|
||
// Fixed colors: toasts don't theme (error=dark slab, success=primary blue)
|
||
const surface = isError
|
||
? 'bg-slab text-slab-text'
|
||
: isSuccess
|
||
? 'bg-primary text-white'
|
||
: 'bg-canvas text-ink border border-hairline';
|
||
return (
|
||
<div
|
||
key={t.id}
|
||
role="status"
|
||
data-testid="toast"
|
||
data-kind={t.kind}
|
||
className={`pointer-events-auto rounded-xl px-md py-sm shadow-soft-lift text-[14px] leading-[1.4] ${surface}`}
|
||
>
|
||
<div className="flex items-start justify-between gap-sm">
|
||
<span className="flex-1">{t.message}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => dismiss(t.id)}
|
||
aria-label="Dismiss notification"
|
||
className="text-current opacity-70 hover:opacity-100"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
}
|