fix(dashboard-v2): make UpcomingPanel rows keyboard-accessible [Phase 2d]#633
Merged
barach6662001-bit merged 1 commit intomainfrom Apr 24, 2026
Merged
Conversation
… 2d] Phase 2d of the accessibility paydown that began with FieldCard (#629, Phase 2b) and continued in OperationsTimeline (#632, Phase 2c). Same proven pattern, applied to the v2 IA "Upcoming" panel — the only remaining clickable-row debt the user asked to fix this round. Before ------ Each row in pages/DashboardV2/components/UpcomingPanel was a <li> with onClick={navigate} and an inline style={{cursor: 'pointer'}} — no role, no tabIndex, no keyDown handler, no accessible name. AT users heard nothing meaningful and keyboard users could not reach or activate the row at all. After ----- Each row gains the same FieldCard / OperationsTimeline contract: * role="button" + tabIndex={0} * onKeyDown handles Enter and Space * Space calls preventDefault to suppress page scroll * unrelated keys (Tab, Escape, Shift, ArrowDown, ...) are ignored * aria-label summarises the row exactly as it reads visually: "{operationType}, {fieldName}, {DD MMM}" "{operationType}, {fieldName}, {DD MMM}, {area} га" (when area > 0) * the decorative date chip and area pill are aria-hidden because their content is duplicated verbatim in aria-label * a token-driven :focus-visible ring (var(--brand), 2px, offset 2) lives in the CSS module, with .item:focus reset for mouse focus * inline cursor: pointer migrated into the CSS module so the focus-visible rule lives next to it (no runtime style override fight) Notes / scope ------------- There is no nested <button> inside the row, so the FieldCard "decorative button → presentation span" step from Phase 2b does not apply here. Visual layout, the navigation target (/operations/{id}), the weather strip, the filter / sort / windowDays logic and every i18n string are preserved verbatim. The Card / Surface API is not touched, no dependencies are added, no routes change, and no other clickable-row debt is swept in this PR — legacy components/dashboard/OperationsTimeline, FieldStatusCard, KpiHeroRow, AppLayout, Sidebar are all untouched per the explicit scope. Tests ----- New file: pages/DashboardV2/components/__tests__/UpcomingPanel.test.tsx 17 cases across 4 groups: empty state - empty operations array → placeholder - all operations completed → placeholder - operations outside the 7-day window → placeholder primary render - one row per upcoming op, type/field/date chip rendered - area suffix shown when areaProcessed > 0 - area suffix omitted when areaProcessed is 0 - optional weather strip rendered when prop supplied - exactly one button per row (no nested interactives) accessibility - aria-label = "{op}, {field}, {date}" - aria-label = "{op}, {field}, {date}, {area} га" with area - tabIndex=0 (keyboard reachable) - decorative date chip / area pill not exposed as buttons activation - click navigates to /operations/{id} - Enter navigates - Space navigates - Space calls preventDefault (page scroll suppressed) - Tab / Escape / Shift / ArrowDown do nothing - multi-row: routes to the correct id Validation ---------- pnpm --filter frontend test → 100/100 passing (17 new + 83 existing) npx tsc -b --noEmit → clean npx eslint <changed files> → clean npx vite build → green (✓ built in 26s)
This was referenced Apr 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2d — UpcomingPanel keyboard accessibility
Continues the surgical a11y paydown from #629 (FieldCard, Phase 2b) and #632 (OperationsTimeline, Phase 2c) on the only remaining clickable-row debt the user asked to fix this round.
Before
pages/DashboardV2/components/UpcomingPanelrendered each upcoming-operation row as:No
role, notabIndex, noonKeyDown, no accessible name. Unreachable by keyboard, unannounced by AT.After
Same contract proven in #629 / #632:
role="button"+tabIndex={0}+onKeyDown(Enter / Space +preventDefaulton Space)aria-labelmirrors the visible row content:{operationType}, {fieldName}, {DD MMM}{operationType}, {fieldName}, {DD MMM}, {area} гаwhen an area suffix is shownaria-hidden="true"— their content is already inaria-label:focus-visiblering (outline: 2px solid var(--brand); outline-offset: 2px;) in the CSS module, with.item:focus { outline: none; }for mouse focuscursor: pointermigrated into the CSS module so the focus rule has no runtime style fightScope (per request)
In: UpcomingPanel.tsx, UpcomingPanel.module.css, new UpcomingPanel.test.tsx.
Out: legacy
components/dashboard/OperationsTimeline.tsx, FieldStatusCard, KpiHeroRow, AppLayout, Sidebar, OperationDetail, OperationsList, Card / Surface API. No new dependencies, no route changes, no API or business-logic changes, no redesign.Why no "decorative button → span" step
Unlike FieldCard (Phase 2b), this row has no nested
<button>to convert. Noted explicitly in the JSDoc so future phases don't expect that step.Tests (17, new file)
aria-labelwithout/with area;tabIndex=0; decorative chips not exposed as buttonspreventDefault; ignored keys; multi-row id routingValidation
pnpm --filter frontend test→ 100 / 100 (17 new + 83 existing)npx tsc -b --noEmit→ cleannpx eslinton changed files → cleannpx vite build→ green (✓ built in 26 s)