--- 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>>` 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 (Mon–Sun 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