Skip to content

Latest commit

Β 

History

History
276 lines (204 loc) Β· 10.3 KB

File metadata and controls

276 lines (204 loc) Β· 10.3 KB

Filter Chips Implementation for Transaction History

Overview

This document describes the implementation of accessible, discoverable filter chips for the Transaction History page (#132), including type, date-range, and amount-range filtering with distinct empty/no-results UX states.

Features Implemented

1. Accessible Filter Chips with Toggle States

Transaction Type Filter Chips

  • Options: All, Draw, Repay, Fee, Interest (excludes StatusChange type)
  • Implementation: Toggle button group with role="group" and aria-labelledby
  • Accessibility:
    • aria-pressed="true/false" indicates current toggle state
    • Keyboard navigation: Tab to focus, Space/Enter to activate
    • Visual feedback: Active state shows accent color with shadow
    • Works with any screen reader via ARIA attributes

Date Range Filter Chips

  • Presets: Today, 7d, 30d, 90d, Custom
  • Implementation: Same toggle button group pattern as type filters
  • Efficiency: Presets provide quick access to common time ranges
  • Accessibility: Identical to type filters (role, aria-pressed, keyboard support)

Amount Range Filter Chips

  • Quick chips: All amounts, Under $5k, $5k-$25k, $25k+
  • Custom flow: Separate mobile-friendly modal for fine-grained min/max filtering
  • Implementation: Reuses the transaction filter chip styling and project modal accessibility hooks
  • Accessibility:
    • Quick chips expose aria-pressed state like the other filter groups
    • Custom trigger exposes aria-haspopup="dialog" and aria-pressed
    • Dialog traps focus, locks background scrolling, and makes background content inert
    • All controls meet the 44px touch-target requirement and keep a visible focus ring

2. Live Result Count Announcements

The result count is displayed in an aria-live="polite" region that:

  • Updates in real-time as filters change
  • Uses aria-atomic="true" to ensure full announcement
  • Shows proper pluralization: "1 transaction shown" vs "3 transactions shown"
  • Announces to screen readers without interrupting user workflow

Example announcements:

  • "28 transactions shown" (initial state)
  • "3 transactions shown" (when 7d filter applied)
  • "0 transactions shown" (when filters produce no results)

3. Distinct Empty States

Three clear, separate UX patterns for data absence:

State 1: No Credit Lines

  • Condition: !hasLines
  • Message: "No credit lines yet"
  • Action: Link to "Request Credit Evaluation"
  • Icon: πŸ“Š
  • Use case: First-time users with no credit applications

State 2: No Transactions

  • Condition: !hasTransactions
  • Message: "No transactions yet"
  • Description: Explains transactions will appear after activity
  • Icon: πŸ“Š
  • Use case: Users with credit lines but no transaction history

State 3: No Filtered Results

  • Condition: filteredTransactions.length === 0
  • Message: "No transactions match these filters"
  • Description: Suggests modifying filters
  • Action: "Clear filters" button (only when filters are active)
  • Icon: πŸ”
  • Use case: Active filters return zero matches

Distinction Logic:

const hasActiveFilters =
  selectedLine !== "all" ||
  selectedType !== "all" ||
  selectedStatus !== "all" ||
  dateRange !== "all" ||
  searchQuery.trim().length > 0;

// "Clear filters" button only shows when:
// 1. No results exist (filteredTransactions.length === 0)
// 2. AND at least one filter is active (hasActiveFilters === true)

4. Filter Application Logic

Filters are applied in cascading order:

  1. Credit Line Filter - Select by lineId
  2. Transaction Type Filter - Filter by type (Draw, Repay, Fee, Interest, StatusChange)
  3. Status Filter - Filter by transaction status (Completed, Pending, Failed)
  4. Amount Range Filter - Filter by quick amount chips or custom min/max bounds
  5. Date Range Filter - Calculate cutoff time from preset days (Today, 7d, 30d, 90d) or custom dates
  6. Search Query - Full-text search across note, lineName, lineId, txHash

Each filter change:

  • Resets pagination to page 1 (prevents disorientation)
  • Triggers result count announcement
  • Updates aria-live region

5. Keyboard Accessibility

Key Action
Tab Navigate between filter chips and other controls
Shift+Tab Navigate backwards
Space Toggle the focused filter chip
Enter Toggle the focused filter chip (alternative to Space)
Arrow Keys Not implemented (follows WAI-ARIA button group pattern)

6. WCAG 2.1 AA Compliance

Criterion 4.1.2 - Name, Role, Value

  • βœ… All filter chips have proper roles (button)
  • βœ… aria-pressed states clearly identify toggle values
  • βœ… Accessible names provided via button labels
  • βœ… Screen readers announce changes via aria-live region

Criterion 2.4.3 - Focus Order

  • βœ… Chips are in logical focus order (left to right, top to bottom)
  • βœ… Focus is visible with :focus-visible outline
  • βœ… Focus management resets to top of page on state change

Criterion 3.2.4 - Consistent Identification

  • βœ… Filter chips use consistent visual language
  • βœ… Active/inactive states are consistent across both chip groups
  • βœ… Styling follows design tokens (accent color, spacing)

Criterion 2.1.1 - Keyboard

  • βœ… All filter functionality accessible via keyboard
  • βœ… No keyboard traps
  • βœ… Tab key properly navigates through chips

Implementation Details

Component Structure

// Main component with all state management
export function TransactionHistory()

// Sub-component for individual transaction rows
function TransactionRow()

// Constants
- TX_TYPE_LABELS: Record of type -> display label
- TX_TYPE_ICONS: Record of type -> emoji icon
- TX_TYPE_COLORS: Record of type -> hex color
- TYPE_FILTER_OPTIONS: Array of filter chip options
- DATE_FILTER_OPTIONS: Array with day counts for calculations

State Management

// Filter states - each change resets pagination
const [selectedType, setSelectedType] = useState<TypeFilter>("all");
const [dateRange, setDateRange] = useState<DateFilter>("all");
const [selectedLine, setSelectedLine] = useState<string>("all");
const [selectedStatus, setSelectedStatus] = useState<string>("all");
const [searchQuery, setSearchQuery] = useState("");

// UI states
const [expandedTx, setExpandedTx] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [showExportMenu, setShowExportMenu] = useState(false);

Memoized Computations

  • allTransactions - Combined and sorted transactions from all credit lines
  • filteredTransactions - Result of applying all active filters
  • paginatedTransactions - Sliced portion for current page
  • paginatedGrouped - Transactions grouped by date (Today/Yesterday/This Week/etc)
  • stats - Summary statistics (Total Drawn, Repaid, Interest, Debt)

Each memoization has explicit dependencies to ensure re-calculation only when needed.

Testing

All functionality is covered by vitest tests:

βœ“ renders type, date, and amount filter chips as labeled pressed toggle groups
βœ“ updates the polite result count when quick amount chips change
βœ“ shows a no-results state with a clear filters action
βœ“ applies a custom amount range from the modal

Run tests:

npm run test -- src/pages/TransactionHistory.test.tsx

CSS Classes

Key classes for styling filter chips:

Class Purpose
.th-filter-chip Base chip styling
.th-filter-chip[aria-pressed="true"] Active chip appearance
.th-filter-chip:focus-visible Keyboard focus indicator
.th-chip-group Container for chip groups
.amount-range-custom-trigger Custom range trigger button
.amount-range-modal Custom amount dialog shell
.th-empty-no-results No-results state styling
.th-clear-filters-btn Clear filters action button
.th-filter-results Result count announcement region

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • Mobile browsers (iOS Safari, Chrome Mobile)

All modern browsers with support for:

  • CSS Grid & Flexbox
  • CSS Custom Properties (CSS Variables)
  • ARIA attributes
  • ES2020 features (optional chaining, nullish coalescing)

Future Enhancements

  1. Saved Filter Presets - Save common filter combinations
  2. Advanced Filters - Status duration, saved combinations, and additional numeric facets
  3. Filter Pills - Show active filters as removable pills
  4. Export Filtered Results - Export only filtered transactions
  5. Mobile Touch Optimization - Larger touch targets, swipe gestures
  6. Dark Mode - High contrast filter chips in dark mode
  7. Localization - Multi-language filter labels and messages

References

Commit History

feat: add accessible filter chips and no-results state to transactions

- Implement toggleable filter chips for transaction type (All/Draw/Repay/Fee/Interest)
- Implement date range chips with quick presets (7d/30d/90d/All)
- Add aria-pressed and role="group" for accessible toggle button groups
- Add aria-live="polite" region for result count announcements
- Implement distinct empty states: no-lines, no-transactions, no-filtered-results
- Add "Clear filters" action button in no-results state
- Filter changes reset pagination to page 1
- All changes include comprehensive inline documentation
- Update tests to verify chip functionality and empty state behavior
- All tests passing, WCAG 2.1 AA compliant