Skip to content

refactor(ui): rebuild Tabs primitive on Button tabs and adopt across admin pages#183

Draft
DavidBabinec wants to merge 1 commit into
mainfrom
refactor/tabs-primitive
Draft

refactor(ui): rebuild Tabs primitive on Button tabs and adopt across admin pages#183
DavidBabinec wants to merge 1 commit into
mainfrom
refactor/tabs-primitive

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What changed

The Tabs compound component (src/ui/components/Tabs/) shipped with WAI-ARIA automatic activation, roving tabindex, and arrow-key navigation — but an underline visual style no page used. It sat with zero consumers while AiPage, UsersPage, and AccountPage each hand-rolled the same Button-based tablist, already drifting:

  • UsersPage's tabs were missing role="tab" / aria-selected that its two siblings had
  • none of the three had keyboard arrow-key navigation
  • all three duplicated the same .tabsRow CSS rule

This PR redesigns the primitive around the Button pattern the pages actually use, then adopts it in all three:

  • Tab renders the shared Button primitive (primary when active, secondary otherwise, size sm)
  • TabPanel lazy-mounts children by default (matching current page behavior); keepMounted opts a panel back into staying mounted while hidden. The panel element itself always stays in the DOM so the active tab's aria-controls resolves
  • Tab gains a testId prop so the pages' existing data-testids carry over unchanged
  • TabList + TabPanels may live in different subtrees of one <Tabs> provider (tab row goes into AdminPageLayout's tabs slot, panels render as children)
  • the three .tabsRow CSS copies are deleted; docs/reference/ui-primitives.md updated

Why

Health-check audit finding: a dead primitive coexisting with 3 hand-rolled versions of the same UI is the exact drift pattern the src/ui primitive rule exists to prevent. Consolidating fixes the UsersPage a11y gap and gives all three pages keyboard navigation for free.

Impact

  • Users/AI/Account pages: identical visuals, new arrow-key/Home/End tab navigation, correct ARIA everywhere
  • usersAdmin tests updated to query role="tab" (the a11y improvement this delivers)
  • New unit suite src/__tests__/ui/tabs.test.tsx covers the primitive's contract (roles, roving tabindex, lazy vs keepMounted panels, aria wiring, keyboard activation, testId)

Verification

bun test        # 6009 pass / 0 fail
bun run build   # tsc -b && vite build — pass
bun run lint    # pass

🤖 Generated with Claude Code

…admin pages

The Tabs compound component (ARIA automatic-activation, roving tabindex,
keyboard nav) shipped with an underline visual style no page used, so it
sat dead while AiPage, UsersPage, and AccountPage each hand-rolled the
same Button-based tablist — with drift: UsersPage's tabs were missing
role="tab"/aria-selected, none had arrow-key navigation, and all three
duplicated the same .tabsRow CSS.

- Tab now renders the shared Button primitive (primary when active,
  secondary otherwise, size sm) — the pattern the pages already used.
- TabPanel lazy-mounts children by default (matching page behavior);
  keepMounted opts panels back into staying mounted while hidden. The
  panel element itself stays in the DOM so aria-controls always resolves.
- Tab gains a testId prop so the pages' existing data-testids carry over.
- AiPage, UsersPage, AccountPage adopt Tabs/TabList/TabPanel; the three
  .tabsRow copies are deleted. All three pages gain keyboard navigation,
  and UsersPage gains the ARIA it was missing.
- usersAdmin tests updated: tab queries use role="tab" (a11y improvement
  the change delivers); new unit tests cover the primitive's contract.

Verification: bun test (6009 pass), bun run build, bun run lint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant