--- 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 (ghost button: `--color-surface` background, `--color-surface-dim` hover — matches Surface A; not an accent CTA) - 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 `