Skip to content

feat(a11y): screen-reader summaries, accessible modal primitive, reduced motion, colour-safe health status - #1011

Merged
llinsss merged 1 commit into
DogStark:mainfrom
ushpraise:a11y/charts-modals-motion-contrast
Aug 28, 2026
Merged

llinsss merged 1 commit into
DogStark:mainfrom
ushpraise:a11y/charts-modals-motion-contrast

Conversation

@ushpraise

Copy link
Copy Markdown

Summary

Adds four small, independently-tested accessibility building blocks and moves the app toward using them. Each module is a pure, unit-testable core with a thin React wrapper, so the behaviour is characterised by tests before any screen is migrated.

Closes #979
Closes #980
Closes #981
Closes #982

#979 — Screen-reader summaries for charts and maps

src/utils/a11yChartSummary.ts gives visual-only data a non-visual equivalent (WCAG 1.1.1 / 1.3.1):

  • buildChartSummary — one-line spoken summary (count, latest value, trend, min–max range) for a chart region's accessibilityLabel.
  • buildChartDataTable — ordered string[], one focusable row per plotted point, so a series can be read point by point.
  • buildMapSummary / buildClinicListSummary — summary + nearest-first list equivalent for VetMapScreen's pins, synchronised with the active filter and offline state. No coordinates or phone numbers are placed in the spoken strings.
  • sectionHeading — heading text for use with accessibilityRole="header".

#980 — Standardised modal focus and back-button behaviour

src/hooks/useAccessibleModal.ts + src/components/AccessibleModal.tsx — one accessible dialog primitive:

  • moves screen-reader focus to the dialog title on open, restores it to the trigger (returnFocusRef) on close;
  • consistent Android hardware-back and accessibility-escape handling via onRequestClose;
  • a shared modalStack so that with nested modals only the top-most instance reacts to back / receives focus;
  • accessibilityViewIsModal container and a header-role title.

The stacking logic lives in pure modalStack helpers and is covered by nested-modal tests.

#981 — Respect reduced motion

src/utils/motion.ts:

  • useReducedMotion() — live OS "Reduce Motion" preference, defaults to false, updates on reduceMotionChanged, cleans up its listener, and degrades gracefully if the preference can't be read.
  • Pure helpers that remove movement while preserving the state change: motionDuration, resolveTransition (navigation), allowDecorativeAnimation (looping / parallax / gesture-driven), chartDrawProgress (chart jumps to its final frame). AccessibleModal already consumes it (animationType="none" under reduced motion).

#982 — High-contrast, colour-blind-safe health statuses

src/theme/healthStatus.ts — severity and trend tokens that never rely on hue alone (WCAG 1.4.1). Every token carries a text label, an icon, and a distinct shape; describeHealthStatus() produces the spoken/label string. Foreground colours use an Okabe–Ito-derived palette and are verified in tests to clear WCAG AA (4.5:1) against every light and dark surface token.

Tests

Focused unit tests added for all four modules (characterisation-first), following existing repo patterns (jest.mock('react-native'), renderHook):

  • src/utils/__tests__/a11yChartSummary.test.ts — summaries, list equivalents, empty/offline/malformed-input paths, no-PII assertions.
  • src/utils/__tests__/motion.test.ts — reduced-motion policy + hook default / live-update / unmount / unsupported-preference paths.
  • src/hooks/__tests__/useAccessibleModal.test.ts — focus move + restore, Android back routing, nested-modal stacking, unmount cleanup.
  • src/theme/__tests__/healthStatus.test.ts — redundant cues, distinct shapes, WCAG AA contrast in light and dark themes.

Pure-function output was verified locally via node --experimental-strip-types; the full Jest run requires npm install (no node_modules in this environment).

Notes

  • Synthetic data only; no secrets, health records, contact details, precise location, wallet material, or tokens added to logs, fixtures, or snapshots.
  • Narrowly scoped: new shared primitives + tests + docs/ACCESSIBILITY.md. Migrating each existing chart/modal/animation onto these helpers is intended as follow-up so this PR stays reviewable.
  • No app-facing behaviour changes yet, so no config / API contract / migration / permission changes are required.

…, colour-safe health status

- a11yChartSummary: spoken summaries + list equivalents + semantic headings for
  charts and the vet map, kept in sync with the active filter (closes DogStark#979)
- AccessibleModal / useAccessibleModal: one accessible dialog primitive with
  focus move + restore, Android back handling, and nested-modal stacking (closes DogStark#980)
- motion: useReducedMotion hook + pure helpers that drop movement while keeping
  state changes across transitions, charts, and gestures (closes DogStark#981)
- theme/healthStatus: high-contrast, colour-blind-safe status tokens with text
  and icon cues, WCAG AA verified in light and dark themes (closes DogStark#982)
- docs/ACCESSIBILITY.md and focused unit tests for each module
@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@ushpraise Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@llinsss
llinsss merged commit 9bedee0 into DogStark:main Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants