Files
Krow-workspace/.claude/agent-memory/ui-ux-design/component-patterns.md

116 lines
5.0 KiB
Markdown
Raw 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.
---
name: KROW Staff App Component Patterns
description: Established UI patterns, widget conventions, and design decisions confirmed in the KROW staff app codebase
type: project
---
## Card Pattern (standard surface)
Cards use:
- `UiColors.cardViewBackground` (white) background
- `Border.all(color: UiColors.border)` outline
- `BorderRadius.circular(UiConstants.radiusBase)` = 12dp
- `EdgeInsets.all(UiConstants.space4)` = 16dp padding
Do NOT use `UiColors.bgSecondary` as card background — that is for toggles/headers inside cards.
## Section Toggle / Expand-Collapse Header
Used for collapsible sections inside cards:
- Background: `UiColors.bgSecondary`
- Radius: `UiConstants.radiusMd` (6dp)
- Height: minimum 48dp (touch target)
- Label: `UiTypography.titleUppercase3m.textSecondary` for ALL-CAPS labels
- Trailing: `UiIcons.chevronDown` animated 180° via `AnimatedRotation`, 200ms
- Ripple: `InkWell` with `borderRadius: UiConstants.radiusMd` and splash `UiColors.primary.withValues(alpha: 0.06)`
## Shimmer Loading Pattern
Use `UiShimmer` wrapper + `UiShimmerLine` / `UiShimmerBox` / `UiShimmerCircle` primitives.
- Base color: `UiColors.muted`
- Highlight: `UiColors.background`
- For list content: 3 shimmer rows by default
- Do NOT use fixed height containers for shimmer — let content flow
## Status Badge (read-only, non-interactive)
Custom `Container` with pill shape:
- `borderRadius: UiConstants.radiusFull`
- `padding: EdgeInsets.symmetric(horizontal: space2, vertical: 2)`
- Label style: `UiTypography.footnote2b`
- Do NOT use the interactive `UiChip` widget for read-only display
Status color mapping:
- ACTIVE: bg=`tagActive`, fg=`textSuccess`
- PENDING: bg=`tagPending`, fg=`textWarning`
- INACTIVE/ENDED: bg=`tagFreeze`, fg=`textSecondary`
- ERROR: bg=`tagError`, fg=`textError`
## Inline Error Banner (inside card)
NOT a full-page error — a compact container inside the widget:
- bg: `UiColors.tagError`
- radius: `UiConstants.radiusMd`
- Icon: `UiIcons.error` at `iconMd` (20dp), color: `UiColors.destructive`
- Title: `body2m.textError`
- Retry link: `body3r.primary` with `TextDecoration.underline`
## Inline Empty State (inside card)
NOT `UiEmptyState` widget (that is full-page). Use compact inline version:
- `Icon(UiIcons.clock, size: iconXl=32, color: UiColors.iconDisabled)`
- `body2r.textSecondary` label
- `EdgeInsets.symmetric(vertical: space6)` padding
## AnimatedSize for Expand/Collapse
```dart
AnimatedSize(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: isExpanded ? content : const SizedBox.shrink(),
)
```
## Benefits Feature Structure
Legacy benefits: `apps/mobile/legacy/legacy-staff-app/lib/features/profile/benefits/`
V2 domain entity: `apps/mobile/packages/domain/lib/src/entities/benefits/benefit.dart`
V2 history entity: needs creation at `packages/domain/lib/src/entities/benefits/benefit_history.dart`
Benefit history is lazy-loaded per card (not with the initial overview fetch).
History state is cached in BLoC as `Map<String, AsyncValue<List<BenefitHistory>>>` keyed by benefitId.
## Screen Page Pattern (overview pages)
Uses `CustomScrollView` with `SliverList` for header + `SliverPadding` wrapping `SliverList.separated` for content.
Bottom padding on content sliver: `EdgeInsets.fromLTRB(16, 16, 16, 120)` to clear bottom nav bar.
## ShiftDateTimeSection / OrderScheduleSection — Shift Detail Section Pattern
Both widgets live in `packages/features/staff/shifts/lib/src/presentation/widgets/`:
- `shift_details/shift_date_time_section.dart` — single date, clock-in/clock-out boxes
- `order_details/order_schedule_section.dart` — date range, 7-day circle row, clock-in/clock-out boxes
**Shared conventions (non-negotiable for section consistency):**
- Outer padding: `EdgeInsets.all(UiConstants.space5)` — 20dp all sides
- Section title: `UiTypography.titleUppercase4b.textSecondary`
- Title → content gap: `UiConstants.space2` (8dp)
- Time boxes: `UiColors.bgThird` background, `UiConstants.radiusBase` (12dp) corners, `UiConstants.space3` (12dp) all padding
- Time box label: `UiTypography.footnote2b.copyWith(color: UiColors.textSecondary, letterSpacing: 0.5)`
- Time box value: `UiTypography.title1m.copyWith(fontWeight: FontWeight.w700).textPrimary`
- Between time boxes: `UiConstants.space4` (16dp) gap
- Date → time boxes gap: `UiConstants.space6` (24dp)
- Time format: `DateFormat('h:mm a')` — uppercase AM/PM with space
**OrderScheduleSection day-of-week circles:**
- 7 circles always shown (MonSun ISO order) regardless of active days
- Circle size: 32×32dp (fixed, not a token)
- Active: bg=`UiColors.primary`, text=`UiColors.white`, style=`footnote2m`
- Inactive: bg=`UiColors.bgThird`, text=`UiColors.textSecondary`, style=`footnote2m`
- Shape: `UiConstants.radiusFull`
- Single-char labels: M T W T F S S
- Inter-circle gap: `UiConstants.space2` (8dp)
- Accessibility: wrap row with `Semantics(label: "Repeats on ...")`, mark individual circles with `ExcludeSemantics`
- Ordering constant: `[DayOfWeek.mon, .tue, .wed, .thu, .fri, .sat, .sun]` — do NOT derive from API list order