Skip to content

UI: standardise look and feel with shared component library + styleguide #105

Description

@pskillen

Problem

The SPA has grown to many routes (home, summary, CRUD lists/detail/edit, import/export, reference tools, settings, map) but page chrome and primitives are inconsistent. Each route composes Mantine directly with ad hoc layout choices:

Area Today Pain
Page shell Container sizes vary (sm on Home, lg on most CRUD/report pages); some routes wrap content in ReportPage, others inline their own Container/Stack/Title
Section cards Import/export (/#/import-export) uses Paper withBorder panels in a SimpleGrid — the best-looking pattern today; most other pages are flat stacks with no visual grouping
Data tables EntityTable exists but is minimal (no shared empty state, toolbar, mobile column policy, or list-page header/actions); channel list adds optional columns locally; #68 tracks mobile collapse separately
Page headers Title + dimmed description repeated by hand; hierarchy (order={1} vs order={2}) not uniform
Navigation AppShell + dual AppNav / SectionNav works but header, primary nav, and section nav styles are not extracted as reusable primitives
Forms & actions Edit pages each lay out Stack/Group/buttons differently; no shared sticky action bar or form section pattern
Theme src/theme.ts sets brand colours only — no documented spacing, radius, or component defaults beyond Mantine defaults

Operators get a functional but uneven UI. Future pages (#92, #103, …) will copy whichever local pattern is nearest, compounding drift.

Intended outcome

Design, build, demo, and roll out a small internal UI kit under src/components/ui/ (name TBD) so every standard HTML/Mantine surface shares one look and feel. Deliver in one PR with atomic conventional commits per layer.

1. Design pass (look + feel)

  • Codify layout tokens on top of existing Mantine theme (theme.ts): page max-widths, section gap, card padding/radius, heading scale, dimmed helper text style.
  • Reference pattern: import/export screen (src/routes/ImportExport.tsx) — Container size="lg", page title + description, Paper withBorder section cards, SimpleGrid where side-by-side panels help.
  • Document decisions briefly in docs/features/ui/README.md (tier-1 feature doc — internal SPA conventions, not vendor wire).

2. Shared components (initial inventory)

Build thin wrappers — props-forwarding to Mantine where sensible, opinionated defaults where not:

Component Role
Page / PageLayout Standard outer shell: width, vertical padding, optional max-width variant (narrow / default / wide)
PageHeader Title order={1} + optional description + optional actions slot (right-aligned on desktop)
PageSection Bordered card (Paper) with optional title, description, and body — mirrors import/export panels
PageSectionGrid Responsive SimpleGrid for 1–2 column section layouts
DataTable Evolve or wrap EntityTable: consistent borders/striping, empty state copy, optional caption; hook point for #68 mobile column collapse later
ListPage Compose Page + PageHeader + toolbar slot + DataTable — standard list-route layout
FormPage Edit-route shell: header, form body, sticky/fixed Save/Cancel footer on mobile (feeds #69)
FormSection Grouped field block with subheading — accordion variant optional follow-up inside same PR if time
EmptyState Icon + message + optional CTA for zero-row tables and empty projects
AppHeader / NavLink primitives Extract repeated header + nav link styling from App.tsx / AppNav / SectionNav without changing behaviour

In scope: all standard HTML/Mantine primitives we use today — buttons, inputs, selects, modals (ConfirmDeleteModal), pills (ModePill, BandPill), alerts, dividers, etc. Either wrap or document the canonical variant in the styleguide.

Out of scope for v1: redesigning map UI, new features, or changing business logic.

3. Styleguide page (hidden route)

  • New route e.g. /#/styleguideno link in nav; reachable by URL only (for dev/design review).
  • Renders every kit component in realistic combinations: page shell, sections, tables (with/without rows), form fields, buttons (variants), pills, modals, empty states, nav samples.
  • Include light/dark if Mantine color scheme is toggled anywhere; otherwise default scheme only.
  • Gate behind nothing (public SPA) — acceptable because it demos static UI only; add a one-line comment in route registry that it is intentionally unlinked.

4. Verify + adopt across the app

After components exist and pass review on the styleguide:

  • Migrate all routes to the kit — priority order:
    1. Import/export (already closest — refactor into shared PageSection without visual regression)
    2. List pages: channels, zones, contacts, talk groups, RX group lists
    3. Detail pages using ReportPage / DetailSections
    4. Edit forms
    5. Home, Summary, Settings, reference routes
  • Deprecate or thin ReportPage → re-export Page/PageHeader if redundant.
  • Keep EntityTable as implementation detail behind DataTable or merge — one table abstraction only.

5. Tests

  • Smoke test: styleguide route renders without throw.
  • One test per critical primitive if behaviour is non-trivial (e.g. ListPage renders header + children).
  • Existing route tests (App.test.tsx, list tests) must still pass after migration.

Current reference files

  • Best page layout: src/routes/ImportExport.tsx
  • Page wrapper today: src/components/report/ReportPage.tsx
  • Tables: src/components/report/EntityTable.tsx (+ BandPlanTable.tsx for reference data)
  • Shell: src/App.tsx, src/components/AppNav/AppNav.tsx, src/components/SectionNav/
  • Theme: src/theme.ts

Affected

  • src/components/ui/ (new)
  • src/routes/styleguide.tsx (new) + route in App.tsx
  • src/theme.ts — extended defaults if needed
  • All routes under src/routes/ — adopt shared layout
  • src/components/report/ReportPage.tsx, EntityTable.tsx — merge or delegate
  • docs/features/ui/README.md + index in docs/features/README.md

Related issues

Out of scope

  • Brand redesign / custom illustration / marketing site.
  • Replacing Mantine with another UI library.
  • Map-specific controls (CodeplugMap, leaflet chrome).
  • Accessibility audit beyond Mantine defaults (follow-up welcome).

Manual verify

  1. Open /#/styleguide — every component renders; visually matches import/export quality.
  2. Spot-check migrated routes: Home, Import/export, Channels list, Channel edit, Settings, Summary — consistent page width, headers, and section cards.
  3. npm run lint && npm run test && npm run build
  4. Mobile viewport: list pages and form footers remain usable.
  5. No new nav link to styleguide; direct URL still works.

Workflow note

Single PR preferred. Branch from origin/main (e.g. {num}/paddy/ui-component-kit). Atomic conventional commits per slice — e.g. feat(ui): add Page and PageHeader, feat(ui): add styleguide route, refactor(routes): adopt Page on ImportExport, … — do not batch the whole migration into one commit at the end. Link PR with Closes #.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions