# Sidebar Section Layout: Top/Bottom Positioning & Scrollable Sections ## Summary Extend `Sidebar.Section` with two new optional props: - `position: 'top' | 'bottom'` — controls whether a section stacks from the top of the sidebar or from the bottom (above the footer). Default: `'top'`. - `maxHeight: string` — CSS length value (e.g. `"250px"`, `"30vh"`) that constrains the section's content area. When content exceeds this height, it scrolls. The section header/toggle always remains visible. This enables a layout where primary sections (Applications, Agents) occupy the top of the sidebar, while secondary sections (Routes, Starred) cluster near the bottom — with a flexible spacer absorbing remaining vertical space between the two groups. ## API Changes ### `Sidebar.Section` — new optional props ```tsx interface SidebarSectionProps { icon: ReactNode label: string open: boolean onToggle: () => void active?: boolean children: ReactNode className?: string position?: 'top' | 'bottom' // default: 'top' maxHeight?: string // CSS length, e.g. "250px", "30vh" } ``` No changes to `Sidebar.Header`, `Sidebar.Footer`, `Sidebar.FooterLink`, or `SidebarRoot` props. ### Consumer usage ```tsx ... ... ``` ## Layout Model ### SidebarRoot child partitioning `SidebarRoot` already inspects children to extract `Header`. This extends the same pattern: 1. Extract `Header` children (existing) 2. Extract `Footer` children 3. Partition remaining children into `topSections` and `bottomSections` based on `position` prop (default `'top'`) ### Render structure ``` ``` When there are no bottom sections, the spacer is omitted. The layout behaves identically to today — footer's `margin-top: auto` handles positioning. Zero breaking changes for existing consumers. ### SidebarSection content wrapper A new `.sectionContent` div wraps only the `children` inside `SidebarSection` (not the toggle header). When `maxHeight` is provided, it receives the value as an inline `max-height` style. ```tsx {open && (
{children}
)} ``` ## CSS Changes ### Group wrappers ```css .sectionGroup { flex: 0 1 auto; overflow-y: auto; min-height: 0; } .sectionSpacer { flex: 1 0 0; } ``` ### Section content scrolling ```css .sectionContent { overflow-y: auto; } ``` `maxHeight` is applied as an inline style, not a CSS class, since it varies per section instance. ### Custom scrollbars Applied to both `.sectionGroup` and `.sectionContent` to keep the dark sidebar aesthetic: ```css /* Standard (Firefox, modern Chrome/Edge) */ scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.15) transparent; /* WebKit (Safari, older Chrome) */ ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 2px; } ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); } ``` ## Collapsed Sidebar Behavior In collapsed (icon rail) mode, sections render as single icon buttons with no scrollable content: - **`position`**: Respected — bottom sections render in the bottom group, so their icons cluster near the footer in the rail. - **`maxHeight`**: Ignored — no content to constrain. The group wrapper and spacer structure remains active in collapsed mode for consistent icon positioning. ## Edge Cases | Scenario | Behavior | |----------|----------| | All sections `"top"` (no bottom sections) | No spacer rendered. Identical to current layout. | | All sections `"bottom"` | Top group empty. Sections cluster above footer. | | `maxHeight` set but content is shorter | No visual effect — wrapper is naturally smaller than max. | | Very short viewport | Both group wrappers scroll independently via `overflow-y: auto` on `.sectionGroup`. | ## Files Changed - `src/design-system/layout/Sidebar/Sidebar.tsx` — add `position` and `maxHeight` props to `SidebarSectionProps`, add `sectionContent` wrapper to `SidebarSection`, partition children in `SidebarRoot` - `src/design-system/layout/Sidebar/Sidebar.module.css` — add `.sectionGroup`, `.sectionSpacer`, `.sectionContent`, custom scrollbar styles - `src/design-system/layout/Sidebar/Sidebar.test.tsx` — new test cases for positioning and scrolling behavior ## Tests - Section with `maxHeight` renders content wrapper with correct inline style and `overflow-y: auto` - Sections with `position="bottom"` render inside the bottom group wrapper - Default `position` (omitted) renders in the top group - When no bottom sections exist, no spacer is rendered - Collapsed sidebar renders bottom sections in the bottom group