feat(396): collapsible sidebar + nav-group accordions
Add two independent, persisted collapse states to the shell sidebar: - a header toggle that collapses it to a 60px icon rail - collapsible accordions per labeled nav group (Media, System); the unlabeled Primary group is always open, default-collapsed groups State + persistence live in web/src/app/sidebarState.ts (useSidebarState); AppShell stamps ctv-app-shell-collapsed on the shell root and the rail look is CSS-driven. Two namespaced localStorage keys (ctv-sidebar-collapsed, ctv-sidebar-groups) per the persisted-UI-state convention. In the rail, accordions are ignored (all items shown icon-only, label kept in the a11y tree + surfaced as a title tooltip, badges as a corner dot); active-route indicator works in both states; grid-column transition respects prefers-reduced-motion. Tests: colocated sidebarState.test.ts + a new describe in App.test.tsx (default-collapsed, accordion toggle+persist, rail, reload persistence). Docs: spa-conventions §13 + decisions.md 2026-07-18. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+83
-1
@@ -26,6 +26,21 @@ body {
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-sans, system-ui, sans-serif);
|
||||
font-size: var(--text-sm, 13px);
|
||||
/* Animate the sidebar collapse/expand (#396). Only the grid track width transitions so the
|
||||
content reflows smoothly without a layout jump. */
|
||||
transition: grid-template-columns var(--dur-base, 140ms) var(--ease-standard);
|
||||
}
|
||||
|
||||
/* Collapsed sidebar = the 60px icon rail (#396). Narrowing the tracked custom property drives the
|
||||
grid-template-columns transition above. */
|
||||
.ctv-app-shell-collapsed {
|
||||
--sidebar-w: 60px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ctv-app-shell {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ctv-sidebar {
|
||||
@@ -97,6 +112,72 @@ body {
|
||||
font-size: var(--text-2xs, 11px);
|
||||
}
|
||||
|
||||
/* Sidebar collapse toggle in the brand header (#396). */
|
||||
.ctv-sidebar-toggle {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm, 5px);
|
||||
background: transparent;
|
||||
color: var(--text-disabled);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--dur-fast) var(--ease-standard),
|
||||
color var(--dur-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.ctv-sidebar-toggle:hover {
|
||||
background: var(--ctv-accent-soft);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ctv-sidebar-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--ring-focus);
|
||||
}
|
||||
|
||||
/* Hairline divider shown between nav groups in the collapsed rail (accordions don't apply
|
||||
there, so the group label/chevron is replaced by a plain separator — #396). */
|
||||
.ctv-nav-divider {
|
||||
height: 1px;
|
||||
margin: var(--space-4, 8px) var(--space-4, 8px);
|
||||
background: var(--border-hairline);
|
||||
}
|
||||
|
||||
/* ---- Collapsed (icon-rail) sidebar overrides (#396) ---- */
|
||||
.ctv-app-shell-collapsed .ctv-brand {
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ctv-app-shell-collapsed .ctv-brand img,
|
||||
.ctv-app-shell-collapsed .ctv-brand-wordmark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ctv-app-shell-collapsed .ctv-sidebar-toggle {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* Footer: hide the version text block and the health label, keep the status dot centered. */
|
||||
.ctv-app-shell-collapsed .ctv-sidebar-health {
|
||||
justify-content: center;
|
||||
padding: var(--space-6, 12px) 0;
|
||||
}
|
||||
|
||||
.ctv-app-shell-collapsed .ctv-sidebar-health > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ctv-app-shell-collapsed .ctv-sidebar-health .ctv-status-dot > span:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ctv-shell-body {
|
||||
min-width: 0;
|
||||
height: 100vh;
|
||||
@@ -3730,7 +3811,8 @@ body {
|
||||
}
|
||||
|
||||
.ctv-nav,
|
||||
.ctv-sidebar-health {
|
||||
.ctv-sidebar-health,
|
||||
.ctv-sidebar-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user