brackt/app/components/marketing/ScoringTables.tsx
Chris Parsons 4bbcac1949
fix: resolve all 48 WCAG 2.2 AA accessibility issues (#439)
* fix: resolve all 48 WCAG 2.2 AA accessibility issues

Critical fixes:
- Add aria-label to all unlabeled inputs/selects in draft dialogs (ParticipantSelectionDialog, TimeBankAdjustmentDialog, AvailableParticipantsSection)
- Add role="dialog" + aria-modal + focus trap to ConnectionOverlay and AuthRecoveryOverlay
- Add aria-live region and connection status announcement to ConnectionOverlay

Serious fixes:
- Add skip-to-content link in root.tsx with id="main-content" on <main>
- Add aria-label to UserMenu trigger button
- Add aria-describedby + role="alert" to all auth form error messages (login, register, onboarding, forgot-password, reset-password)
- Replace emoji column headers in StandingsTable with aria-label + aria-hidden spans
- Add aria-live="assertive" to "It's your turn" desktop and mobile on-clock indicators
- Add aria-live="polite" to draft room countdown timer
- Add pause button to SportTicker (WCAG 2.2.2); add aria-hidden to ticker content
- Fix Footer text contrast (changed from 28% to text-muted-foreground)
- Fix OvernightPauseSettings: add htmlFor/id pairs and role="radiogroup"+aria-checked to mode buttons
- Fix DraftSetupSection: replace broken htmlFor with aria-label on date picker button
- Add aria-label to PeopleSection owner and commissioner selects
- Add labels to ScoringPresetPicker score inputs; add role="radiogroup"+aria-checked to preset buttons
- Add role="radiogroup"+aria-checked to AutodraftSettings option buttons
- Add accessible names, aria-current="step", and <ol> list semantics to WizardStepper

Moderate fixes:
- Add aria-controls to RecentPicksFeed toggle button; wrap picks list in aria-live region
- Add role="tab"+aria-selected+aria-controls to mobile board sub-tabs + role="tabpanel"
- Add role="radiogroup"+aria-checked to TimerModeSelector
- Add aria-current="page" + aria-label to SettingsDesktopNav
- Add aria-label="Admin navigation" to admin sidebar nav
- Add scope="col" + <caption> to StandingsTable and ScoringTables
- Add ARIA table roles (role="table/rowgroup/row/columnheader/rowheader/cell") to DraftSummaryView CSS grid

Minor fixes:
- Add aria-hidden="true" to decorative trend icons in StandingsTable
- Add aria-hidden="true" to desktop column header labels row in AvailableParticipantsSection
- Replace title with aria-label on all icon-only buttons (watchlist, queue) in AvailableParticipantsSection
- Add aria-label to NotificationSettings switchOnly Switch
- Add prefers-reduced-motion check to SlotMachineHeadline JS animation
- Bump --muted-foreground from 55% to 62% opacity for improved contrast margin

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix code review findings from WCAG compliance pass

- Add Arrow key navigation + roving tabindex to all role=radiogroup
  components (AutodraftSettings x2, TimerModeSelector,
  OvernightPauseSettings, ScoringPresetPicker) per ARIA radio pattern
- Extract shared focus-trap logic into useFocusTrap hook; update
  ConnectionOverlay and AuthRecoveryOverlay to use it
- Add tabIndex={-1} to ConnectionOverlay Card so focus can land in
  spinner-only state (no interactive children)
- Replace aria-live on loading dots container with sr-only span so
  status changes are announced by text content, not aria-label
- Remove contradictory aria-hidden+role=columnheader from
  AvailableParticipantsSection visual-only header row
- Remove invalid scope="col" from div[role=columnheader] in
  DraftSummaryView (scope is only valid on <th>)
- Remove redundant aria-label from ParticipantSelectionDialog sport
  select (htmlFor label is sufficient)
- Change WizardStepper connector <li> to role=presentation
- Revert muted-foreground from 62% to 55% (original already passes
  contrast; footer was fixed separately via text-muted-foreground)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

* Fix lint error and update tests for WCAG role changes

- Replace el! non-null assertion with optional chaining in useFocusTrap
- Update AutodraftSettings tests to query role="radio" instead of
  role="button" (buttons have an explicit radio role since the WCAG pass)
- Update AvailableParticipantsSection watchlist tests to use
  getByRole/getAllByRole instead of getByTitle/getAllByTitle (watchlist
  buttons now use aria-label instead of title)

https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 20:11:38 -07:00

53 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

interface TableProps {
className?: string;
}
export function ScoringPointsTable({ className }: TableProps) {
return (
<div className={`bg-muted rounded-lg overflow-hidden ${className ?? ""}`}>
<table className="w-full text-sm">
<caption className="sr-only">Scoring points by finish position</caption>
<thead>
<tr className="border-b border-border">
<th scope="col" className="text-left p-3 font-semibold text-foreground">Finish</th>
<th scope="col" className="text-right p-3 font-semibold text-foreground">Points</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
<tr><td className="p-3">1st Place</td><td className="p-3 text-right font-semibold text-foreground">100</td></tr>
<tr><td className="p-3">2nd Place</td><td className="p-3 text-right font-semibold text-foreground">70</td></tr>
<tr><td className="p-3">3rd Place</td><td className="p-3 text-right font-semibold text-foreground">50</td></tr>
<tr><td className="p-3">4th Place</td><td className="p-3 text-right font-semibold text-foreground">40</td></tr>
<tr><td className="p-3">5th6th Place</td><td className="p-3 text-right font-semibold text-foreground">25</td></tr>
<tr><td className="p-3">7th8th Place</td><td className="p-3 text-right font-semibold text-foreground">15</td></tr>
</tbody>
</table>
</div>
);
}
export function QualifyingPointsTable({ className }: TableProps) {
return (
<div className={`bg-muted rounded-lg overflow-hidden ${className ?? ""}`}>
<table className="w-full text-sm">
<caption className="sr-only">Qualifying points by major finish position</caption>
<thead>
<tr className="border-b border-border">
<th scope="col" className="text-left p-3 font-semibold text-foreground">Major Finish</th>
<th scope="col" className="text-right p-3 font-semibold text-foreground">Qualifying Points</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
<tr><td className="p-3">1st</td><td className="p-3 text-right font-semibold text-foreground">20 QP</td></tr>
<tr><td className="p-3">2nd</td><td className="p-3 text-right font-semibold text-foreground">14 QP</td></tr>
<tr><td className="p-3">3rd</td><td className="p-3 text-right font-semibold text-foreground">10 QP</td></tr>
<tr><td className="p-3">4th</td><td className="p-3 text-right font-semibold text-foreground">8 QP</td></tr>
<tr><td className="p-3">5th6th</td><td className="p-3 text-right font-semibold text-foreground">5 QP</td></tr>
<tr><td className="p-3">7th8th</td><td className="p-3 text-right font-semibold text-foreground">3 QP</td></tr>
<tr><td className="p-3">9th12th</td><td className="p-3 text-right font-semibold text-foreground">2 QP</td></tr>
<tr><td className="p-3">13th16th</td><td className="p-3 text-right font-semibold text-foreground">1 QP</td></tr>
</tbody>
</table>
</div>
);
}