docs: clarify search ownership and icon-rail click behavior
Search: DS renders dumb controlled input, app owns state and passes filterQuery to SidebarTree instances. Icon-rail click: fires both onCollapseToggle and onToggle simultaneously, no navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,9 +60,12 @@ The outer shell. Renders the sidebar frame with an optional search input and col
|
|||||||
| `collapsed` | `boolean` | `false` | Render as ~48px icon rail |
|
| `collapsed` | `boolean` | `false` | Render as ~48px icon rail |
|
||||||
| `onCollapseToggle` | `() => void` | - | Collapse/expand toggle clicked |
|
| `onCollapseToggle` | `() => void` | - | Collapse/expand toggle clicked |
|
||||||
| `onSearchChange` | `(query: string) => void` | - | Search input changed. Omit to hide search. |
|
| `onSearchChange` | `(query: string) => void` | - | Search input changed. Omit to hide search. |
|
||||||
|
| `searchValue` | `string` | `''` | Controlled value for the search input |
|
||||||
| `children` | `ReactNode` | - | Sidebar.Header, Sidebar.Section, Sidebar.Footer |
|
| `children` | `ReactNode` | - | Sidebar.Header, Sidebar.Section, Sidebar.Footer |
|
||||||
| `className` | `string` | - | Additional CSS class |
|
| `className` | `string` | - | Additional CSS class |
|
||||||
|
|
||||||
|
**Search state ownership:** The DS renders the search input as a dumb controlled input and calls `onSearchChange` on every keystroke. The consuming application owns the search state and passes it to each `SidebarTree` as `filterQuery`. This lets the app control filtering behavior (e.g., clear search when switching sections, filter only certain sections). The DS does not hold any search state internally.
|
||||||
|
|
||||||
**Rendering rules:**
|
**Rendering rules:**
|
||||||
- Expanded: full width (~260px), all content visible
|
- Expanded: full width (~260px), all content visible
|
||||||
- Collapsed: ~48px wide, only icons visible, tooltips on hover
|
- Collapsed: ~48px wide, only icons visible, tooltips on hover
|
||||||
@@ -129,7 +132,9 @@ v [icon] APPLICATIONS
|
|||||||
[icon] <- centered, tooltip shows label on hover
|
[icon] <- centered, tooltip shows label on hover
|
||||||
```
|
```
|
||||||
|
|
||||||
Header has: chevron (left), icon, label. Chevron rotates on collapse/expand. Active section gets the amber left-border accent (existing pattern). Clicking the header calls `onToggle`. In icon-rail mode, clicking the icon calls both `onCollapseToggle` (to expand the sidebar) and `onToggle`.
|
Header has: chevron (left), icon, label. Chevron rotates on collapse/expand. Active section gets the amber left-border accent (existing pattern). Clicking the header calls `onToggle`.
|
||||||
|
|
||||||
|
**Icon-rail click behavior:** In collapsed mode, clicking a section icon fires both `onCollapseToggle` and `onToggle` simultaneously on the same click. The sidebar expands and the section opens in one motion. No navigation occurs — the user is expanding the sidebar to see what's inside, not committing to a destination. They click a tree item after the section is visible to navigate.
|
||||||
|
|
||||||
### `<Sidebar.Footer>`
|
### `<Sidebar.Footer>`
|
||||||
|
|
||||||
@@ -278,6 +283,7 @@ useEffect(() => {
|
|||||||
<Sidebar
|
<Sidebar
|
||||||
collapsed={sidebarCollapsed}
|
collapsed={sidebarCollapsed}
|
||||||
onCollapseToggle={() => setSidebarCollapsed(v => !v)}
|
onCollapseToggle={() => setSidebarCollapsed(v => !v)}
|
||||||
|
searchValue={filterQuery}
|
||||||
onSearchChange={setFilterQuery}
|
onSearchChange={setFilterQuery}
|
||||||
>
|
>
|
||||||
<Sidebar.Header logo={<CameleerLogo />} title="cameleer" version="v3.2.1" />
|
<Sidebar.Header logo={<CameleerLogo />} title="cameleer" version="v3.2.1" />
|
||||||
@@ -310,6 +316,78 @@ useEffect(() => {
|
|||||||
</Sidebar>
|
</Sidebar>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Mock App Migration — LayoutShell
|
||||||
|
|
||||||
|
The 11 page files currently duplicating `<AppShell sidebar={<Sidebar apps={SIDEBAR_APPS} />}>` will be consolidated into a single `LayoutShell` component.
|
||||||
|
|
||||||
|
### `src/layout/LayoutShell.tsx`
|
||||||
|
|
||||||
|
Composes the sidebar once using the new compound API. All page-specific content is rendered via `<Outlet />`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/layout/LayoutShell.tsx
|
||||||
|
export function LayoutShell() {
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||||
|
const [filterQuery, setFilterQuery] = useState('')
|
||||||
|
const [appsCollapsed, setAppsCollapsed] = useState(false)
|
||||||
|
const [agentsCollapsed, setAgentsCollapsed] = useState(false)
|
||||||
|
const [routesCollapsed, setRoutesCollapsed] = useState(false)
|
||||||
|
const { starredIds, isStarred, toggleStar } = useStarred()
|
||||||
|
const location = useLocation()
|
||||||
|
// ... build tree nodes from SIDEBAR_APPS, starred section, etc.
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
sidebar={
|
||||||
|
<Sidebar
|
||||||
|
collapsed={sidebarCollapsed}
|
||||||
|
onCollapseToggle={() => setSidebarCollapsed(v => !v)}
|
||||||
|
searchValue={filterQuery}
|
||||||
|
onSearchChange={setFilterQuery}
|
||||||
|
>
|
||||||
|
<Sidebar.Header logo={...} title="cameleer" version="v3.2.1" />
|
||||||
|
<Sidebar.Section label="Applications" icon={...}
|
||||||
|
collapsed={appsCollapsed} onToggle={() => setAppsCollapsed(v => !v)}>
|
||||||
|
<SidebarTree nodes={appNodes} filterQuery={filterQuery} ... />
|
||||||
|
</Sidebar.Section>
|
||||||
|
<Sidebar.Section label="Agents" icon={...}
|
||||||
|
collapsed={agentsCollapsed} onToggle={() => setAgentsCollapsed(v => !v)}>
|
||||||
|
<SidebarTree nodes={agentNodes} filterQuery={filterQuery} ... />
|
||||||
|
</Sidebar.Section>
|
||||||
|
<Sidebar.Section label="Routes" icon={...}
|
||||||
|
collapsed={routesCollapsed} onToggle={() => setRoutesCollapsed(v => !v)}>
|
||||||
|
<SidebarTree nodes={routeNodes} filterQuery={filterQuery} ... />
|
||||||
|
</Sidebar.Section>
|
||||||
|
{/* Starred section built from useStarred + SIDEBAR_APPS */}
|
||||||
|
<Sidebar.Footer>
|
||||||
|
<Sidebar.FooterLink icon={...} label="Admin" ... />
|
||||||
|
<Sidebar.FooterLink icon={...} label="API Docs" ... />
|
||||||
|
</Sidebar.Footer>
|
||||||
|
</Sidebar>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Route structure change
|
||||||
|
|
||||||
|
`App.tsx` switches from per-page `<Route element={<Page />}>` to a layout route:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Route element={<LayoutShell />}>
|
||||||
|
<Route path="/apps" element={<Dashboard />} />
|
||||||
|
<Route path="/apps/:id" element={<Dashboard />} />
|
||||||
|
...all existing routes...
|
||||||
|
</Route>
|
||||||
|
```
|
||||||
|
|
||||||
|
All tree-building helpers (`buildAppTreeNodes`, `buildRouteTreeNodes`, `buildAgentTreeNodes`), starred section logic (`collectStarredItems`, `StarredGroup`), `formatCount`, and `sidebarRevealPath` handling move from `Sidebar.tsx` into `LayoutShell.tsx`. Each page file loses its `<AppShell sidebar={...}>` wrapper and becomes just the page content.
|
||||||
|
|
||||||
|
The Inventory page's `LayoutSection` keeps its own inline `<Sidebar>` demo with `SAMPLE_APPS` data — it's a showcase, not a navigation shell.
|
||||||
|
|
||||||
## Breaking Change
|
## Breaking Change
|
||||||
|
|
||||||
This is a **breaking change** to the `Sidebar` API. The old `<Sidebar apps={[...]} onNavigate={...} />` signature is removed entirely. The consuming application must migrate to the compound component API in the same release cycle.
|
This is a **breaking change** to the `Sidebar` API. The old `<Sidebar apps={[...]} onNavigate={...} />` signature is removed entirely. The consuming application must migrate to the compound component API in the same release cycle.
|
||||||
|
|||||||
Reference in New Issue
Block a user