From 96193831c4f28262148a60b8a75cb42e5d8d6d3a Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 18 Jun 2026 16:50:31 -0400 Subject: [PATCH] docs(20): UI design contract --- .../20-UI-SPEC.md | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 .planning/phases/20-admin-member-editor-form-declutter/20-UI-SPEC.md diff --git a/.planning/phases/20-admin-member-editor-form-declutter/20-UI-SPEC.md b/.planning/phases/20-admin-member-editor-form-declutter/20-UI-SPEC.md new file mode 100644 index 0000000..8a9b2b3 --- /dev/null +++ b/.planning/phases/20-admin-member-editor-form-declutter/20-UI-SPEC.md @@ -0,0 +1,317 @@ +--- +phase: 20 +slug: admin-member-editor-form-declutter +status: draft +shadcn_initialized: false +preset: none +created: 2026-06-18 +--- + +# Phase 20 — UI Design Contract + +> Visual and interaction contract for the Admin Member Editor & Form Declutter phase. +> Generated by gsd-ui-researcher, verified by gsd-ui-checker. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none — custom CSS custom properties | +| Preset | not applicable | +| Component library | none — inline styles referencing CSS tokens | +| Icon library | lucide-react (existing: CheckCircle, AlertCircle, Loader2; add ChevronRight) | +| Font | system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif | + +No `components.json` detected. Project uses a hand-rolled design token system declared in +`apps/pwa/src/styles/tokens.css`. All new components must reference those CSS custom properties +(never hard-code hex/px). This is an established project convention — do not introduce shadcn or +any third-party component library in this phase. + +--- + +## Spacing Scale + +All tokens sourced from `apps/pwa/src/styles/tokens.css` (pre-populated — no change needed): + +| Token | Value | Usage | +|-------|-------|-------| +| `--space-1` | 4px | Icon gaps, label-to-input margin | +| `--space-2` | 8px | Badge icon gap, toast icon gap, button row gap | +| `--space-3` | 12px | Field gap inside editor sections, tab padding | +| `--space-4` | 16px | Default field padding, section heading margin-bottom | +| `--space-6` | 24px | Sheet padding, section gap between Profile / Password / App-password | +| `--space-8` | 32px | Section bottom margin in Members panel | +| `--space-12` | 48px | Page top/bottom padding | + +Exceptions: +- Member row minimum height: 44px (touch target floor — not a spacing token) +- Sheet border-radius: 12px phone bottom-sheet top corners / 12px desktop modal all corners (matches existing CredentialSheet / ResetPasswordSheet) +- MemberRow add-member trigger button bottom margin: `--space-4` below member list, above the trigger + +--- + +## Typography + +All values sourced from `tokens.css` (pre-populated — no change): + +| Role | Size | Weight | Line Height | Usage in this phase | +|------|------|--------|-------------|---------------------| +| Body | 15px (`--text-body-size`) | 400 (`--text-body-weight`) | 1.5 (`--text-body-line-height`) | Member display name in row; section description text; subtitle line under sheet heading | +| Label | 13px (`--text-label-size`) | 400 or 600 | 1.4 (`--text-label-line-height`) | Form field labels (600), credential status badges (400), toast copy (400), section MEMBERS heading (600 + uppercase) | +| Heading | 18px (`--text-heading-size`) | 600 (`--text-heading-weight`) | 1.25 (`--text-heading-line-height`) | Sheet h2 ("Edit member", "Add member"), editor section dividers | +| Display | 24px (`--text-display-size`) | 600 (`--text-display-weight`) | 1.2 (`--text-display-line-height`) | Not used in this phase | + +--- + +## Color + +All values sourced from `tokens.css` (pre-populated): + +| Role | Value | Usage | +|------|-------|-------| +| Dominant (60%) | `--color-surface` (#ffffff) | Page background, sheet background, input background | +| Secondary (30%) | `--color-surface-dim` (#f7f7f8) / `--color-border-subtle` (#eceef2) | MemberRow bottom border, section dividers, "Add member" trigger button border | +| Accent (10%) | `--color-member-0` (#e8915a) | Primary action buttons only (see reserved-for list below) | +| Destructive | `--color-destructive` (#dc2626) | Inline error text, error-state input border | + +Accent (`--color-member-0`) reserved for: +1. Primary save/submit button background (enabled state) in the Member editor — each section's "Save" button +2. Active tab underline indicator in the Members & Accounts / Settings tab strip +3. "Currently shared" label text in the calendar picker (existing pattern, unchanged) +4. Toast success icon (`CheckCircle`) +5. "Use detected:" timezone affordance link (existing pattern, unchanged) + +Accent is NOT used on: +- Cancel buttons (use `--color-text-secondary`, no background) +- Disabled buttons (use `--color-border` as background) +- The "Add member" trigger button (use `--color-member-0` background — it is the primary CTA for the panel) +- The row chevron/edit icon (use `--color-text-muted`) + +Member avatar swatches use `--color-member-{colorIndex}` (index-derived, unchanged from existing MemberRow). + +--- + +## Surface Inventory + +This phase introduces or reworks the following surfaces: + +### Surface A — Member list (Members tab, existing panel body) + +The current inline "Add member" form and the per-row action-button cluster are removed. +The panel body becomes: + +``` +[Section label: MEMBERS] +[MemberRow] × N ← whole-row tap opens editor sheet; trailing ChevronRight icon +[Divider] +[+ Add member button] ← single trigger at bottom; opens MemberEditorSheet in create mode +``` + +MemberRow (reworked): +- Layout: avatar swatch (32px circle) → name + status badge → flex spacer → ChevronRight icon (16px, `--color-text-muted`) +- The entire row is the tap/click target (min-height 44px, `cursor: pointer`) +- `role="button"` on the row div, `aria-label="Edit {displayName}"`, `tabIndex={0}`, keyboard: Enter/Space opens editor +- Trailing admin badge: when `member.isAdmin === true`, show a small inline badge — label "Admin", 12px/600, `--color-member-0` text on `--color-surface-dim` background, border-radius 4px, padding 2px 6px — placed between the status badge and the chevron +- Remove: "Rotate" button, "Add credential" button, "Reset password" button — all removed from MemberRow + +"Add member" trigger button: +- Full-width or left-aligned ghost button with `+` icon prefix (use lucide `Plus`, 16px) +- Label: "Add member" +- Border: `1px solid var(--color-border)`, border-radius 8px, padding `--space-3` vertical / `--space-4` horizontal +- Background: `--color-surface`; hover/focus: `--color-surface-dim` +- Min-height: 44px +- Opens MemberEditorSheet in create mode + +### Surface B — Member editor sheet (MemberEditorSheet, new component) + +Single sheet component with `mode` prop: `'edit'` (id present) or `'create'` (no id). + +Sheet chrome (identical to CredentialSheet / ResetPasswordSheet): +- Phone: fixed bottom, `border-radius 12px 12px 0 0`, `box-shadow: 0 -4px 24px rgba(0,0,0,0.15)`, `padding: var(--space-6)`, zIndex 301 +- Desktop: centered modal, `max-width 480px`, `border-radius 12px`, `box-shadow: 0 8px 32px rgba(0,0,0,0.18)`, `padding: var(--space-6)`, zIndex 301 +- Backdrop: `rgba(0,0,0,0.32)` (`--color-overlay`), zIndex 300, click closes +- Escape closes; focus returns to trigger row on close +- `role="dialog"`, `aria-modal="true"`, `aria-label` = sheet heading +- `useFocusTrap` on dialog div +- On open: focus the h2 heading (`tabIndex={-1}`) +- Desktop: `max-height: calc(100dvh - var(--space-8))`, `overflow-y: auto` + +Sheet heading: +- `h2`, 18px/600/1.25, `--color-text-primary` +- Edit mode: "Edit member" +- Create mode: "Add member" + +Member subtitle (edit mode only): +- 15px/400, `--color-text-secondary`, `margin-bottom: var(--space-6)` +- Displays `member.displayName` + +**Edit mode — three collapsible sections rendered as labeled card blocks:** + +Section divider style (between sections): +- `border-top: 1px solid var(--color-border-subtle)`, `margin: var(--space-6) 0` + +**Section 1 — Profile** (always shown in edit mode): +- Section heading: "Profile", 13px/600/uppercase/letterspacing 0.06em, `--color-text-muted` +- Field: "Display name" — text input, 15px, min-height 44px, border `--color-border`, border-radius 4px, padding `--space-3 --space-4` +- Field: "Admin" — toggle/checkbox row: + - Label: "Admin", 15px/400, `--color-text-primary` + - Sub-label: "Can access admin settings", 13px/400, `--color-text-secondary` + - Control: native `` styled as a toggle (or a `