The arrow-key nudge handler existed but only ever acted on a POINTER selection, and nothing could select without a mouse — so the feature was unusable by exactly the keyboard users it's for. This is the scoped first slice: give the canvas a keyboard path in, and fix the Modal focus trap that every destructive confirmation goes through. Canvas: - The <svg> gets role="application" + an aria-label describing the controls, and a <title> naming the garden — a screen reader now announces an interactive canvas rather than an empty graphic. - Each object <g> is a focusable role="button" with an aria-label (name + kind) and aria-pressed reflecting selection. Enter/Space selects it — the step that was missing — which makes the existing arrow-key nudge reachable. - A :focus-visible CSS rule draws a dashed accent ring on keyboard focus (and NOT on a mouse click, which is the point of :focus-visible). CSS rather than React state because onFocus on an SVG <g> is unreliable, and a CSS rule cleanly overrides the shape's inline stroke. Modal (blast radius: DeleteGarden/ClearBed/DeletePlant/DeleteSeedLot/Share): - Tab is trapped inside the dialog and wraps at the ends, instead of walking out into the page behind the backdrop. - On close, focus returns to the element that opened the dialog rather than landing on <body>. Verified live against the built binary with real keyboard input: Tab focuses an object (SVG <g tabindex> genuinely takes focus), Enter flips aria-pressed false→true, the focus-visible dash renders (computed stroke-dasharray "5px, 4px"), the dialog traps focus through 5 Tabs, and Escape closes it and restores focus to the opener. Follow-ups noted, not done here: object dimensions in the aria-label (needs the garden's unit context this component doesn't hold), roving-tabindex between plops inside a focused bed, and the EditorRail tablist semantics. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
60 lines
1.5 KiB
CSS
60 lines
1.5 KiB
CSS
@import 'tailwindcss';
|
|
|
|
/* Garden-planner theme tokens. Each --color-* becomes a Tailwind utility
|
|
(bg-*, text-*, border-*). Light by default; dark via prefers-color-scheme. */
|
|
@theme {
|
|
--color-bg: #f8faf7;
|
|
--color-surface: #ffffff;
|
|
--color-border: #e3e8e0;
|
|
--color-muted: #61705d;
|
|
--color-fg: #1c2419;
|
|
|
|
--color-accent: #3f8f4f;
|
|
--color-accent-strong: #2f7a3e;
|
|
--color-accent-contrast: #ffffff;
|
|
|
|
--font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
}
|
|
|
|
@layer base {
|
|
html,
|
|
body,
|
|
#root {
|
|
height: 100%;
|
|
}
|
|
body {
|
|
background-color: var(--color-bg);
|
|
color: var(--color-fg);
|
|
font-family: var(--font-sans);
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* Keyboard focus on a canvas object (#84). :focus-visible shows the ring for
|
|
keyboard focus but not a mouse click; the dashed accent ring distinguishes
|
|
"focused" from the solid ring that marks "selected". A CSS rule overrides
|
|
the shape's inline stroke presentation attributes. */
|
|
.object-shape {
|
|
outline: none;
|
|
}
|
|
.object-shape:focus-visible :is(rect, ellipse) {
|
|
stroke: var(--color-accent-strong);
|
|
stroke-width: 2;
|
|
stroke-dasharray: 5 4;
|
|
}
|
|
}
|
|
|
|
/* Dark theme: override the same tokens so utilities recolor automatically. */
|
|
@media (prefers-color-scheme: dark) {
|
|
@theme {
|
|
--color-bg: #10140f;
|
|
--color-surface: #171c15;
|
|
--color-border: #2a3226;
|
|
--color-muted: #8a967f;
|
|
--color-fg: #e7ede2;
|
|
|
|
--color-accent: #5bb06a;
|
|
--color-accent-strong: #6fc07d;
|
|
--color-accent-contrast: #0c1109;
|
|
}
|
|
}
|