docs: add sidebar section layout design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# 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
|
||||
<Sidebar>
|
||||
<Sidebar.Header ... />
|
||||
|
||||
<Sidebar.Section label="Applications" ...>
|
||||
<SidebarTree ... />
|
||||
</Sidebar.Section>
|
||||
|
||||
<Sidebar.Section label="Agents" ...>
|
||||
<SidebarTree ... />
|
||||
</Sidebar.Section>
|
||||
|
||||
<Sidebar.Section label="Routes" position="bottom" maxHeight="200px" ...>
|
||||
<SidebarTree ... />
|
||||
</Sidebar.Section>
|
||||
|
||||
<Sidebar.Section label="Starred" position="bottom" ...>
|
||||
...
|
||||
</Sidebar.Section>
|
||||
|
||||
<Sidebar.Footer>...</Sidebar.Footer>
|
||||
</Sidebar>
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
<aside class="sidebar">
|
||||
[collapse toggle]
|
||||
{header}
|
||||
{search bar}
|
||||
<div class="sectionGroup sectionGroupTop">
|
||||
{topSections}
|
||||
</div>
|
||||
<div class="sectionSpacer" />
|
||||
<div class="sectionGroup sectionGroupBottom">
|
||||
{bottomSections}
|
||||
</div>
|
||||
{footer}
|
||||
</aside>
|
||||
```
|
||||
|
||||
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 && (
|
||||
<div
|
||||
className={styles.sectionContent}
|
||||
style={maxHeight ? { maxHeight } : undefined}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user