Closes #460
This PR adds a fully accessible, three-step export modal (ExportHistoryModal) that allows users to download their transaction history as CSV or PDF. The modal provides configurable date ranges, column selection, and privacy masking toggles — reusing existing maskName() and truncateHash() helpers from the receipt module.
| File | Purpose |
|---|---|
src/components/dashboard/export-history-modal.tsx |
Main component — 3-step flow: Configure → Generating → Complete |
src/components/dashboard/export-history-modal.test.tsx |
28 unit tests covering all states, interactions, and accessibility |
docs/export-history-modal-design-system.md |
Full design system documentation |
| File | Change |
|---|---|
src/components/dashboard/index.ts |
Added export * from "./export-history-modal" |
- Radio-style toggle buttons with icons (
FileSpreadsheet/FileText) - Default: CSV
- PDF opens a formatted print view via
window.print()
- Preset buttons: Last 7 days, Last 30 days, Last 90 days, This year, All time
- Custom range: inline
<input type="date">with min/max constraints - Radio-group semantics (
role="radio",aria-checked)
- 6 checkboxes: Date, Description, Amount, Status, Transaction ID, Counterparty
- All selected by default; export button disabled when none selected
- Native checkboxes with
accentColorstyling
- Mask counterparty names — uses
maskName()from receipt/masking - Mask transaction IDs — uses
truncateHash()from receipt/masking - Switch controls with
role="switch"andaria-checked - Privacy summary shown in the complete step with masked examples
- Configure — all settings in a scrollable form with
fieldset/legendgrouping - Generating —
Spinner+ progress bar (role="progressbar") with animated fill - Complete — success check, download buttons, back button, privacy summary
| Requirement | Implementation |
|---|---|
role="dialog" with aria-modal="true" |
Dialog container |
| Focus trap | FocusTrap component wraps dialog |
| Escape to dismiss | keydown listener on document (blocked during generation) |
| Screen reader announcements | LiveRegion with conditional aria-live="assertive" on completion |
| Radio semantics | role="radio" + aria-checked on format and date range buttons |
| Switch semantics | role="switch" + aria-checked on privacy toggles |
| Progress bar | role="progressbar" with aria-valuenow/min/max |
| Focus indicators | focus-visible:ring-2 focus-visible:ring-cyan-300 on all interactive elements |
| Reduced motion | motion-reduce:transition-none on progress bar animation |
✓ src/components/dashboard/export-history-modal.test.tsx (28 tests) 1383ms
Test Files 1 passed (1)
Tests 28 passed (28)
Test coverage includes:
- Visibility (open/closed)
- ARIA attributes (dialog, radio, switch, checkbox, progressbar)
- Format switching (CSV ↔ PDF)
- Date range presets and custom range reveal
- Column selection (check all, uncheck, disable button on none)
- Privacy toggle interaction
- Close button and Escape key
- Generating step transition
- Escape blocked during generation
- Custom
onExporthandler (called with config, rejection recovery) - Complete step (success state, download button, back button, privacy summary)
- State reset on modal re-open
- Mobile (<640px): single-column layout, full-width modal
- Tablet (640px+):
max-w-lg, two-column grids for format/columns - All interactive elements have minimum 44px touch targets
- Uses
elevation-4,border-white/12,rounded-3xlpattern (consistent withReceiptModal,RefundConfirmationModal) - Reuses
FocusTrap,LiveRegion,Spinnerfrom common/ui components - Reuses
maskName(),truncateHash()from receipt module - Dark/light theme support via CSS custom properties
- RTL-compatible with
icon-directionalclass on chevron
- Contrast ratios meet WCAG 2.1 AA (4.5:1)
- Keyboard navigation is logical and focus rings are visible
- Semantic HTML and ARIA labels are used correctly
- Screen reader announcements via LiveRegion
- Layout is mobile-first and works on all breakpoints
- Touch targets ≥ 44px
- Loading / generating / complete / error states handled
- Design tokens (elevation, border, spacing) followed
- Reduced motion supported
- Navigate to any page and render
<ExportHistoryModal isOpen={true} onClose={() => {}} /> - Try switching between CSV and PDF formats
- Select different date range presets, try custom range
- Toggle columns on/off — verify export button disables when none selected
- Toggle privacy switches on/off
- Click export — observe the generating step with progress bar
- Verify the complete step shows download buttons and privacy summary
- Test keyboard navigation: Tab through all controls, Escape to close
- Test with screen reader active
- Resize to mobile viewport — verify responsive layout