Contributor docs for shared UI conventions in the SPA.
| Area | Status | Notes |
|---|---|---|
Icon library (@tabler/icons-react) |
Shipped | #64 |
| Shared size constants | Shipped | src/lib/iconSizes.ts |
| Display conventions | Shipped | display-conventions.md |
| Two-section navigation | Shipped | #81 — AppNav, SectionNav, src/nav/ |
| Layout & component kit | Shipped | #105 — src/components/ui/, /#/styleguide |
| Standardised datatables | Shipped | #138 — sort, sticky header, toolbar |
| List table persistence | Shipped | #146 — filters, sort, columns in localStorage |
| CRUD actions | Shipped | List/detail/edit routes, ConfirmDeleteModal, ZoneMemberPicker |
| Import/export/workflows | Shipped | ImportDropzone, Export, SummaryCard, ProjectList |
| Map/location | Shipped | MapControls, UseMyLocationButton |
| Doc | Purpose |
|---|---|
| icons-progress.md | Icons rollout log |
| icons-outstanding.md | Icons debt |
| nav-progress.md | Two-section nav execution log (#81) |
| nav-outstanding.md | Nav debt discovered during #81 |
| component-kit-progress.md | Component kit execution log (#105) |
| component-kit-outstanding.md | Kit debt discovered during #105 |
| datatable-progress.md | DataTable depth pass (#138) |
| datatable-outstanding.md | DataTable debt discovered during #138 |
| list-prefs-progress.md | List table persistence log (#146) |
| list-prefs-outstanding.md | List prefs debt discovered during #146 |
| display-conventions.md | Icons, badges, nav layout |
Shared page chrome lives in src/components/ui/. Reference layout: import/export (src/routes/ImportExport.tsx) — page title + dimmed description, bordered section cards in a responsive grid.
Defined in tokens.ts and applied via Page:
| Variant | Mantine Container size |
Typical routes |
|---|---|---|
narrow |
sm |
Home, Settings |
default |
lg |
CRUD, import/export, report |
wide |
xl |
Reserved for data-heavy reference pages |
| Token | Value | Use |
|---|---|---|
PAGE_STACK_GAP |
lg |
Between major page blocks |
PAGE_HEADER_GAP |
xs |
Title + description |
PAGE_SECTION_GAP |
md |
Inside bordered section cards |
| Section card | Paper withBorder, p="md", radius="md" |
Grouped settings, import/export panels |
Hidden dev route (no nav link): /#/styleguide — demos every kit primitive.
| Component | Role |
|---|---|
Page |
Outer shell — width, vertical padding |
PageHeader |
Title order={1} + description + optional actions |
PageSection |
Bordered card with optional title/description |
PageSectionGrid |
Responsive 1–2 column section layout |
ListPage |
List-route shell |
FormPage |
Edit-route shell with sticky mobile footer |
FormSection |
Titled field group |
DataTable |
Entity list table — sort, sticky header, toolbar, optional selection |
EmptyState |
Zero-row / empty project placeholder |
List datatable contract (#138)
DataTable sidecar: DataTable.md.
| Variant | Toolbar | Sort | Typical routes |
|---|---|---|---|
list |
Search (?q= via useListNameQuery on simple lists), optional-column picker (localStorage key per list), result count |
Click column headers; channels also sync name/distance with URL sort |
Entity list routes |
embedded |
None | Header sort only | Detail-page member/usage tables |
Channels: band/mode/duplex/distance filters stay in ChannelsListSectionNav; optional columns use per-project mm9pdy-codeplug-tool.list.channels.{projectId}.columns via columnVisibilityStorageKey.
Simple lists: name search moved from section nav into the table toolbar; EntityListSectionNav keeps only the New action.
List table state (#146)
Entity list filters, column sort, and channel optional columns persist in browser localStorage, scoped per active project (mm9pdy-codeplug-tool.list.<entity>.<projectId>).
| State | Storage | Notes |
|---|---|---|
| Name search, channel filters | URL + localStorage write-through |
useChannelListQuery, useListNameQuery(entity) |
| Column header sort | localStorage only |
usePersistedEntityListSort, usePersistedChannelColumnSort |
| Channel optional columns | Per-project localStorage |
columnVisibilityStorageKey + columnVisibilityLoad |
Restore: when a list route loads with no relevant URL params, hooks hydrate the URL from stored prefs for that project.
URL wins: landing with query params (bookmark/shared link) uses URL values for that visit; subsequent edits update storage.
Code: src/lib/listPrefs/, useChannelListQuery, useListNameQuery.
┌──────────────┬─────────────────┬──────────────────────────┐
│ AppNav │ SectionNav │ Routes (main content) │
│ (primary) │ (secondary) │ │
└──────────────┴─────────────────┴──────────────────────────┘
| Piece | Path | Role |
|---|---|---|
| Primary nav | src/components/AppNav/ |
Project routes, ActiveProjectBar, Reference/Settings |
| Secondary nav | src/components/SectionNav/ |
Section filters, New actions, sub-links |
| Registry | src/nav/sectionNavRegistry.ts |
Pathname prefix → section component |
| Nav config | src/nav/primaryNavItems.ts |
Icons, labels, entity count keys |
Desktop: both columns in AppShell.Navbar (~480px when secondary visible). Mobile: useMediaQuery shows secondary as a Paper toolbar above <Routes>.
Summary (/summary) has no registry entry — secondary column hidden.
State: URL search params for shareable filters (useChannelListQuery, useListNameQuery, useVendorFormatParam); list filters/sort/columns also in per-project localStorage (#146 — see List table state).
Icons use Tabler Icons via @tabler/icons-react. Import by name per file; do not barrel-re-export.
Icons aid scanning and primary actions (nav, New/Edit/Delete, import/export). Data-dense surfaces (tables, badges, frequency text) stay text-first.
| Entity | Icon |
|---|---|
| Summary | IconLayoutDashboard |
| Channels | IconAntenna |
| Zones | IconFolders |
| Talk groups | IconUsersGroup |
| Contacts | IconAddressBook |
| RX Group Lists | IconListDetails |
| Import & export | IconArrowsLeftRight |
| Reference | IconBook |
| Settings | IconSettings |
- Tracking: codeplug-tool#64 (icons), #81 (nav), #138 (datatables), #146 (list persistence)
- Component sidecars:
AppNav.md,SectionNav.md,DataTable.md