@fragments-sdk/ui 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,774 @@
1
+ import React, { useState } from 'react';
2
+ import { defineSegment } from '@fragments/core';
3
+ import { Sidebar, SidebarProvider, useSidebar } from './index.js';
4
+ import { Button } from '../Button/index.js';
5
+
6
+ // Example icons (inline SVGs for demo)
7
+ const HomeIcon = () => (
8
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
9
+ <path d="M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8H96a8,8,0,0,0,8-8V160h48v56a8,8,0,0,0,8,8h56a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H168V152a8,8,0,0,0-8-8H96a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z" />
10
+ </svg>
11
+ );
12
+
13
+ const UsersIcon = () => (
14
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
15
+ <path d="M117.25,157.92a60,60,0,1,0-66.5,0A95.83,95.83,0,0,0,3.53,195.63a8,8,0,1,0,13.4,8.74,80,80,0,0,1,134.14,0,8,8,0,0,0,13.4-8.74A95.83,95.83,0,0,0,117.25,157.92ZM40,108a44,44,0,1,1,44,44A44.05,44.05,0,0,1,40,108Zm210.14,98.7a8,8,0,0,1-11.07-2.33A79.83,79.83,0,0,0,172,168a8,8,0,0,1,0-16,44,44,0,1,0-16.34-84.87,8,8,0,1,1-5.94-14.85,60,60,0,0,1,55.53,105.64,95.83,95.83,0,0,1,47.22,37.71A8,8,0,0,1,250.14,206.7Z" />
16
+ </svg>
17
+ );
18
+
19
+ const FolderIcon = () => (
20
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
21
+ <path d="M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216Z" />
22
+ </svg>
23
+ );
24
+
25
+ const ChartIcon = () => (
26
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
27
+ <path d="M224,200h-8V40a8,8,0,0,0-16,0V200H168V96a8,8,0,0,0-16,0V200H120V136a8,8,0,0,0-16,0v64H72V168a8,8,0,0,0-16,0v32H40a8,8,0,0,0,0,16H224a8,8,0,0,0,0-16Z" />
28
+ </svg>
29
+ );
30
+
31
+ const GearIcon = () => (
32
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
33
+ <path d="M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Zm88-29.84q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.21,107.21,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.71,107.71,0,0,0-26.25-10.87,8,8,0,0,0-7.06,1.49L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.21,107.21,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06Zm-16.1-6.5a73.93,73.93,0,0,1,0,8.68,8,8,0,0,0,1.74,5.48l14.19,17.73a91.57,91.57,0,0,1-6.23,15L187,173.11a8,8,0,0,0-5.1,2.64,74.11,74.11,0,0,1-6.14,6.14,8,8,0,0,0-2.64,5.1l-2.51,22.58a91.32,91.32,0,0,1-15,6.23l-17.74-14.19a8,8,0,0,0-5-1.75h-.48a73.93,73.93,0,0,1-8.68,0,8,8,0,0,0-5.48,1.74L100.45,215.8a91.57,91.57,0,0,1-15-6.23L82.89,187a8,8,0,0,0-2.64-5.1,74.11,74.11,0,0,1-6.14-6.14,8,8,0,0,0-5.1-2.64L46.43,170.6a91.32,91.32,0,0,1-6.23-15l14.19-17.74a8,8,0,0,0,1.74-5.48,73.93,73.93,0,0,1,0-8.68,8,8,0,0,0-1.74-5.48L40.2,100.45a91.57,91.57,0,0,1,6.23-15L69,82.89a8,8,0,0,0,5.1-2.64,74.11,74.11,0,0,1,6.14-6.14A8,8,0,0,0,82.89,69L85.4,46.43a91.32,91.32,0,0,1,15-6.23l17.74,14.19a8,8,0,0,0,5.48,1.74,73.93,73.93,0,0,1,8.68,0,8,8,0,0,0,5.48-1.74L155.55,40.2a91.57,91.57,0,0,1,15,6.23L173.11,69a8,8,0,0,0,2.64,5.1,74.11,74.11,0,0,1,6.14,6.14,8,8,0,0,0,5.1,2.64l22.58,2.51a91.32,91.32,0,0,1,6.23,15l-14.19,17.74A8,8,0,0,0,199.87,123.66Z" />
34
+ </svg>
35
+ );
36
+
37
+ const HelpIcon = () => (
38
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
39
+ <path d="M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z" />
40
+ </svg>
41
+ );
42
+
43
+ const PlusIcon = () => (
44
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor">
45
+ <path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z" />
46
+ </svg>
47
+ );
48
+
49
+ const SidebarToggleIcon = () => (
50
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" fill="currentColor">
51
+ <path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,56H80V200H40ZM216,200H96V56H216V200Z" />
52
+ </svg>
53
+ );
54
+
55
+ // Mock Link component for demonstrating asChild pattern
56
+ const MockLink = React.forwardRef<HTMLAnchorElement, React.AnchorHTMLAttributes<HTMLAnchorElement>>(
57
+ ({ children, ...props }, ref) => (
58
+ <a ref={ref} {...props} onClick={(e) => { e.preventDefault(); props.onClick?.(e); }}>
59
+ {children}
60
+ </a>
61
+ )
62
+ );
63
+
64
+ const LogoIcon = ({ size = 32 }: { size?: number }) => (
65
+ <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 256 256" fill="currentColor">
66
+ <path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm0,176H48V48H208V208Z" />
67
+ </svg>
68
+ );
69
+
70
+ // Shared styles for demo content area
71
+ const mainContentStyle: React.CSSProperties = {
72
+ flex: 1,
73
+ minWidth: 0, // Prevents flex item from overflowing
74
+ padding: '24px',
75
+ background: 'var(--fui-bg-secondary)',
76
+ display: 'flex',
77
+ alignItems: 'center',
78
+ justifyContent: 'center',
79
+ color: 'var(--fui-text-secondary)',
80
+ fontSize: 'var(--fui-font-size-sm)',
81
+ };
82
+
83
+ const demoContainerStyle: React.CSSProperties = {
84
+ height: '400px',
85
+ display: 'flex',
86
+ width: '100%',
87
+ };
88
+
89
+ // Stateful demo for collapsed state
90
+ function CollapsedDemo() {
91
+ const [collapsed, setCollapsed] = useState(true);
92
+ return (
93
+ <div style={demoContainerStyle}>
94
+ <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
95
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
96
+ <LogoIcon size={32} />
97
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
98
+ </Sidebar.Header>
99
+ <Sidebar.Nav>
100
+ <Sidebar.Section>
101
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
102
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
103
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
104
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
105
+ </Sidebar.Section>
106
+ <Sidebar.Section label="Settings">
107
+ <Sidebar.Item icon={<GearIcon />}>Preferences</Sidebar.Item>
108
+ <Sidebar.Item icon={<HelpIcon />}>Help</Sidebar.Item>
109
+ </Sidebar.Section>
110
+ </Sidebar.Nav>
111
+ <Sidebar.Footer>
112
+ <Sidebar.CollapseToggle />
113
+ </Sidebar.Footer>
114
+ </Sidebar>
115
+ <main style={mainContentStyle}>
116
+ Hover over icons to see tooltips. Click toggle to expand.
117
+ </main>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ // Demo for submenu expansion using uncontrolled defaultExpanded
123
+ function SubmenuDemo() {
124
+ return (
125
+ <div style={demoContainerStyle}>
126
+ <Sidebar>
127
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
128
+ <LogoIcon size={32} />
129
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
130
+ </Sidebar.Header>
131
+ <Sidebar.Nav>
132
+ <Sidebar.Section>
133
+ <Sidebar.Item icon={<HomeIcon />}>Dashboard</Sidebar.Item>
134
+ <Sidebar.Item icon={<FolderIcon />} hasSubmenu defaultExpanded>
135
+ Projects
136
+ </Sidebar.Item>
137
+ <Sidebar.Submenu>
138
+ <Sidebar.SubItem active>Website Redesign</Sidebar.SubItem>
139
+ <Sidebar.SubItem>Mobile App</Sidebar.SubItem>
140
+ <Sidebar.SubItem>API Integration</Sidebar.SubItem>
141
+ </Sidebar.Submenu>
142
+ <Sidebar.Item icon={<UsersIcon />} hasSubmenu>
143
+ Team
144
+ </Sidebar.Item>
145
+ <Sidebar.Submenu>
146
+ <Sidebar.SubItem>Engineering</Sidebar.SubItem>
147
+ <Sidebar.SubItem>Design</Sidebar.SubItem>
148
+ </Sidebar.Submenu>
149
+ </Sidebar.Section>
150
+ </Sidebar.Nav>
151
+ <Sidebar.Footer>
152
+ <Sidebar.CollapseToggle />
153
+ </Sidebar.Footer>
154
+ </Sidebar>
155
+ <main style={mainContentStyle}>
156
+ Click on "Projects" or "Team" to toggle their submenus
157
+ </main>
158
+ </div>
159
+ );
160
+ }
161
+
162
+ // Demo for SidebarProvider with external trigger
163
+ function ExternalTriggerContent() {
164
+ const { toggleSidebar, state } = useSidebar();
165
+ return (
166
+ <main style={mainContentStyle}>
167
+ <div style={{ textAlign: 'center' }}>
168
+ <p style={{ marginBottom: '16px' }}>
169
+ Sidebar state: <strong>{state}</strong>
170
+ </p>
171
+ <Button onClick={toggleSidebar} variant="outline" size="sm">
172
+ <SidebarToggleIcon /> Toggle Sidebar
173
+ </Button>
174
+ <p style={{ marginTop: '16px', fontSize: '12px', opacity: 0.7 }}>
175
+ Also try pressing Cmd/Ctrl+B
176
+ </p>
177
+ </div>
178
+ </main>
179
+ );
180
+ }
181
+
182
+ function ProviderDemo() {
183
+ return (
184
+ <div style={demoContainerStyle}>
185
+ <SidebarProvider defaultCollapsed={false}>
186
+ <Sidebar>
187
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
188
+ <LogoIcon size={32} />
189
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
190
+ </Sidebar.Header>
191
+ <Sidebar.Nav>
192
+ <Sidebar.Section>
193
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
194
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
195
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
196
+ </Sidebar.Section>
197
+ </Sidebar.Nav>
198
+ <Sidebar.Footer>
199
+ <Sidebar.CollapseToggle />
200
+ </Sidebar.Footer>
201
+ </Sidebar>
202
+ <ExternalTriggerContent />
203
+ </SidebarProvider>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ // Demo for asChild pattern with mock Link
209
+ function AsChildDemo() {
210
+ return (
211
+ <div style={demoContainerStyle}>
212
+ <Sidebar>
213
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
214
+ <LogoIcon size={32} />
215
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
216
+ </Sidebar.Header>
217
+ <Sidebar.Nav>
218
+ <Sidebar.Section>
219
+ <Sidebar.Item icon={<HomeIcon />} active asChild>
220
+ <MockLink href="/dashboard">Dashboard</MockLink>
221
+ </Sidebar.Item>
222
+ <Sidebar.Item icon={<ChartIcon />} asChild>
223
+ <MockLink href="/analytics">Analytics</MockLink>
224
+ </Sidebar.Item>
225
+ <Sidebar.Item icon={<UsersIcon />} asChild>
226
+ <MockLink href="/team">Team</MockLink>
227
+ </Sidebar.Item>
228
+ <Sidebar.Item icon={<FolderIcon />} asChild>
229
+ <MockLink href="/projects">Projects</MockLink>
230
+ </Sidebar.Item>
231
+ </Sidebar.Section>
232
+ </Sidebar.Nav>
233
+ <Sidebar.Footer>
234
+ <Sidebar.CollapseToggle />
235
+ </Sidebar.Footer>
236
+ </Sidebar>
237
+ <main style={mainContentStyle}>
238
+ Items rendered as Link components using asChild pattern
239
+ </main>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ // Demo for section with action button
245
+ function SectionActionDemo() {
246
+ return (
247
+ <div style={demoContainerStyle}>
248
+ <Sidebar>
249
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
250
+ <LogoIcon size={32} />
251
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
252
+ </Sidebar.Header>
253
+ <Sidebar.Nav>
254
+ <Sidebar.Section>
255
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
256
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
257
+ </Sidebar.Section>
258
+ <Sidebar.Section
259
+ label="Projects"
260
+ action={
261
+ <Sidebar.SectionAction aria-label="Add project" onClick={() => alert('Add project clicked')}>
262
+ <PlusIcon />
263
+ </Sidebar.SectionAction>
264
+ }
265
+ >
266
+ <Sidebar.Item icon={<FolderIcon />}>Website Redesign</Sidebar.Item>
267
+ <Sidebar.Item icon={<FolderIcon />}>Mobile App</Sidebar.Item>
268
+ <Sidebar.Item icon={<FolderIcon />}>API Integration</Sidebar.Item>
269
+ </Sidebar.Section>
270
+ </Sidebar.Nav>
271
+ <Sidebar.Footer>
272
+ <Sidebar.CollapseToggle />
273
+ </Sidebar.Footer>
274
+ </Sidebar>
275
+ <main style={mainContentStyle}>
276
+ Section header has an "Add" action button
277
+ </main>
278
+ </div>
279
+ );
280
+ }
281
+
282
+ // Demo for skeleton loading state
283
+ function SkeletonDemo() {
284
+ const [loading, setLoading] = useState(true);
285
+
286
+ return (
287
+ <div style={demoContainerStyle}>
288
+ <Sidebar>
289
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
290
+ <LogoIcon size={32} />
291
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
292
+ </Sidebar.Header>
293
+ <Sidebar.Nav>
294
+ {loading ? (
295
+ <Sidebar.MenuSkeleton count={6} />
296
+ ) : (
297
+ <Sidebar.Section>
298
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
299
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
300
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
301
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
302
+ <Sidebar.Item icon={<GearIcon />}>Settings</Sidebar.Item>
303
+ <Sidebar.Item icon={<HelpIcon />}>Help</Sidebar.Item>
304
+ </Sidebar.Section>
305
+ )}
306
+ </Sidebar.Nav>
307
+ <Sidebar.Footer>
308
+ <Sidebar.CollapseToggle />
309
+ </Sidebar.Footer>
310
+ </Sidebar>
311
+ <main style={mainContentStyle}>
312
+ <div style={{ textAlign: 'center' }}>
313
+ <p style={{ marginBottom: '16px' }}>
314
+ {loading ? 'Loading navigation...' : 'Navigation loaded!'}
315
+ </p>
316
+ <Button onClick={() => setLoading(!loading)} variant="outline" size="sm">
317
+ {loading ? 'Show Content' : 'Show Skeleton'}
318
+ </Button>
319
+ </div>
320
+ </main>
321
+ </div>
322
+ );
323
+ }
324
+
325
+ // Demo for rail toggle
326
+ function RailDemo() {
327
+ const [collapsed, setCollapsed] = useState(false);
328
+
329
+ return (
330
+ <div style={{ ...demoContainerStyle, position: 'relative' }}>
331
+ <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
332
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
333
+ <LogoIcon size={32} />
334
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
335
+ </Sidebar.Header>
336
+ <Sidebar.Nav>
337
+ <Sidebar.Section>
338
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
339
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
340
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
341
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
342
+ </Sidebar.Section>
343
+ </Sidebar.Nav>
344
+ <Sidebar.Footer>
345
+ <Sidebar.CollapseToggle />
346
+ </Sidebar.Footer>
347
+ <Sidebar.Rail />
348
+ </Sidebar>
349
+ <main style={mainContentStyle}>
350
+ <div style={{ textAlign: 'center' }}>
351
+ <p>Hover over the right edge of the sidebar to see the rail indicator.</p>
352
+ <p style={{ marginTop: '8px', opacity: 0.7 }}>Click the rail to toggle collapse/expand.</p>
353
+ </div>
354
+ </main>
355
+ </div>
356
+ );
357
+ }
358
+
359
+ export default defineSegment({
360
+ component: Sidebar,
361
+
362
+ meta: {
363
+ name: 'Sidebar',
364
+ description: 'Responsive navigation sidebar with collapsible desktop mode and mobile drawer behavior.',
365
+ category: 'navigation',
366
+ status: 'stable',
367
+ tags: ['sidebar', 'navigation', 'drawer', 'menu', 'layout'],
368
+ since: '0.3.0',
369
+ },
370
+
371
+ usage: {
372
+ when: [
373
+ 'Primary app navigation with multiple sections',
374
+ 'Dashboard layouts requiring persistent navigation',
375
+ 'Admin interfaces with hierarchical menu structure',
376
+ 'Apps that need both mobile drawer and desktop sidebar',
377
+ ],
378
+ whenNot: [
379
+ 'Simple websites with few pages (use header nav)',
380
+ 'Content-focused sites where navigation is secondary',
381
+ 'Single-page applications with no navigation needs',
382
+ 'Mobile-only apps where bottom navigation is preferred',
383
+ ],
384
+ guidelines: [
385
+ 'Group related items into sections with clear labels',
386
+ 'Use icons for all items to support collapsed mode',
387
+ 'Limit nesting to 2 levels maximum',
388
+ 'Place most frequently used items at the top',
389
+ 'Use badges sparingly for notifications or counts',
390
+ 'The `active` prop on items should be controlled by your app based on current route',
391
+ 'Use `collapsedContent` on Header to show just a logo icon when collapsed',
392
+ 'Submenus are hidden when collapsed - use tooltips for navigation hints instead',
393
+ 'Use SidebarProvider to enable external triggers and keyboard shortcuts',
394
+ 'Use asChild with routing libraries (Next.js Link, React Router NavLink)',
395
+ 'Use Sidebar.MenuSkeleton while loading navigation data',
396
+ ],
397
+ accessibility: [
398
+ 'Uses semantic <nav> element with aria-label',
399
+ 'aria-current="page" on active items',
400
+ 'aria-expanded on items with submenus',
401
+ 'Escape key closes mobile drawer',
402
+ 'Cmd/Ctrl+B keyboard shortcut toggles sidebar (when using SidebarProvider)',
403
+ 'Focus trap in mobile drawer mode',
404
+ 'Minimum 44px touch targets',
405
+ ],
406
+ },
407
+
408
+ props: {
409
+ children: {
410
+ type: 'node',
411
+ description: 'Sidebar content (use Sidebar.Header, Sidebar.Nav, Sidebar.Section, etc.)',
412
+ required: true,
413
+ },
414
+ collapsed: {
415
+ type: 'boolean',
416
+ description: 'Icon-only mode for desktop (controlled)',
417
+ },
418
+ defaultCollapsed: {
419
+ type: 'boolean',
420
+ description: 'Initial collapsed state (uncontrolled)',
421
+ default: false,
422
+ },
423
+ onCollapsedChange: {
424
+ type: 'function',
425
+ description: 'Called when collapsed state changes',
426
+ },
427
+ open: {
428
+ type: 'boolean',
429
+ description: 'Mobile drawer open state (controlled)',
430
+ },
431
+ defaultOpen: {
432
+ type: 'boolean',
433
+ description: 'Initial open state (uncontrolled)',
434
+ default: false,
435
+ },
436
+ onOpenChange: {
437
+ type: 'function',
438
+ description: 'Called when open state changes',
439
+ },
440
+ width: {
441
+ type: 'string',
442
+ description: 'Width of expanded sidebar',
443
+ default: '240px',
444
+ },
445
+ collapsedWidth: {
446
+ type: 'string',
447
+ description: 'Width when collapsed',
448
+ default: '64px',
449
+ },
450
+ position: {
451
+ type: 'enum',
452
+ description: 'Sidebar position',
453
+ values: ['left', 'right'],
454
+ default: 'left',
455
+ },
456
+ collapsible: {
457
+ type: 'enum',
458
+ description: 'Collapse behavior mode',
459
+ values: ['icon', 'offcanvas', 'none'],
460
+ default: 'icon',
461
+ },
462
+ asChild: {
463
+ type: 'boolean',
464
+ description: '(Sidebar.Item) Render as child element for polymorphic composition',
465
+ default: false,
466
+ },
467
+ },
468
+
469
+ relations: [
470
+ { component: 'Tabs', relationship: 'alternative', note: 'Use Tabs for in-page section navigation' },
471
+ { component: 'Menu', relationship: 'composition', note: 'Use Menu for contextual actions within sidebar' },
472
+ ],
473
+
474
+ contract: {
475
+ propsSummary: [
476
+ 'collapsed: boolean - icon-only desktop mode',
477
+ 'open: boolean - mobile drawer state',
478
+ 'width: string - expanded width (default: 240px)',
479
+ 'position: left|right - sidebar position',
480
+ 'active: boolean - set on Sidebar.Item to mark current page (app-controlled)',
481
+ ],
482
+ scenarioTags: [
483
+ 'navigation.sidebar',
484
+ 'layout.sidebar',
485
+ 'navigation.drawer',
486
+ ],
487
+ a11yRules: ['A11Y_NAV_LANDMARK', 'A11Y_FOCUS_TRAP', 'A11Y_ESCAPE_CLOSE'],
488
+ },
489
+
490
+ variants: [
491
+ {
492
+ name: 'Default',
493
+ description: 'Standard sidebar with navigation sections. The `active` prop highlights the current page.',
494
+ code: `<Sidebar>
495
+ <Sidebar.Header>
496
+ <Logo />
497
+ <span>Acme App</span>
498
+ </Sidebar.Header>
499
+ <Sidebar.Nav>
500
+ <Sidebar.Section>
501
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
502
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
503
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
504
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
505
+ </Sidebar.Section>
506
+ <Sidebar.Section label="Settings">
507
+ <Sidebar.Item icon={<GearIcon />}>Preferences</Sidebar.Item>
508
+ <Sidebar.Item icon={<HelpIcon />}>Help</Sidebar.Item>
509
+ </Sidebar.Section>
510
+ </Sidebar.Nav>
511
+ <Sidebar.Footer>
512
+ <Sidebar.CollapseToggle />
513
+ </Sidebar.Footer>
514
+ </Sidebar>`,
515
+ render: () => (
516
+ <div style={demoContainerStyle}>
517
+ <Sidebar>
518
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
519
+ <LogoIcon size={32} />
520
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
521
+ </Sidebar.Header>
522
+ <Sidebar.Nav>
523
+ <Sidebar.Section>
524
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
525
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
526
+ <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
527
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
528
+ </Sidebar.Section>
529
+ <Sidebar.Section label="Settings">
530
+ <Sidebar.Item icon={<GearIcon />}>Preferences</Sidebar.Item>
531
+ <Sidebar.Item icon={<HelpIcon />}>Help</Sidebar.Item>
532
+ </Sidebar.Section>
533
+ </Sidebar.Nav>
534
+ <Sidebar.Footer>
535
+ <Sidebar.CollapseToggle />
536
+ </Sidebar.Footer>
537
+ </Sidebar>
538
+ <main style={mainContentStyle}>
539
+ Dashboard content goes here
540
+ </main>
541
+ </div>
542
+ ),
543
+ },
544
+ {
545
+ name: 'Collapsed',
546
+ description: 'Icon-only collapsed state. Header shows only logo, tooltips appear on hover.',
547
+ code: `function App() {
548
+ const [collapsed, setCollapsed] = useState(true);
549
+
550
+ return (
551
+ <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
552
+ <Sidebar.Header collapsedContent={<Logo />}>
553
+ <Logo />
554
+ <span>Acme App</span>
555
+ </Sidebar.Header>
556
+ <Sidebar.Nav>
557
+ <Sidebar.Section>
558
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
559
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
560
+ </Sidebar.Section>
561
+ </Sidebar.Nav>
562
+ <Sidebar.Footer>
563
+ <Sidebar.CollapseToggle />
564
+ </Sidebar.Footer>
565
+ </Sidebar>
566
+ );
567
+ }`,
568
+ render: () => <CollapsedDemo />,
569
+ },
570
+ {
571
+ name: 'With Badges',
572
+ description: 'Navigation items with notification badges for counts or alerts.',
573
+ code: `<Sidebar>
574
+ <Sidebar.Nav>
575
+ <Sidebar.Section>
576
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
577
+ <Sidebar.Item icon={<ChartIcon />} badge="3">Analytics</Sidebar.Item>
578
+ <Sidebar.Item icon={<UsersIcon />} badge="12">Team</Sidebar.Item>
579
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
580
+ </Sidebar.Section>
581
+ </Sidebar.Nav>
582
+ </Sidebar>`,
583
+ render: () => (
584
+ <div style={demoContainerStyle}>
585
+ <Sidebar>
586
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
587
+ <LogoIcon size={32} />
588
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
589
+ </Sidebar.Header>
590
+ <Sidebar.Nav>
591
+ <Sidebar.Section>
592
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
593
+ <Sidebar.Item icon={<ChartIcon />} badge="3">Analytics</Sidebar.Item>
594
+ <Sidebar.Item icon={<UsersIcon />} badge="12">Team</Sidebar.Item>
595
+ <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
596
+ </Sidebar.Section>
597
+ </Sidebar.Nav>
598
+ <Sidebar.Footer>
599
+ <Sidebar.CollapseToggle />
600
+ </Sidebar.Footer>
601
+ </Sidebar>
602
+ <main style={mainContentStyle}>
603
+ Badges indicate unread items or notifications
604
+ </main>
605
+ </div>
606
+ ),
607
+ },
608
+ {
609
+ name: 'With Submenu',
610
+ description: 'Nested navigation with expandable sections. Use defaultExpanded for initial state without manual state tracking.',
611
+ code: `<Sidebar>
612
+ <Sidebar.Nav>
613
+ <Sidebar.Section>
614
+ <Sidebar.Item icon={<HomeIcon />}>Dashboard</Sidebar.Item>
615
+ {/* Use defaultExpanded for uncontrolled mode - no state needed! */}
616
+ <Sidebar.Item icon={<FolderIcon />} hasSubmenu defaultExpanded>
617
+ Projects
618
+ </Sidebar.Item>
619
+ <Sidebar.Submenu>
620
+ <Sidebar.SubItem active>Website Redesign</Sidebar.SubItem>
621
+ <Sidebar.SubItem>Mobile App</Sidebar.SubItem>
622
+ <Sidebar.SubItem>API Integration</Sidebar.SubItem>
623
+ </Sidebar.Submenu>
624
+ {/* Multiple submenus work without tracking separate state */}
625
+ <Sidebar.Item icon={<UsersIcon />} hasSubmenu>
626
+ Team
627
+ </Sidebar.Item>
628
+ <Sidebar.Submenu>
629
+ <Sidebar.SubItem>Engineering</Sidebar.SubItem>
630
+ <Sidebar.SubItem>Design</Sidebar.SubItem>
631
+ </Sidebar.Submenu>
632
+ </Sidebar.Section>
633
+ </Sidebar.Nav>
634
+ </Sidebar>`,
635
+ render: () => <SubmenuDemo />,
636
+ },
637
+ {
638
+ name: 'With Disabled Items',
639
+ description: 'Some navigation items are disabled for permissions or feature flags.',
640
+ code: `<Sidebar>
641
+ <Sidebar.Nav>
642
+ <Sidebar.Section>
643
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
644
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
645
+ <Sidebar.Item icon={<UsersIcon />} disabled>
646
+ Team (Coming Soon)
647
+ </Sidebar.Item>
648
+ <Sidebar.Item icon={<FolderIcon />} disabled>
649
+ Projects (Upgrade)
650
+ </Sidebar.Item>
651
+ </Sidebar.Section>
652
+ </Sidebar.Nav>
653
+ </Sidebar>`,
654
+ render: () => (
655
+ <div style={demoContainerStyle}>
656
+ <Sidebar>
657
+ <Sidebar.Header collapsedContent={<LogoIcon size={32} />}>
658
+ <LogoIcon size={32} />
659
+ <span style={{ fontWeight: 600, fontSize: '16px' }}>Acme App</span>
660
+ </Sidebar.Header>
661
+ <Sidebar.Nav>
662
+ <Sidebar.Section>
663
+ <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
664
+ <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
665
+ <Sidebar.Item icon={<UsersIcon />} disabled>Team (Coming Soon)</Sidebar.Item>
666
+ <Sidebar.Item icon={<FolderIcon />} disabled>Projects (Upgrade)</Sidebar.Item>
667
+ </Sidebar.Section>
668
+ </Sidebar.Nav>
669
+ <Sidebar.Footer>
670
+ <Sidebar.CollapseToggle />
671
+ </Sidebar.Footer>
672
+ </Sidebar>
673
+ <main style={mainContentStyle}>
674
+ Disabled items cannot be clicked
675
+ </main>
676
+ </div>
677
+ ),
678
+ },
679
+ {
680
+ name: 'With Provider & External Trigger',
681
+ description: 'SidebarProvider enables external triggers and keyboard shortcuts (Cmd/Ctrl+B).',
682
+ code: `function App() {
683
+ return (
684
+ <SidebarProvider defaultCollapsed={false}>
685
+ <Sidebar>
686
+ {/* sidebar content */}
687
+ </Sidebar>
688
+ <MainContent />
689
+ </SidebarProvider>
690
+ );
691
+ }
692
+
693
+ function MainContent() {
694
+ const { toggleSidebar, state } = useSidebar();
695
+ return (
696
+ <main>
697
+ <p>State: {state}</p>
698
+ <button onClick={toggleSidebar}>Toggle</button>
699
+ </main>
700
+ );
701
+ }`,
702
+ render: () => <ProviderDemo />,
703
+ },
704
+ {
705
+ name: 'With asChild (Polymorphic)',
706
+ description: 'Use asChild to render items as custom elements like Next.js Link or React Router NavLink.',
707
+ code: `import { Link } from 'next/link';
708
+
709
+ <Sidebar>
710
+ <Sidebar.Nav>
711
+ <Sidebar.Section>
712
+ <Sidebar.Item icon={<HomeIcon />} active asChild>
713
+ <Link href="/dashboard">Dashboard</Link>
714
+ </Sidebar.Item>
715
+ <Sidebar.Item icon={<ChartIcon />} asChild>
716
+ <Link href="/analytics">Analytics</Link>
717
+ </Sidebar.Item>
718
+ </Sidebar.Section>
719
+ </Sidebar.Nav>
720
+ </Sidebar>`,
721
+ render: () => <AsChildDemo />,
722
+ },
723
+ {
724
+ name: 'With Section Action',
725
+ description: 'Section headers can include action buttons for quick actions like "Add Project".',
726
+ code: `<Sidebar>
727
+ <Sidebar.Nav>
728
+ <Sidebar.Section
729
+ label="Projects"
730
+ action={
731
+ <Sidebar.SectionAction aria-label="Add project" onClick={handleAdd}>
732
+ <PlusIcon />
733
+ </Sidebar.SectionAction>
734
+ }
735
+ >
736
+ <Sidebar.Item icon={<FolderIcon />}>Website Redesign</Sidebar.Item>
737
+ <Sidebar.Item icon={<FolderIcon />}>Mobile App</Sidebar.Item>
738
+ </Sidebar.Section>
739
+ </Sidebar.Nav>
740
+ </Sidebar>`,
741
+ render: () => <SectionActionDemo />,
742
+ },
743
+ {
744
+ name: 'With Loading Skeleton',
745
+ description: 'Show skeleton placeholders while navigation data is loading.',
746
+ code: `<Sidebar>
747
+ <Sidebar.Nav>
748
+ {loading ? (
749
+ <Sidebar.MenuSkeleton count={6} />
750
+ ) : (
751
+ <Sidebar.Section>
752
+ <Sidebar.Item icon={<HomeIcon />}>Dashboard</Sidebar.Item>
753
+ {/* ... */}
754
+ </Sidebar.Section>
755
+ )}
756
+ </Sidebar.Nav>
757
+ </Sidebar>`,
758
+ render: () => <SkeletonDemo />,
759
+ },
760
+ {
761
+ name: 'With Rail Toggle',
762
+ description: 'Add a Rail component for a subtle drag-handle style toggle at the sidebar edge. Hover to reveal, click to toggle.',
763
+ code: `<Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
764
+ <Sidebar.Header>{/* ... */}</Sidebar.Header>
765
+ <Sidebar.Nav>{/* ... */}</Sidebar.Nav>
766
+ <Sidebar.Footer>
767
+ <Sidebar.CollapseToggle />
768
+ </Sidebar.Footer>
769
+ <Sidebar.Rail />
770
+ </Sidebar>`,
771
+ render: () => <RailDemo />,
772
+ },
773
+ ],
774
+ });