Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

07 — Component Library (Settings Module)

Module-specific components built from shared App* primitives (00-shared/03). Props, states, animation, a11y, tokens, Flutter mapping. Reuse over new — only components that don't exist in the shared library are defined here.


A. Setting row & list artifacts

AppSettingRow

FieldSpec
PurposeOne setting in the list (data-driven — renders whatever GET /settings returns)
Propskey, value (any), group, dirty, editing, onTap, onMenu, saving
Type chipderived from runtime value: str / num / bool / json (AppBadge)
RenderingAppCard flat (e-0) + AppListTile-style: leading group icon, title mono key, subtitle truncated value preview, trailing type chip + AppMenu
Dirtyprimary left indicator dot + primaryContainer tint, announced via semantics "unsaved"
Savingtrailing CircularProgressIndicator 16 dp replaces chip while PUT in flight
a11ysingle Semantics(button, label: "<key>, value <preview>, <type>")
FlutterCard + InkWell + Row; icons from group map

AppGroupChips

  • Filter chips for the 6 enum groups + "All" (setting.schema.ts:7-14); server-refetch per chip (GET /settings?group=, settings.controller.ts:26-28).
  • Selected chip secondaryContainer; horizontal scroll on phone (SingleChildScrollView).
  • a11y: Semantics(toggled:) per chip; group order = enum declaration order.

AppSettingsSectionHeader

  • AppSectionHeader variant: group name + count from the fetched array ("Attendance (12)"), titleMedium, sticky on desktop pane.

B. Typed value editors

AppTypedValueEditor (dispatcher)

  • Props: value (any), onChanged, onSave, saving, compact.
  • Dispatches by runtime type → AppTextField (string), numeric field (number), AppSwitch row (boolean), AppJsonEditor (object/array/unknown). Compact mode = single line for list rows; expanded = detail pane.

AppJsonEditor

FieldSpec
PurposeEdit object/array/null values with live parse feedback (server stores raw JSON — setting.schema.ts:21-22)
Propsvalue, onChanged, onSave, minLines (6), expands
Renderingmulti-line TextFormField mono 14, no syntax highlight (keep dependency-free), grow with content
Validationlive jsonDecode; valid → primary check icon + Save enabled; invalid → error outline + inline "Not valid JSON" (bodySmall) + Save disabled
SaveCtrl/Cmd+Enter (desktop) / Save button; pretty-prints (JsonEncoder.withIndent(' ')) on success
a11ySemantics(error) on parse failure, announced live; label "JSON value"
FlutterTextFormField(maxLines: null, keyboardType: TextInputType.multiline)

AppBoolRow

  • AppSwitch in a ListTile; instant-save on toggle (optimistic, rollback on error — 00-shared/06 §3.5); saving disables switch; subtitle shows last save status.

C. Save & feedback

AppSaveBar (batch)

  • Bottom bar in selection mode: "Save all (N)" FilledButton full width + "Cancel" TextButton; counts only dirty rows; on save → per-row status (✓ saved / ✗ failed, tap row retries).
  • a11y: bar announced; result via snackbar "Saved 4 of 5 settings".

AppSettingNotFound

  • AppEmptyState variant for GET /settings/:key 404 (settings.service.ts:20): icon tune, "Setting not found", back button.

D. Cross-surface teaser

  • Dismissible AppBanner(info) pointing at /organization (embedded settings surface); persisted dismissal key org_settings_link_dismissed (shared_preferences).

E. Flutter widget map (module)

ComponentFlutter widgets
AppSettingRowCard + InkWell + ListTile + MenuAnchor
AppGroupChipsFilterChip row in SingleChildScrollView
AppSettingsSectionHeaderPadding + Row + Text
AppTypedValueEditorswitch on runtime type → fields below
AppJsonEditorTextFormField + jsonDecode validator
AppBoolRowSwitchListTile
AppSaveBarMaterial + SafeArea + FilledButton
AppSettingNotFoundAppEmptyState
AppOrgSettingsLinkMaterialBanner

F. Golden tests (base 00-shared/03 G)

Every module component golden at phone/tablet/desktop + dark; AppJsonEditor valid/invalid states; AppSettingRow clean/dirty/saving; AppSaveBar 0/1/N dirty.