Expense Type Picker Widget
Component Detail
Description
A stateful multi-select widget presenting the four fixed expense categories (kilometers driven, tolls, parking, public transit) as discrete tappable option cards. Reactively disables incompatible options when a selection is made, providing immediate visual feedback on mutual exclusion constraints.
expense-type-picker-widget
Summaries
The Expense Type Picker Widget is the primary decision point in the expense submission flow, directly affecting how quickly and correctly peer mentors can claim reimbursement. By presenting the four expense categories as clear, tappable option cards and automatically disabling incompatible combinations with plain-language explanations, the widget eliminates a common source of submission errors — claiming both mileage and public transit for the same trip leg. Fewer errors mean fewer rejected claims, less administrative back-and-forth, and faster reimbursement cycles. This translates directly into improved peer mentor satisfaction and retention, which is critical to the organization's service delivery model.
This is a medium-complexity UI component with two direct dependencies: the Expense Type Option Card sub-component and the Expense Selection BLoC. Both must be completed before this widget can be integrated and tested end-to-end. The mutual exclusion logic sits in the BLoC, so UI work can proceed in parallel with BLoC development using mock state. Accessibility is a delivery requirement — the disabled reason tooltip and screen reader semantics must be tested with TalkBack and VoiceOver, which requires physical device testing time.
The Mutual Exclusion Hint Banner sub-component introduces animation, which should be scoped conservatively and tested across older Android/iOS devices to avoid frame drops. Plan for a dedicated accessibility review pass before release.
ExpenseTypePickerWidget is a StatefulWidget that delegates selection state entirely to the Expense Selection BLoC via a ValueChanged
The MutualExclusionHintBanner is conditionally rendered below the grid based on whether any disabledTypes are non-empty, with an AnimatedSwitcher for smooth appearance. Use Semantics widgets on each card to expose the disabled reason as a semantic label. BLoC integration should use flutter_bloc's BlocBuilder or Riverpod's ConsumerWidget pattern for reactive rebuilds on state change.
Responsibilities
- Render four fixed expense type option cards
- Reactively disable incompatible options on selection change
- Display selected/deselected/disabled states with accessible contrast
- Emit selection change events to BLoC/Riverpod state
Interfaces
ExpenseTypePickerWidget({required List<ExpenseType> selectedTypes, required ValueChanged<ExpenseType> onToggle})
build(BuildContext context)
_isDisabled(ExpenseType type)
_onOptionTap(ExpenseType type)
Relationships
Sub-Components (2)
Individual selectable card representing a single expense category with icon, label, and visual state (selected, unselected, disabled). Communicates disabled reason via tooltip for accessibility.
- Display expense type icon and label
- Render selected, unselected, and disabled visual states
- Provide accessible semantics and disabled reason for screen readers
- +1 more
Inline contextual hint that appears below the picker when a selection causes other options to be disabled. Explains in plain language why certain combinations are not allowed (e.g., 'Mileage reimbursement and public transit cannot be combined for the same trip leg').
- Show contextual explanation when mutual exclusion is triggered
- Hide when no exclusions are active
- Animate in/out smoothly on state change
Related Data Entities (1)
Data entities managed by this component