Mantine's `--mantine-color-error` defaults to `red-6` in light and `red-8` in dark. Both fail the 4.5:1 AA threshold that applies at the 12px size it renders input error text: 4.48:1 on white, and 1.93:1 on our charcoal body. Override to `red-7` (5.72:1) and `red-4` (6.44:1) — the latter matching the one-to-two-step lift App.css already applies to the semantic status/log reds for dark. Surfaced by the JsonObjectInput stories, which are the first in the repo to render an input error state and so the first to put this in front of the Storybook a11y gate. It is app-wide, not specific to that component — every form error in the Inspector was affected. It has to go through `cssVariablesResolver`: MantineProvider injects these variables into a `<style>` block appended after the stylesheet imports, so a `:root` rule in App.css loses to it on source order at equal specificity. That resolver was duplicated in main.tsx and .storybook/preview.tsx and missing from renderWithMantine, so the three could drift on any token they set. Extract one `theme/cssVariables.ts` and use it at all three sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
99 lines
3 KiB
TypeScript
99 lines
3 KiB
TypeScript
import type { Preview } from "@storybook/react-vite";
|
|
import { MantineProvider, useMantineColorScheme } from "@mantine/core";
|
|
import { Notifications } from "@mantine/notifications";
|
|
import "@mantine/core/styles.css";
|
|
import "@mantine/notifications/styles.css";
|
|
import "../src/App.css";
|
|
import { theme } from "../src/theme/theme";
|
|
import { cssVariablesResolver } from "../src/theme/cssVariables";
|
|
import { useEffect } from "react";
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
function ColorSchemeWrapper({
|
|
colorScheme,
|
|
children,
|
|
}: {
|
|
colorScheme: "light" | "dark";
|
|
children: React.ReactNode;
|
|
}) {
|
|
const { setColorScheme } = useMantineColorScheme();
|
|
|
|
useEffect(() => {
|
|
setColorScheme(colorScheme);
|
|
}, [colorScheme, setColorScheme]);
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
const preview: Preview = {
|
|
globalTypes: {
|
|
colorScheme: {
|
|
description: "Mantine color scheme",
|
|
toolbar: {
|
|
title: "Color Scheme",
|
|
icon: "mirror",
|
|
items: [
|
|
{ value: "light", title: "Light", icon: "sun" },
|
|
{ value: "dark", title: "Dark", icon: "moon" },
|
|
],
|
|
dynamicTitle: true,
|
|
},
|
|
},
|
|
},
|
|
initialGlobals: {
|
|
colorScheme: "light",
|
|
},
|
|
decorators: [
|
|
(Story, context) => {
|
|
const isFullscreen = context.parameters?.layout === "fullscreen";
|
|
return (
|
|
<MantineProvider
|
|
theme={theme}
|
|
defaultColorScheme="light"
|
|
cssVariablesResolver={cssVariablesResolver}
|
|
>
|
|
<ColorSchemeWrapper
|
|
colorScheme={context.globals.colorScheme ?? "light"}
|
|
>
|
|
<Notifications position="top-right" />
|
|
{isFullscreen ? (
|
|
<div style={{ height: "100vh", overflow: "hidden" }}>
|
|
<Story />
|
|
</div>
|
|
) : (
|
|
<Story />
|
|
)}
|
|
</ColorSchemeWrapper>
|
|
</MantineProvider>
|
|
);
|
|
},
|
|
],
|
|
parameters: {
|
|
controls: {
|
|
matchers: {
|
|
color: /(background|color)$/i,
|
|
date: /Date$/i,
|
|
},
|
|
},
|
|
a11y: {
|
|
// `'error'` fails the Storybook play-function tests (part of `npm run ci`)
|
|
// on any axe violation, so the zero-violation state this PR reached is
|
|
// enforced going forward rather than being a point-in-time result.
|
|
test: "error",
|
|
// Stories render presentational components (and whole screens) in
|
|
// isolation, outside the app's `AppShell` — which is what provides the
|
|
// page landmarks (`<main>`, nav) in the real app. The `region` rule
|
|
// ("all content must live inside a landmark") is therefore a page-level
|
|
// concern that can't be satisfied in Storybook isolation and would fire on
|
|
// essentially every story; it's covered by the app shell at runtime, not
|
|
// by the components under test. Disable it here so the a11y panel surfaces
|
|
// only rules the components can actually own.
|
|
config: {
|
|
rules: [{ id: "region", enabled: false }],
|
|
},
|
|
},
|
|
layout: "centered",
|
|
},
|
|
};
|
|
|
|
export default preview;
|