diff --git a/src/design-system/layout/Sidebar/Sidebar.test.tsx b/src/design-system/layout/Sidebar/Sidebar.test.tsx
index b308ae3..0c5b570 100644
--- a/src/design-system/layout/Sidebar/Sidebar.test.tsx
+++ b/src/design-system/layout/Sidebar/Sidebar.test.tsx
@@ -393,4 +393,87 @@ describe('Sidebar compound component', () => {
const contentWrapper = container.querySelector('.sectionContent')
expect(contentWrapper).not.toBeInTheDocument()
})
+
+ // 20. renders top sections in sectionGroup wrapper
+ it('renders sections in top group wrapper by default', () => {
+ const { container } = render(
+
+
+ ic} label="Apps" open onToggle={vi.fn()}>
+ apps content
+
+
+ ,
+ )
+
+ const topGroup = container.querySelector('.sectionGroup')
+ expect(topGroup).toBeInTheDocument()
+ expect(topGroup!.textContent).toContain('Apps')
+ })
+
+ // 21. renders bottom sections in separate group with spacer
+ it('renders bottom sections in a separate group with spacer between', () => {
+ const { container } = render(
+
+
+ ic} label="Apps" open onToggle={vi.fn()}>
+ apps content
+
+ ic} label="Routes" open onToggle={vi.fn()} position="bottom">
+ routes content
+
+
+ ,
+ )
+
+ const groups = container.querySelectorAll('.sectionGroup')
+ expect(groups).toHaveLength(2)
+ expect(groups[0].textContent).toContain('Apps')
+ expect(groups[1].textContent).toContain('Routes')
+
+ const spacer = container.querySelector('.sectionSpacer')
+ expect(spacer).toBeInTheDocument()
+ })
+
+ // 22. does not render spacer when no bottom sections
+ it('does not render spacer when all sections are top', () => {
+ const { container } = render(
+
+
+ ic} label="Apps" open onToggle={vi.fn()}>
+ apps content
+
+ ic} label="Agents" open onToggle={vi.fn()}>
+ agents content
+
+
+ ,
+ )
+
+ const spacer = container.querySelector('.sectionSpacer')
+ expect(spacer).not.toBeInTheDocument()
+ })
+
+ // 23. collapsed sidebar renders bottom sections in bottom group
+ it('renders bottom sections in bottom group when sidebar is collapsed', () => {
+ const { container } = render(
+
+
+ ic} label="Apps" open={false} onToggle={vi.fn()}>
+ apps
+
+ ic} label="Routes" open={false} onToggle={vi.fn()} position="bottom">
+ routes
+
+
+ ,
+ )
+
+ const groups = container.querySelectorAll('.sectionGroup')
+ expect(groups).toHaveLength(2)
+
+ // Bottom group should contain the Routes rail item
+ const bottomGroup = groups[1]
+ expect(bottomGroup.querySelector('[title="Routes"]')).toBeInTheDocument()
+ })
})
diff --git a/src/design-system/layout/Sidebar/Sidebar.tsx b/src/design-system/layout/Sidebar/Sidebar.tsx
index 8dddcc3..d88563d 100644
--- a/src/design-system/layout/Sidebar/Sidebar.tsx
+++ b/src/design-system/layout/Sidebar/Sidebar.tsx
@@ -205,9 +205,11 @@ function SidebarRoot({
)}
- {/* Render Header first, then search, then remaining children */}
+ {/* Render Header first, then search, then partitioned sections, then footer */}
{(() => {
const childArray = Children.toArray(children)
+
+ // Extract header
const headerIdx = childArray.findIndex(
(child) => isValidElement(child) && child.type === SidebarHeader,
)
@@ -216,6 +218,31 @@ function SidebarRoot({
? [...childArray.slice(0, headerIdx), ...childArray.slice(headerIdx + 1)]
: childArray
+ // Extract footer
+ const footerIdx = rest.findIndex(
+ (child) => isValidElement(child) && child.type === SidebarFooter,
+ )
+ const footer = footerIdx >= 0 ? rest[footerIdx] : null
+ const sections = footerIdx >= 0
+ ? [...rest.slice(0, footerIdx), ...rest.slice(footerIdx + 1)]
+ : rest
+
+ // Partition sections into top/bottom by position prop
+ const topSections: typeof sections = []
+ const bottomSections: typeof sections = []
+ for (const child of sections) {
+ if (
+ isValidElement(child) &&
+ child.props.position === 'bottom'
+ ) {
+ bottomSections.push(child)
+ } else {
+ topSections.push(child)
+ }
+ }
+
+ const hasBottomSections = bottomSections.length > 0
+
return (
<>
{header}
@@ -245,7 +272,16 @@ function SidebarRoot({
)}
- {rest}
+
+ {topSections}
+
+ {hasBottomSections && }
+ {hasBottomSections && (
+
+ {bottomSections}
+
+ )}
+ {footer}
>
)
})()}