@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.
@@ -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.