@ceed/cds 1.19.0 → 1.19.1
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.
- package/dist/components/data-display/Markdown.md +832 -0
- package/dist/components/feedback/Dialog.md +605 -3
- package/dist/components/feedback/Modal.md +559 -5
- package/dist/components/feedback/llms.txt +1 -1
- package/dist/components/inputs/Autocomplete.md +734 -2
- package/dist/components/inputs/Calendar.md +655 -1
- package/dist/components/inputs/DatePicker.md +699 -3
- package/dist/components/inputs/DateRangePicker.md +815 -1
- package/dist/components/inputs/MonthPicker.md +626 -4
- package/dist/components/inputs/MonthRangePicker.md +682 -4
- package/dist/components/inputs/Select.md +600 -0
- package/dist/components/layout/Container.md +507 -0
- package/dist/components/navigation/Breadcrumbs.md +470 -0
- package/dist/components/navigation/IconMenuButton.md +693 -0
- package/dist/components/navigation/InsetDrawer.md +775 -118
- package/dist/components/navigation/Link.md +434 -0
- package/dist/components/navigation/MenuButton.md +632 -0
- package/dist/components/navigation/NavigationGroup.md +401 -1
- package/dist/components/navigation/NavigationItem.md +311 -0
- package/dist/components/navigation/Navigator.md +373 -0
- package/dist/components/navigation/Pagination.md +532 -2
- package/dist/components/navigation/Tabs.md +466 -7
- package/dist/components/surfaces/Accordions.md +947 -3
- package/dist/llms.txt +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
NavigationItem is a single navigation element used within sidebar navigation systems. Built on Joy UI's ListItem and ListItemButton, it provides a clickable navigation item with support for icons, selection states, and nesting levels. NavigationItem is typically used within Navigator or as a standalone navigation element in custom sidebar implementations.
|
|
6
|
+
|
|
5
7
|
```text
|
|
6
8
|
// Unable to derive source for Default
|
|
7
9
|
```
|
|
@@ -14,4 +16,313 @@
|
|
|
14
16
|
|
|
15
17
|
```tsx
|
|
16
18
|
import { NavigationItem } from '@ceed/cds';
|
|
19
|
+
|
|
20
|
+
function Sidebar() {
|
|
21
|
+
return (
|
|
22
|
+
<NavigationItem
|
|
23
|
+
id="dashboard"
|
|
24
|
+
startDecorator={<DashboardIcon />}
|
|
25
|
+
selected={true}
|
|
26
|
+
onClick={(id) => console.log('Selected:', id)}
|
|
27
|
+
>
|
|
28
|
+
Dashboard
|
|
29
|
+
</NavigationItem>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### Default
|
|
37
|
+
|
|
38
|
+
Basic navigation item with text content.
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
// Unable to derive source for Default
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Selected State
|
|
45
|
+
|
|
46
|
+
Shows the visual indicator for the currently active navigation item.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
// Unable to derive source for Selected
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### With Start Decorator
|
|
53
|
+
|
|
54
|
+
Add an icon before the navigation item text.
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
// Unable to derive source for WithStartDecorator
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Nesting Levels
|
|
61
|
+
|
|
62
|
+
NavigationItem supports different indentation levels for hierarchical navigation.
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
// Unable to derive source for Level1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
// Unable to derive source for Level2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Levels with Icons
|
|
73
|
+
|
|
74
|
+
Icons work at any nesting level.
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
// Unable to derive source for Level1WithStartDecorator
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
// Unable to derive source for Level2WithStartDecorator
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## When to Use
|
|
85
|
+
|
|
86
|
+
### ✅ Good Use Cases
|
|
87
|
+
|
|
88
|
+
- **Sidebar navigation**: Individual menu items in a sidebar
|
|
89
|
+
- **Nested menus**: Items within expandable navigation groups
|
|
90
|
+
- **Settings navigation**: Links to different settings sections
|
|
91
|
+
- **Dashboard menus**: Quick access to different dashboard views
|
|
92
|
+
- **Custom navigation**: Building custom navigation components
|
|
93
|
+
|
|
94
|
+
### ❌ When Not to Use
|
|
95
|
+
|
|
96
|
+
- **Horizontal navigation**: Use Tabs or horizontal menu patterns instead
|
|
97
|
+
- **Action buttons**: Use Button for triggering actions, not navigation
|
|
98
|
+
- **Breadcrumbs**: Use Breadcrumbs component for path navigation
|
|
99
|
+
- **Dropdown menus**: Use Dropdown with Menu for popup menus
|
|
100
|
+
|
|
101
|
+
## Common Use Cases
|
|
102
|
+
|
|
103
|
+
### Sidebar with Selection State
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
function Sidebar({ currentPath }) {
|
|
107
|
+
const handleClick = (id) => {
|
|
108
|
+
navigate(`/${id}`);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<List>
|
|
113
|
+
<NavigationItem
|
|
114
|
+
id="dashboard"
|
|
115
|
+
startDecorator={<DashboardIcon />}
|
|
116
|
+
selected={currentPath === '/dashboard'}
|
|
117
|
+
onClick={handleClick}
|
|
118
|
+
>
|
|
119
|
+
Dashboard
|
|
120
|
+
</NavigationItem>
|
|
121
|
+
<NavigationItem
|
|
122
|
+
id="users"
|
|
123
|
+
startDecorator={<PeopleIcon />}
|
|
124
|
+
selected={currentPath === '/users'}
|
|
125
|
+
onClick={handleClick}
|
|
126
|
+
>
|
|
127
|
+
Users
|
|
128
|
+
</NavigationItem>
|
|
129
|
+
<NavigationItem
|
|
130
|
+
id="settings"
|
|
131
|
+
startDecorator={<SettingsIcon />}
|
|
132
|
+
selected={currentPath === '/settings'}
|
|
133
|
+
onClick={handleClick}
|
|
134
|
+
>
|
|
135
|
+
Settings
|
|
136
|
+
</NavigationItem>
|
|
137
|
+
</List>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Nested Navigation Items
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
function NestedNavigation() {
|
|
146
|
+
return (
|
|
147
|
+
<>
|
|
148
|
+
<NavigationItem id="reports" startDecorator={<ReportIcon />}>
|
|
149
|
+
Reports
|
|
150
|
+
</NavigationItem>
|
|
151
|
+
{/* Nested items at level 1 */}
|
|
152
|
+
<NavigationItem id="daily" level={1}>
|
|
153
|
+
Daily Reports
|
|
154
|
+
</NavigationItem>
|
|
155
|
+
<NavigationItem id="weekly" level={1}>
|
|
156
|
+
Weekly Reports
|
|
157
|
+
</NavigationItem>
|
|
158
|
+
<NavigationItem id="monthly" level={1}>
|
|
159
|
+
Monthly Reports
|
|
160
|
+
</NavigationItem>
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Dynamic Navigation from Data
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
function DynamicNavigation({ menuItems, selectedId, onSelect }) {
|
|
170
|
+
return (
|
|
171
|
+
<List>
|
|
172
|
+
{menuItems.map((item) => (
|
|
173
|
+
<NavigationItem
|
|
174
|
+
key={item.id}
|
|
175
|
+
id={item.id}
|
|
176
|
+
startDecorator={item.icon}
|
|
177
|
+
selected={selectedId === item.id}
|
|
178
|
+
level={item.level}
|
|
179
|
+
onClick={onSelect}
|
|
180
|
+
>
|
|
181
|
+
{item.label}
|
|
182
|
+
</NavigationItem>
|
|
183
|
+
))}
|
|
184
|
+
</List>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Props and Customization
|
|
190
|
+
|
|
191
|
+
### Key Props
|
|
192
|
+
|
|
193
|
+
| Prop | Type | Default | Description |
|
|
194
|
+
| ---------------- | ---------------------- | ------- | -------------------------------------- |
|
|
195
|
+
| `id` | `string` | - | Unique identifier for the item |
|
|
196
|
+
| `children` | `ReactNode` | - | Navigation item content/label |
|
|
197
|
+
| `startDecorator` | `ReactNode` | - | Icon or element before the label |
|
|
198
|
+
| `level` | `number` | `0` | Nesting level for indentation |
|
|
199
|
+
| `selected` | `boolean` | `false` | Whether the item is currently selected |
|
|
200
|
+
| `onClick` | `(id: string) => void` | - | Click handler receiving the item id |
|
|
201
|
+
|
|
202
|
+
### Indentation by Level
|
|
203
|
+
|
|
204
|
+
NavigationItem automatically applies indentation based on the `level` prop:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
// Root level (no indentation)
|
|
208
|
+
<NavigationItem id="home" level={0}>Home</NavigationItem>
|
|
209
|
+
|
|
210
|
+
// First level (slight indentation)
|
|
211
|
+
<NavigationItem id="sub1" level={1}>Sub Item 1</NavigationItem>
|
|
212
|
+
|
|
213
|
+
// Second level (more indentation)
|
|
214
|
+
<NavigationItem id="sub2" level={2}>Sub Item 2</NavigationItem>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Custom Styling
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<NavigationItem
|
|
221
|
+
id="custom"
|
|
222
|
+
sx={{
|
|
223
|
+
'& .NavigationItem-Button': {
|
|
224
|
+
borderRadius: 'md',
|
|
225
|
+
},
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
Custom Styled Item
|
|
229
|
+
</NavigationItem>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Accessibility
|
|
233
|
+
|
|
234
|
+
NavigationItem includes built-in accessibility features:
|
|
235
|
+
|
|
236
|
+
### ARIA Attributes
|
|
237
|
+
|
|
238
|
+
- Uses `aria-current` to indicate the selected item
|
|
239
|
+
- Built on semantic list item structure
|
|
240
|
+
- Proper button role for clickable behavior
|
|
241
|
+
|
|
242
|
+
### Keyboard Navigation
|
|
243
|
+
|
|
244
|
+
- **Tab**: Focus the navigation item
|
|
245
|
+
- **Enter/Space**: Activate the item (trigger onClick)
|
|
246
|
+
|
|
247
|
+
### Screen Reader Support
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
// Selected state is announced
|
|
251
|
+
<NavigationItem selected={true}>
|
|
252
|
+
Dashboard {/* Announced as "Dashboard, current page" */}
|
|
253
|
+
</NavigationItem>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Best Practices
|
|
257
|
+
|
|
258
|
+
### ✅ Do
|
|
259
|
+
|
|
260
|
+
1. **Provide unique IDs**: Each item needs a unique identifier for selection tracking
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// ✅ Good: Unique IDs
|
|
264
|
+
<NavigationItem id="users">Users</NavigationItem>
|
|
265
|
+
<NavigationItem id="settings">Settings</NavigationItem>
|
|
17
266
|
```
|
|
267
|
+
|
|
268
|
+
2. **Use consistent icons**: Either all items have icons or none
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
// ✅ Good: All items have icons
|
|
272
|
+
<NavigationItem id="home" startDecorator={<HomeIcon />}>Home</NavigationItem>
|
|
273
|
+
<NavigationItem id="users" startDecorator={<PeopleIcon />}>Users</NavigationItem>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
3. **Keep labels concise**: Short, descriptive labels work best
|
|
277
|
+
|
|
278
|
+
4. **Manage selection at parent level**: Track selected state in the parent component
|
|
279
|
+
|
|
280
|
+
### ❌ Don't
|
|
281
|
+
|
|
282
|
+
1. **Don't nest NavigationItem inside NavigationItem**: Use the `level` prop instead
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
// ❌ Bad: Nested components
|
|
286
|
+
<NavigationItem>
|
|
287
|
+
Parent
|
|
288
|
+
<NavigationItem>Child</NavigationItem>
|
|
289
|
+
</NavigationItem>
|
|
290
|
+
|
|
291
|
+
// ✅ Good: Use level prop
|
|
292
|
+
<NavigationItem id="parent">Parent</NavigationItem>
|
|
293
|
+
<NavigationItem id="child" level={1}>Child</NavigationItem>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
2. **Don't use for non-navigation purposes**: Use Button for actions
|
|
297
|
+
|
|
298
|
+
3. **Don't skip levels**: Go from level 0 to 1 to 2, not 0 to 2
|
|
299
|
+
|
|
300
|
+
## Performance Considerations
|
|
301
|
+
|
|
302
|
+
### Memoize Click Handlers
|
|
303
|
+
|
|
304
|
+
When rendering many items, memoize the click handler:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
const handleClick = useCallback((id) => {
|
|
308
|
+
setSelectedId(id);
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
311
|
+
// Pass stable handler to all items
|
|
312
|
+
<NavigationItem id="item1" onClick={handleClick}>Item 1</NavigationItem>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Avoid Inline Objects
|
|
316
|
+
|
|
317
|
+
Don't create new decorator elements on each render:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
// ❌ Bad: New element created each render
|
|
321
|
+
<NavigationItem startDecorator={<HomeIcon />}>Home</NavigationItem>
|
|
322
|
+
|
|
323
|
+
// ✅ Good: Memoized or static
|
|
324
|
+
const homeIcon = <HomeIcon />;
|
|
325
|
+
<NavigationItem startDecorator={homeIcon}>Home</NavigationItem>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
NavigationItem provides a foundational building block for sidebar navigation. Use it within Navigator for automatic item management, or combine it with NavigationGroup for custom hierarchical navigation structures.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
The Navigator component is a sidebar navigation system that renders hierarchical navigation menus. It combines NavigationItem and NavigationGroup components to create expandable, nested navigation structures commonly found in admin dashboards and complex applications. Navigator automatically handles indentation levels and provides a consistent visual hierarchy for multi-level navigation.
|
|
6
|
+
|
|
5
7
|
```text
|
|
6
8
|
// Unable to derive source for Playground
|
|
7
9
|
```
|
|
@@ -14,4 +16,375 @@
|
|
|
14
16
|
|
|
15
17
|
```tsx
|
|
16
18
|
import { Navigator } from '@ceed/cds';
|
|
19
|
+
|
|
20
|
+
function Sidebar() {
|
|
21
|
+
const items = [
|
|
22
|
+
{ id: 'home', type: 'item', title: 'Home', startDecorator: <HomeIcon /> },
|
|
23
|
+
{
|
|
24
|
+
type: 'group',
|
|
25
|
+
title: 'Projects',
|
|
26
|
+
startDecorator: <FolderIcon />,
|
|
27
|
+
content: (
|
|
28
|
+
<Navigator
|
|
29
|
+
items={[
|
|
30
|
+
{ id: 'project-a', type: 'item', title: 'Project A' },
|
|
31
|
+
{ id: 'project-b', type: 'item', title: 'Project B' },
|
|
32
|
+
]}
|
|
33
|
+
level={1}
|
|
34
|
+
onSelect={handleSelect}
|
|
35
|
+
/>
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
return <Navigator items={items} onSelect={handleSelect} />;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
### Basic Navigation
|
|
47
|
+
|
|
48
|
+
A simple navigation with items and a collapsible group.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
// Unable to derive source for Playground
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Default Expanded
|
|
55
|
+
|
|
56
|
+
Groups can be expanded by default using the `expanded` property.
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
// Unable to derive source for DefaultExpanded
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## When to Use
|
|
63
|
+
|
|
64
|
+
### ✅ Good Use Cases
|
|
65
|
+
|
|
66
|
+
- **Admin dashboards**: Sidebar navigation for complex admin interfaces
|
|
67
|
+
- **Nested content structures**: When content has hierarchical relationships
|
|
68
|
+
- **Multi-section applications**: Apps with distinct modules or feature areas
|
|
69
|
+
- **File/folder structures**: Representing folder hierarchies in file managers
|
|
70
|
+
- **Settings pages**: Organizing settings into categories and subcategories
|
|
71
|
+
|
|
72
|
+
### ❌ When Not to Use
|
|
73
|
+
|
|
74
|
+
- **Primary navigation bars**: Use horizontal navigation or tabs instead
|
|
75
|
+
- **Simple sites**: Sites with fewer than 5 navigation items don't need hierarchy
|
|
76
|
+
- **Mobile-first layouts**: Consider drawer navigation or bottom tabs instead
|
|
77
|
+
- **Flat navigation**: When all items are at the same level, use simple list
|
|
78
|
+
- **Wizard/step flows**: Use Stepper for sequential processes
|
|
79
|
+
|
|
80
|
+
## Common Use Cases
|
|
81
|
+
|
|
82
|
+
### Admin Dashboard Sidebar
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
function AdminSidebar() {
|
|
86
|
+
const [selectedId, setSelectedId] = useState('dashboard');
|
|
87
|
+
|
|
88
|
+
const items = [
|
|
89
|
+
{
|
|
90
|
+
id: 'dashboard',
|
|
91
|
+
type: 'item',
|
|
92
|
+
title: 'Dashboard',
|
|
93
|
+
startDecorator: <DashboardIcon />,
|
|
94
|
+
selected: selectedId === 'dashboard',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'group',
|
|
98
|
+
title: 'User Management',
|
|
99
|
+
startDecorator: <PeopleIcon />,
|
|
100
|
+
content: (
|
|
101
|
+
<Navigator
|
|
102
|
+
level={1}
|
|
103
|
+
onSelect={setSelectedId}
|
|
104
|
+
items={[
|
|
105
|
+
{ id: 'users', type: 'item', title: 'All Users', selected: selectedId === 'users' },
|
|
106
|
+
{ id: 'roles', type: 'item', title: 'Roles', selected: selectedId === 'roles' },
|
|
107
|
+
{ id: 'permissions', type: 'item', title: 'Permissions', selected: selectedId === 'permissions' },
|
|
108
|
+
]}
|
|
109
|
+
/>
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'group',
|
|
114
|
+
title: 'Settings',
|
|
115
|
+
startDecorator: <SettingsIcon />,
|
|
116
|
+
content: (
|
|
117
|
+
<Navigator
|
|
118
|
+
level={1}
|
|
119
|
+
onSelect={setSelectedId}
|
|
120
|
+
items={[
|
|
121
|
+
{ id: 'general', type: 'item', title: 'General', selected: selectedId === 'general' },
|
|
122
|
+
{ id: 'security', type: 'item', title: 'Security', selected: selectedId === 'security' },
|
|
123
|
+
]}
|
|
124
|
+
/>
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
return <Navigator items={items} onSelect={setSelectedId} />;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### File Browser Navigation
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
function FileBrowser({ folders, onFolderSelect }) {
|
|
137
|
+
const buildNavigatorItems = (folderList, level = 0) => {
|
|
138
|
+
return folderList.map((folder) => {
|
|
139
|
+
if (folder.children && folder.children.length > 0) {
|
|
140
|
+
return {
|
|
141
|
+
type: 'group',
|
|
142
|
+
title: folder.name,
|
|
143
|
+
startDecorator: <FolderIcon />,
|
|
144
|
+
expanded: folder.expanded,
|
|
145
|
+
content: (
|
|
146
|
+
<Navigator
|
|
147
|
+
level={level + 1}
|
|
148
|
+
items={buildNavigatorItems(folder.children, level + 1)}
|
|
149
|
+
onSelect={onFolderSelect}
|
|
150
|
+
/>
|
|
151
|
+
),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
id: folder.id,
|
|
156
|
+
type: 'item',
|
|
157
|
+
title: folder.name,
|
|
158
|
+
startDecorator: <FolderIcon />,
|
|
159
|
+
selected: folder.selected,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<Navigator
|
|
166
|
+
items={buildNavigatorItems(folders)}
|
|
167
|
+
onSelect={onFolderSelect}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Multi-level Documentation Navigation
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
function DocsNavigation({ sections, currentPath }) {
|
|
177
|
+
const items = sections.map((section) => ({
|
|
178
|
+
type: 'group',
|
|
179
|
+
title: section.title,
|
|
180
|
+
startDecorator: <ArticleIcon />,
|
|
181
|
+
expanded: currentPath.startsWith(section.path),
|
|
182
|
+
content: (
|
|
183
|
+
<Navigator
|
|
184
|
+
level={1}
|
|
185
|
+
onSelect={navigateTo}
|
|
186
|
+
items={section.pages.map((page) => ({
|
|
187
|
+
id: page.path,
|
|
188
|
+
type: 'item',
|
|
189
|
+
title: page.title,
|
|
190
|
+
selected: currentPath === page.path,
|
|
191
|
+
}))}
|
|
192
|
+
/>
|
|
193
|
+
),
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
return <Navigator items={items} onSelect={navigateTo} />;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Component Structure
|
|
201
|
+
|
|
202
|
+
Navigator uses a composition pattern with NavigationItem and NavigationGroup:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<Navigator>
|
|
206
|
+
{/* Renders internally: */}
|
|
207
|
+
<NavigationItem> {/* For type: 'item' */}
|
|
208
|
+
<NavigationGroup> {/* For type: 'group' */}
|
|
209
|
+
{content} {/* Usually another Navigator for nesting */}
|
|
210
|
+
</NavigationGroup>
|
|
211
|
+
</Navigator>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Props and Customization
|
|
215
|
+
|
|
216
|
+
### Navigator Props
|
|
217
|
+
|
|
218
|
+
| Prop | Type | Default | Description |
|
|
219
|
+
| ---------- | ------------------------------------- | ------- | --------------------------------- |
|
|
220
|
+
| `items` | `(NavigatorItem \| NavigatorGroup)[]` | `[]` | Array of navigation items |
|
|
221
|
+
| `level` | `number` | `0` | Nesting level for indentation |
|
|
222
|
+
| `onSelect` | `(id: string) => void` | - | Callback when an item is selected |
|
|
223
|
+
|
|
224
|
+
### Navigator Item Type
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
interface NavigatorItem {
|
|
228
|
+
id: string; // Unique identifier
|
|
229
|
+
type: 'item'; // Indicates a navigation item
|
|
230
|
+
title: string | React.ReactNode; // Display text or element
|
|
231
|
+
startDecorator?: React.ReactNode; // Icon before title
|
|
232
|
+
selected?: boolean; // Selection state
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Navigator Group Type
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
interface NavigatorGroup {
|
|
240
|
+
type: 'group'; // Indicates a group
|
|
241
|
+
title: string | React.ReactNode; // Group title
|
|
242
|
+
startDecorator?: React.ReactNode; // Icon before title
|
|
243
|
+
expanded?: boolean; // Default expanded state
|
|
244
|
+
content: React.ReactNode; // Content when expanded (usually nested Navigator)
|
|
245
|
+
}
|
|
17
246
|
```
|
|
247
|
+
|
|
248
|
+
### Nested Navigation Pattern
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// Create nested navigation by passing higher level values
|
|
252
|
+
<Navigator
|
|
253
|
+
level={0} // Root level
|
|
254
|
+
items={[
|
|
255
|
+
{
|
|
256
|
+
type: 'group',
|
|
257
|
+
title: 'Section',
|
|
258
|
+
content: (
|
|
259
|
+
<Navigator
|
|
260
|
+
level={1} // First nesting level
|
|
261
|
+
items={[...]}
|
|
262
|
+
onSelect={handleSelect}
|
|
263
|
+
/>
|
|
264
|
+
),
|
|
265
|
+
},
|
|
266
|
+
]}
|
|
267
|
+
onSelect={handleSelect}
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Accessibility
|
|
272
|
+
|
|
273
|
+
Navigator components follow accessibility best practices:
|
|
274
|
+
|
|
275
|
+
### ARIA Attributes
|
|
276
|
+
|
|
277
|
+
- NavigationItem uses `aria-current` for the selected item
|
|
278
|
+
- NavigationGroup uses accordion ARIA patterns for expand/collapse
|
|
279
|
+
- Proper focus management within navigation
|
|
280
|
+
|
|
281
|
+
### Keyboard Navigation
|
|
282
|
+
|
|
283
|
+
- **Tab**: Move focus through navigation items
|
|
284
|
+
- **Enter/Space**: Select item or toggle group
|
|
285
|
+
- **Arrow Up/Down**: Navigate between items (when accordion is focused)
|
|
286
|
+
|
|
287
|
+
### Screen Reader Support
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// Items are announced with their selection state
|
|
291
|
+
<NavigationItem selected={true}>
|
|
292
|
+
Dashboard {/* Announced as current page */}
|
|
293
|
+
</NavigationItem>
|
|
294
|
+
|
|
295
|
+
// Groups announce their expanded state
|
|
296
|
+
<NavigationGroup expanded={true}>
|
|
297
|
+
Settings {/* Announced as expanded */}
|
|
298
|
+
</NavigationGroup>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Best Practices
|
|
302
|
+
|
|
303
|
+
### ✅ Do
|
|
304
|
+
|
|
305
|
+
1. **Use icons consistently**: Either use icons for all items or none
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
// ✅ Good: Consistent icons
|
|
309
|
+
const items = [
|
|
310
|
+
{ id: 'home', type: 'item', title: 'Home', startDecorator: <HomeIcon /> },
|
|
311
|
+
{ id: 'settings', type: 'item', title: 'Settings', startDecorator: <SettingsIcon /> },
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
// ❌ Bad: Inconsistent icons
|
|
315
|
+
const items = [
|
|
316
|
+
{ id: 'home', type: 'item', title: 'Home', startDecorator: <HomeIcon /> },
|
|
317
|
+
{ id: 'settings', type: 'item', title: 'Settings' }, // Missing icon
|
|
318
|
+
];
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
2. **Manage selection state in parent**: Keep track of selected item at the top level
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
function Sidebar() {
|
|
325
|
+
const [selectedId, setSelectedId] = useState('home');
|
|
326
|
+
|
|
327
|
+
// Pass selected state down through items
|
|
328
|
+
const items = buildItems(selectedId);
|
|
329
|
+
|
|
330
|
+
return <Navigator items={items} onSelect={setSelectedId} />;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
3. **Pass onSelect to nested Navigators**: Ensure child navigators also receive the callback
|
|
335
|
+
|
|
336
|
+
4. **Limit nesting depth**: 2-3 levels maximum for usability
|
|
337
|
+
|
|
338
|
+
### ❌ Don't
|
|
339
|
+
|
|
340
|
+
1. **Don't over-nest**: Deep nesting makes navigation confusing
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// ❌ Bad: Too many levels
|
|
344
|
+
<Navigator level={0}>
|
|
345
|
+
<Navigator level={1}>
|
|
346
|
+
<Navigator level={2}>
|
|
347
|
+
<Navigator level={3}> {/* Too deep */}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
2. **Don't mix navigation patterns**: Stick to either all items or items with groups
|
|
351
|
+
|
|
352
|
+
3. **Don't use for actions**: Navigator is for navigation, not for triggering actions
|
|
353
|
+
|
|
354
|
+
## Performance Considerations
|
|
355
|
+
|
|
356
|
+
### Memoize Items Array
|
|
357
|
+
|
|
358
|
+
When items are computed, memoize them to prevent unnecessary re-renders:
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
const items = useMemo(() => [
|
|
362
|
+
{
|
|
363
|
+
id: 'home',
|
|
364
|
+
type: 'item',
|
|
365
|
+
title: 'Home',
|
|
366
|
+
selected: currentPath === '/home',
|
|
367
|
+
},
|
|
368
|
+
// ... more items
|
|
369
|
+
], [currentPath]);
|
|
370
|
+
|
|
371
|
+
<Navigator items={items} onSelect={handleSelect} />
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Lazy Load Content
|
|
375
|
+
|
|
376
|
+
For groups with heavy content, consider lazy loading:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
const item = {
|
|
380
|
+
type: 'group',
|
|
381
|
+
title: 'Reports',
|
|
382
|
+
content: expanded ? <ReportsList /> : null,
|
|
383
|
+
};
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Virtual Scrolling
|
|
387
|
+
|
|
388
|
+
For very long navigation lists, consider implementing virtual scrolling or pagination.
|
|
389
|
+
|
|
390
|
+
Navigator provides a flexible foundation for building hierarchical navigation systems. Use it alongside NavigationItem and NavigationGroup for maximum control, or let Navigator handle the rendering automatically based on your items array.
|