diff --git a/frontend/src/pages/DashboardV2/components/UpcomingPanel.module.css b/frontend/src/pages/DashboardV2/components/UpcomingPanel.module.css index c9a8b965..8b5af390 100644 --- a/frontend/src/pages/DashboardV2/components/UpcomingPanel.module.css +++ b/frontend/src/pages/DashboardV2/components/UpcomingPanel.module.css @@ -15,10 +15,33 @@ align-items: center; padding: 10px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.04); + /* + * Phase 2d migrated this from an inline `style={{cursor: 'pointer'}}` + * on the JSX so the focus-visible rule below can live alongside it + * without a runtime style override fight. + */ + cursor: pointer; } .item:last-child { border-bottom: 0; } +/* + * Phase 2d keyboard-focus affordance — mirrors the FieldCard / Phase 2c + * OperationsTimeline pattern: + * - .item:focus resets the platform-default outline so mouse-driven + * focus stays ring-free (the row has no hover background to begin + * with — there is nothing to "preserve parity" with, so the ring + * stands alone for keyboard users only). + * - .item:focus-visible draws a token-driven brand-coloured ring. + * 2px outset is enough to clear the row's bottom-border divider. + */ +.item:focus { outline: none; } + +.item:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + .dateChip { font-family: var(--fm); font-size: 11px; diff --git a/frontend/src/pages/DashboardV2/components/UpcomingPanel.tsx b/frontend/src/pages/DashboardV2/components/UpcomingPanel.tsx index 7a64c8a2..4a6d21f5 100644 --- a/frontend/src/pages/DashboardV2/components/UpcomingPanel.tsx +++ b/frontend/src/pages/DashboardV2/components/UpcomingPanel.tsx @@ -1,3 +1,4 @@ +import type { KeyboardEvent } from 'react'; import { useNavigate } from 'react-router-dom'; import { Cloud, Sun } from 'lucide-react'; import dayjs from 'dayjs'; @@ -22,6 +23,31 @@ interface Props { * with a small weather strip at the bottom. Row 5, right column in the * v2 IA. Filters operations to: not completed AND plannedDate within * [today, today + windowDays]. + * + * Phase 2d: pays down the same accessibility debt fixed by Phase 2b + * (FieldCard) and Phase 2c (dashboard OperationsTimeline). Each row was + * a
  • with onClick + an inline `cursor: pointer` and no keyboard + * affordance — unreachable by keyboard, unnamed for assistive tech. + * + * Pattern (identical to 2b/2c): + * - role="button" + tabIndex={0} + onKeyDown (Enter, Space) on each + * row, with preventDefault on Space to suppress page scroll + * - aria-label summarising the row (operation type + field + date, + * plus the optional area suffix when present) so screen readers + * announce a meaningful destination instead of an unnamed button + * - the decorative date chip and area pill are marked aria-hidden + * because their content is duplicated in aria-label + * - inline `cursor: pointer` migrated into the CSS module so the + * `:focus-visible` rule can live alongside it without a runtime + * style override fight + * - a token-driven :focus-visible ring lives in the CSS module + * + * No nested