Skip to content
This repository was archived by the owner on Jun 28, 2026. It is now read-only.

Commit f678d73

Browse files
committed
feat: add Sidebar component
1 parent 530a0bd commit f678d73

128 files changed

Lines changed: 5650 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/showcase/assets/menu/submenu/menu-headless.data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ export const headlessMenu = [
219219
{
220220
name: 'useMenu',
221221
href: '/docs/headless/menu'
222+
},
223+
{
224+
name: 'useSidebar',
225+
href: '/docs/headless/sidebar'
222226
}
223227
]
224228
},

apps/showcase/assets/menu/submenu/menu-primitive.data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ export const primitiveMenu = [
211211
{
212212
name: 'Menu',
213213
href: '/docs/primitive/menu'
214+
},
215+
{
216+
name: 'Sidebar',
217+
href: '/docs/primitive/sidebar'
214218
}
215219
]
216220
},

apps/showcase/assets/menu/submenu/menu-styled.data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ export const styledMenu = [
287287
{
288288
name: 'Menu',
289289
href: '/docs/styled/components/menu'
290+
},
291+
{
292+
name: 'Sidebar',
293+
href: '/docs/styled/components/sidebar'
290294
}
291295
]
292296
},

apps/showcase/assets/menu/submenu/menu-tailwind.data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ export const tailwindMenu = [
139139
name: 'ScrollArea',
140140
href: '/docs/tailwind/components/scrollarea'
141141
},
142+
{
143+
name: 'Sidebar',
144+
href: '/docs/tailwind/components/sidebar'
145+
},
142146
{
143147
name: 'Splitter',
144148
href: '/docs/tailwind/components/splitter'
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
.wrapper {
2+
max-width: 48rem;
3+
margin: 0 auto;
4+
border: 1px solid light-dark(var(--p-surface-200), var(--p-surface-700));
5+
border-radius: 0.5rem;
6+
overflow: hidden;
7+
}
8+
9+
.layout {
10+
display: flex;
11+
min-height: 24rem;
12+
}
13+
14+
.sidebar {
15+
display: flex;
16+
flex-direction: column;
17+
width: 16rem;
18+
border-right: 1px solid light-dark(var(--p-surface-200), var(--p-surface-700));
19+
background: light-dark(var(--p-surface-0), var(--p-surface-950));
20+
color: light-dark(var(--p-surface-700), var(--p-surface-200));
21+
transition: width 250ms cubic-bezier(.4, 0, .2, 1);
22+
overflow: hidden;
23+
}
24+
25+
.sidebar[data-state='collapsed'] {
26+
width: 3rem;
27+
}
28+
29+
.header {
30+
display: flex;
31+
align-items: center;
32+
gap: 0.5rem;
33+
padding: 0.5rem;
34+
}
35+
36+
.logo {
37+
width: 1.5rem;
38+
height: 1.5rem;
39+
display: flex;
40+
align-items: center;
41+
justify-content: center;
42+
border-radius: 0.375rem;
43+
background: linear-gradient(to bottom right, #8b5cf6, #4f46e5);
44+
color: white;
45+
font-size: 0.75rem;
46+
font-weight: 700;
47+
flex-shrink: 0;
48+
}
49+
50+
.logoText {
51+
font-weight: 600;
52+
font-size: 0.875rem;
53+
white-space: nowrap;
54+
}
55+
56+
.content {
57+
flex: 1;
58+
overflow: auto;
59+
padding: 0.25rem;
60+
}
61+
62+
.group {
63+
padding: 0.25rem;
64+
}
65+
66+
.groupLabel {
67+
padding: 0.25rem 0.5rem;
68+
font-size: 0.75rem;
69+
font-weight: 500;
70+
color: light-dark(var(--p-surface-500), var(--p-surface-400));
71+
white-space: nowrap;
72+
}
73+
74+
.menu {
75+
display: flex;
76+
flex-direction: column;
77+
gap: 0.125rem;
78+
list-style: none;
79+
padding: 0;
80+
margin: 0;
81+
}
82+
83+
.menuItem {
84+
list-style: none;
85+
}
86+
87+
.menuButton {
88+
display: flex;
89+
width: 100%;
90+
align-items: center;
91+
gap: 0.5rem;
92+
padding: 0.375rem 0.5rem;
93+
border: none;
94+
border-radius: 0.375rem;
95+
background: none;
96+
font-size: 0.875rem;
97+
color: light-dark(var(--p-surface-600), var(--p-surface-400));
98+
cursor: pointer;
99+
white-space: nowrap;
100+
overflow: hidden;
101+
}
102+
103+
.menuButton:hover {
104+
background: light-dark(var(--p-surface-100), var(--p-surface-800));
105+
color: light-dark(var(--p-surface-900), var(--p-surface-0));
106+
}
107+
108+
.menuButton[data-active] {
109+
background: light-dark(var(--p-surface-100), var(--p-surface-800));
110+
color: light-dark(var(--p-surface-900), var(--p-surface-0));
111+
font-weight: 500;
112+
}
113+
114+
.menuButton svg {
115+
width: 1rem;
116+
height: 1rem;
117+
flex-shrink: 0;
118+
}
119+
120+
.footer {
121+
padding: 0.5rem;
122+
border-top: 1px solid light-dark(var(--p-surface-200), var(--p-surface-700));
123+
}
124+
125+
.avatar {
126+
width: 1.5rem;
127+
height: 1.5rem;
128+
display: flex;
129+
align-items: center;
130+
justify-content: center;
131+
border-radius: 9999px;
132+
background: light-dark(var(--p-surface-200), var(--p-surface-800));
133+
font-size: 0.625rem;
134+
font-weight: 500;
135+
flex-shrink: 0;
136+
}
137+
138+
.main {
139+
flex: 1;
140+
padding: 1.5rem;
141+
}
142+
143+
.main h2 {
144+
font-size: 1.25rem;
145+
font-weight: 700;
146+
margin-bottom: 0.5rem;
147+
}
148+
149+
.main p {
150+
color: light-dark(var(--p-surface-500), var(--p-surface-400));
151+
}
152+
153+
.trigger {
154+
padding: 0.375rem 0.75rem;
155+
border: 1px solid light-dark(var(--p-surface-200), var(--p-surface-700));
156+
border-radius: 0.375rem;
157+
background: none;
158+
cursor: pointer;
159+
font-size: 0.875rem;
160+
margin-bottom: 1rem;
161+
}
162+
163+
.chevron {
164+
width: 1rem;
165+
height: 1rem;
166+
margin-left: auto;
167+
transition: transform 200ms;
168+
flex-shrink: 0;
169+
}
170+
171+
.chevronOpen {
172+
transform: rotate(180deg);
173+
}
174+
175+
.subMenu {
176+
display: flex;
177+
flex-direction: column;
178+
gap: 0.125rem;
179+
list-style: none;
180+
padding: 0.125rem 0 0.125rem 1.5rem;
181+
margin: 0;
182+
}
183+
184+
.subMenuButton {
185+
display: flex;
186+
width: 100%;
187+
align-items: center;
188+
gap: 0.5rem;
189+
padding: 0.375rem 0.5rem;
190+
border: none;
191+
border-radius: 0.375rem;
192+
background: none;
193+
font-size: 0.875rem;
194+
color: light-dark(var(--p-surface-600), var(--p-surface-400));
195+
cursor: pointer;
196+
white-space: nowrap;
197+
}
198+
199+
.subMenuButton:hover {
200+
background: light-dark(var(--p-surface-100), var(--p-surface-800));
201+
color: light-dark(var(--p-surface-900), var(--p-surface-0));
202+
}
203+
204+
.subMenuButton[data-active] {
205+
background: light-dark(var(--p-surface-100), var(--p-surface-800));
206+
color: light-dark(var(--p-surface-900), var(--p-surface-0));
207+
font-weight: 500;
208+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use client';
2+
import { Bell, ChartBar, ChevronDown, Folder, Home, Inbox, Search, Users } from '@primeicons/react';
3+
import { useSidebar, useSidebarLayout, useSidebarMenuItem } from '@primereact/headless/sidebar';
4+
import * as React from 'react';
5+
import styles from './basic-demo.module.css';
6+
7+
const navItems = [
8+
{ icon: Home, label: 'Home', isActive: true },
9+
{ icon: Inbox, label: 'Inbox' },
10+
{ icon: Search, label: 'Search' },
11+
{ icon: Bell, label: 'Notifications' }
12+
];
13+
14+
const projectItems = [
15+
{ icon: ChartBar, label: 'Analytics', subItems: [{ label: 'Overview', isActive: true }, { label: 'Reports' }] },
16+
{ icon: Users, label: 'Team' },
17+
{ icon: Folder, label: 'Documents', subItems: [{ label: 'Shared' }, { label: 'Private' }] }
18+
];
19+
20+
function CollapsibleMenuItem({ icon: Icon, label, subItems, isOpen }: { icon: React.FC<any>; label: string; subItems: { label: string; isActive?: boolean }[]; isOpen: boolean }) {
21+
const { state: itemState, triggerProps, contentProps } = useSidebarMenuItem({ collapsible: true, defaultOpen: isOpen });
22+
23+
return (
24+
<li className={styles.menuItem}>
25+
<button {...triggerProps} className={styles.menuButton}>
26+
<Icon />
27+
<span>{label}</span>
28+
<ChevronDown className={`${styles.chevron} ${itemState.open ? styles.chevronOpen : ''}`} />
29+
</button>
30+
{itemState.open && (
31+
<ul {...contentProps} className={styles.subMenu}>
32+
{subItems.map((sub) => (
33+
<li key={sub.label}>
34+
<button className={styles.subMenuButton} data-active={sub.isActive || undefined}>
35+
<span>{sub.label}</span>
36+
</button>
37+
</li>
38+
))}
39+
</ul>
40+
)}
41+
</li>
42+
);
43+
}
44+
45+
function SidebarContent({ layout }: { layout: any }) {
46+
const { rootProps, state, toggle } = useSidebar({ id: 'main', layout, defaultOpen: true, collapsible: 'icon' });
47+
48+
return (
49+
<>
50+
<aside {...rootProps} className={styles.sidebar} data-state={state.displayState}>
51+
<div className={styles.header}>
52+
<div className={styles.logo}>A</div>
53+
{state.open && <span className={styles.logoText}>Acme Inc</span>}
54+
</div>
55+
<nav className={styles.content}>
56+
<div className={styles.group}>
57+
{state.open && <div className={styles.groupLabel}>Navigation</div>}
58+
<ul className={styles.menu}>
59+
{navItems.map((item) => (
60+
<li key={item.label} className={styles.menuItem}>
61+
<button className={styles.menuButton} data-active={item.isActive || undefined}>
62+
<item.icon />
63+
{state.open && <span>{item.label}</span>}
64+
</button>
65+
</li>
66+
))}
67+
</ul>
68+
</div>
69+
<div className={styles.group}>
70+
{state.open && <div className={styles.groupLabel}>Projects</div>}
71+
<ul className={styles.menu}>
72+
{projectItems.map((item) =>
73+
item.subItems && state.open ? (
74+
<CollapsibleMenuItem key={item.label} icon={item.icon} label={item.label} subItems={item.subItems} isOpen={item.subItems.some((s) => s.isActive)} />
75+
) : (
76+
<li key={item.label} className={styles.menuItem}>
77+
<button className={styles.menuButton}>
78+
<item.icon />
79+
{state.open && <span>{item.label}</span>}
80+
</button>
81+
</li>
82+
)
83+
)}
84+
</ul>
85+
</div>
86+
</nav>
87+
<div className={styles.footer}>
88+
<button className={styles.menuButton}>
89+
<div className={styles.avatar}>JD</div>
90+
{state.open && <span>John Doe</span>}
91+
</button>
92+
</div>
93+
</aside>
94+
<main className={styles.main}>
95+
<button onClick={() => toggle()} className={styles.trigger}>
96+
{state.open ? 'Collapse' : 'Expand'}
97+
</button>
98+
<h2>Dashboard</h2>
99+
<p>Select an item from the sidebar to get started.</p>
100+
</main>
101+
</>
102+
);
103+
}
104+
105+
export default function BasicDemo() {
106+
const layout = useSidebarLayout({});
107+
108+
return (
109+
<div className={styles.wrapper}>
110+
<div className={styles.layout}>
111+
<SidebarContent layout={layout} />
112+
</div>
113+
</div>
114+
);
115+
}

0 commit comments

Comments
 (0)