diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index eb94771d..caa4545d 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -3,6 +3,7 @@ import "@mantine/spotlight/styles.css"; import "@mantine/notifications/styles.css"; import '@mantine/dates/styles.css'; import "@/styles/a11y-overrides.css"; +import "@/styles/notification-overrides.css"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; @@ -47,7 +48,15 @@ function renderApp() { - + {/* top-center: toasts sit in the top of the viewport, in the line + of sight, and no longer cover centered content (e.g. "Load + more"). The below-chrome vertical offset is applied via a + position-scoped CSS rule in notification-overrides.css (NOT an + inline `style`): Mantine renders all six position containers at + once and an inline root style would land on every one, giving the + bottom-* containers both top+bottom → full-viewport transparent + overlays that swallow clicks. */} + {/* Root boundary above every lazy route's Suspense: a stale-chunk 404 after a deploy is caught and recovered here instead of diff --git a/apps/client/src/styles/notification-overrides.css b/apps/client/src/styles/notification-overrides.css new file mode 100644 index 00000000..a1dbbe33 --- /dev/null +++ b/apps/client/src/styles/notification-overrides.css @@ -0,0 +1,64 @@ +/* + * Toast (Mantine Notification) visibility overrides. + * Mantine renders colorless toasts on --mantine-color-body (== the page + * background: white in light mode) with a faint shadow, so on white pages the + * card has no visible edge. These rules give every toast a type-tinted + * background, a WCAG-checked border and a stronger shadow so it separates from + * the page. The [data-mantine-color-scheme] + static-class selector (0,2,0) + * beats Mantine's own (0,1,0) rules regardless of stylesheet order (Mantine's + * bg/border rules wrap the scheme attribute in :where(), so they stay (0,1,0)). + * --notification-color is defined on the same element (defaults to primary, + * set per `color` prop), so tint/border follow the toast type. This also covers + * the loading/import toast (no accent bar, since the spinner takes the icon + * slot): its visibility comes from tone + border + shadow + the colored spinner. + */ + +/* + * Push the top-anchored toast containers below the top chrome (fixed 45px + * header + optional 45px format toolbar + ~6px gap) so a toast (z-index 10000) + * neither covers nor intercepts clicks on the header/toolbar (both z-index 99). + * + * Scoped to [data-position^='top'] on purpose. Mantine renders ALL SIX position + * containers simultaneously (`position` only routes toasts into one via the + * store); the root `style` prop would be applied to every one of them by + * getStyles("root"). A blanket `top` would land on the bottom-* containers too + * (which carry `bottom:16px`) → position:fixed + both edges + height:auto makes + * them stretch the full viewport height, and the container root has neither + * pointer-events:none nor a background, so those transparent z-10000 overlays + * would swallow clicks across the whole page. Restricting to top-* leaves the + * bottom containers at height:0. + * + * Specificity: `.mantine-Notifications-root[data-position^='top']` is (0,2,0) + * (class + attribute) and beats Mantine's own top rule + * `.m_b37d9ac7:where([data-position='top-center']){top:16px}` which is (0,1,0) + * (the :where() contributes 0), regardless of stylesheet order. + */ +.mantine-Notifications-root[data-position^='top'] { + top: 96px; +} + +[data-mantine-color-scheme='light'] .mantine-Notification-root { + /* ~10% type color over white: clearly off-white, text contrast preserved */ + background-color: color-mix(in srgb, var(--notification-color) 10%, var(--mantine-color-white)); + /* Border must clear WCAG 3:1 non-text contrast on white. The repo rejects + gray-4 for this (a11y-overrides.css); gray-6 base (~3.32:1) darkened by the + type color stays >= 3:1. */ + border: 1px solid color-mix(in srgb, var(--notification-color) 45%, var(--mantine-color-gray-6)); + box-shadow: var(--mantine-shadow-xl); +} + +[data-mantine-color-scheme='dark'] .mantine-Notification-root { + /* Dark page (dark-7/8) vs toast (dark-6) already separate a little; border + + shadow carry the type cue here (a 7% dark tint was near-invisible). */ + background-color: color-mix(in srgb, var(--notification-color) 14%, var(--mantine-color-dark-6)); + border: 1px solid color-mix(in srgb, var(--notification-color) 45%, var(--mantine-color-dark-3)); + box-shadow: var(--mantine-shadow-xl); +} + +/* Mantine's message-with-title color is gray-6 (#868e96, already only ~3.32:1 + on white — below AA 4.5:1); the new tint pushes it lower. Bump to gray-7 to + keep multi-line colored toasts readable, consistent with the repo's existing + WCAG tuning (theme.ts already bumps this same gray-6 up elsewhere). */ +[data-mantine-color-scheme='light'] .mantine-Notification-description[data-with-title] { + color: var(--mantine-color-gray-7); +}