116 lines
5.0 KiB
Markdown
116 lines
5.0 KiB
Markdown
---
|
||
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 (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
|