From 1324a0c8cecf6b5d153835f3226c345b21e10182 Mon Sep 17 00:00:00 2001 From: Knacky Date: Sun, 21 Jun 2026 22:11:38 +0200 Subject: [PATCH] docs(design): add tabs / inline banners / compact-table-density to DESIGN.md Adds: - Navigation > Sub-page tabs subsection (tab-underline recipes, count pill, ARIA) - Data Tables: row min-height 44px -> 32px + WCAG SC 2.5.5 rationale - Toast Notifications > Inline Banners subsection (4 variants table) Also adds Sprint 11 entry to CHANGELOG.md. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ DESIGN.md | 31 ++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554bf64..d27197d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## [Unreleased] +### Added — Sprint 11 (Spectrum UX port: 4 primitives + compact density global) + +**Frontend only** (233 vitest passing — baseline 212 + 21 new tests across 4 new specs) + +- `frontend/src/hooks/useHashTab.ts` — NEW. Pure-TS hook that reads `window.location.hash`, falls back to a default tab id, and listens to `hashchange` (cleanup on unmount). No style dependency. +- `frontend/src/components/Tabs.tsx` — NEW. `` component. Underline variant: `tab-underline` / `tab-underline-active` CSS recipes. Count pill: `tab-count-pill` / `tab-count-pill-active` (`rounded-pill` exception per DESIGN.md). `role="tablist"` + `aria-selected` on tabs. Zero transitions. +- `frontend/src/components/AlertBanner.tsx` — NEW. ``. Border-l-4 semantic strip, `bg-paper`, Lucide icon at `size={16}`. ARIA: `role="alert"` for error/warn, `role="status"` for success/info. No shadow, no radius, no transition. +- `frontend/src/components/BackLink.tsx` — NEW. `` renders `ArrowLeft` (Lucide, 14px) + text with `caption-md text-graphite hover:text-primary`. Replaces 3 hand-rolled back-link instances. +- `frontend/src/styles/index.css` — Added recipes: `.tab-underline`, `.tab-underline-active`, `.tab-count-pill`, `.tab-count-pill-active`, `.alert-error`, `.alert-warn`, `.alert-success`, `.alert-info`, `.table-compact` (32px row density, global). +- `frontend/src/pages/EngagementDetailPage.tsx` — Refactored to 3-tab layout (Schedule / Description / Simulations) wired via `useHashTab('schedule')`. Count pill on Simulations tab. BackLink replaces hand-rolled link. +- `frontend/src/pages/SimulationFormPage.tsx` — Done banner and SOC-blocked banner migrated to ``. Both back-link instances replaced with ``. +- `frontend/src/pages/TemplateFormPage.tsx` — Back-link replaced with ``. +- `frontend/src/pages/EngagementsListPage.tsx` — Table marked `table-compact`, inline `px-xl py-md` padding removed from th/td (recipe handles it). +- `frontend/src/pages/TemplatesListPage.tsx` — Same compact table treatment. +- `frontend/src/pages/UsersAdminPage.tsx` — Same compact table treatment. +- `frontend/src/components/SimulationList.tsx` — Same compact table treatment. +- `DESIGN.md` — Added `### Navigation › Sub-page tabs` subsection; updated `### Data Tables` row min-height 44px → 32px with WCAG SC 2.5.5 rationale; added `### Inline Banners` subsection under `### Toast Notifications`. + +**Tests added** (21 new assertions across 4 new spec files): +- `tests/hooks/useHashTab.test.tsx` — 4 tests (default, hash read on mount, navigate(), empty-hash fallback) +- `tests/components/Tabs.test.tsx` — 7 tests (render, aria-selected, active class, count pill, count-pill-active, onChange, brutalism) +- `tests/components/AlertBanner.test.tsx` — 6 tests (4 variants × role + class, title prop, brutalism) +- `tests/components/BackLink.test.tsx` — 4 tests (render, href, brutalism, svg icon) + +**No backend changes.** No DB schema change. No migration. + ### Changed — Sprint 7 (Terminal-SOC design refresh) **Frontend** (136 vitest passing — unchanged count, 3 assertions updated for new token names) diff --git a/DESIGN.md b/DESIGN.md index a4cd1d0..bb99a46 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -216,11 +216,23 @@ Shadows (`box-shadow`) are not used anywhere. - Text `{colors.slab-text}`, caption-md, `rounded-none` - Active state: 2px primary bottom border +#### Sub-page tabs + +Used inside pages that need content partitioning without a URL change (e.g. EngagementDetailPage: Schedule / Description / Simulations). + +- **Container**: `flex items-end border-b border-hairline gap-xs` — sits on the `{colors.hairline}` bottom line. +- **`.tab-underline`**: `text-graphite caption-bold cursor-pointer border-b-2 border-transparent hover:text-ink px-xs`. Hover is **instant** — no `transition-*`. +- **`.tab-underline-active`**: `text-primary border-primary` — 2px primary underline, primary text. +- **Count pill** (optional, e.g. simulation count): `.tab-count-pill` — `rounded-pill bg-cloud text-graphite text-[11px] px-xs py-0 font-mono`. `rounded-pill` is the documented exception. +- **Active count pill**: `.tab-count-pill-active` — `bg-primary-soft text-primary`. +- ARIA: `role="tablist"` on container; `role="tab"` + `aria-selected` on each button. + ### Data Tables - `table-layout: fixed`, `word-break: break-word` - Header row: background `{colors.cloud}`, `body-emphasis` text, 1px `{colors.hairline}` bottom border -- Data cells: 44px min-height, 1px `{colors.hairline}` bottom border +- Data cells: **32px min-height** (compact density, all surfaces). Class: `.table-compact`. + - **WCAG SC 2.5.5 tradeoff**: the recommended 44×44px touch target is intentionally not met on data rows. Mimic is a BAS operator console used at desktop on mouse/keyboard — density and scanability take priority over touch target size. Action buttons within rows retain icon ≥ 24px with adequate padding to preserve a usable hit area. - ID / date / technique columns: `font-mono` - Zebra striping optional — use `{colors.cloud}` for odd rows if table is wide @@ -229,6 +241,23 @@ Shadows (`box-shadow`) are not used anywhere. - Angular (`rounded-none`), 4px left border strip in semantic color (success/warn/bloom-deep/primary) - Background `{colors.paper}`, 1px `{colors.hairline}` border +#### Inline Banners + +For persistent in-page status messages (not ephemeral toasts). Use `` component. + +Four variants — all share: `bg-paper border border-hairline border-l-4 rounded-none px-md py-sm flex items-start gap-sm`. + +| Variant | Left strip color | ARIA role | Lucide icon | +|---------|-----------------|-----------|-------------| +| `error` | `border-l-bloom-deep` | `role="alert"` | `AlertCircle` | +| `warn` | `border-l-warn` | `role="alert"` | `AlertTriangle` | +| `success` | `border-l-success` | `role="status"` | `CheckCircle` | +| `info` | `border-l-primary` | `role="status"` | `Info` | + +- Icon: Lucide at `size={16}`, `aria-hidden`, `mt-[2px] shrink-0` for baseline alignment. +- No shadow, no rounded corners, no transition. +- Optional `title` prop renders as `font-medium text-ink` above the message body. + --- ## Do's and Don'ts