@ceed/cds 1.22.2 → 1.22.4
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/InfoSign.md +74 -91
- package/dist/components/data-display/Typography.md +363 -63
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Dialog.md +8 -4
- package/dist/components/feedback/Modal.md +7 -3
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -104
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterableCheckboxGroup.md +141 -20
- package/dist/components/inputs/FormControl.md +368 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/Input.md +203 -77
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +143 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +57 -308
- package/dist/components/navigation/Drawer.md +180 -0
- package/dist/components/navigation/Dropdown.md +98 -215
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +281 -650
- package/dist/components/navigation/Link.md +31 -348
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Accordions.md +49 -804
- package/dist/components/surfaces/Card.md +97 -157
- package/dist/components/surfaces/Divider.md +83 -234
- package/dist/components/surfaces/Sheet.md +152 -327
- package/dist/guides/ThemeProvider.md +89 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
The Breadcrumbs component displays a navigation trail that helps users understand their current location within a
|
|
5
|
+
The Breadcrumbs component displays a navigation trail that helps users understand their current location within a hierarchical structure and navigate back to previous levels. Each level in the path is represented as a clickable link except for the current page, which is displayed as plain text.
|
|
6
|
+
|
|
7
|
+
Breadcrumbs are essential for sites with deep navigation hierarchies -- they provide context at a glance, reduce the number of steps needed to return to higher-level pages, and improve overall wayfinding within an application.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Breadcrumbs
|
|
@@ -56,26 +58,18 @@ function MyComponent() {
|
|
|
56
58
|
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
57
59
|
{ label: 'Products', type: 'link', linkHref: '/products' },
|
|
58
60
|
{ label: 'Electronics', type: 'link', linkHref: '/products/electronics' },
|
|
59
|
-
{ label: 'Laptops', type: 'text' },
|
|
61
|
+
{ label: 'Laptops', type: 'text' },
|
|
60
62
|
];
|
|
61
63
|
|
|
62
64
|
return <Breadcrumbs crumbs={crumbs} />;
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
##
|
|
67
|
-
|
|
68
|
-
### Basic Breadcrumbs
|
|
69
|
-
|
|
70
|
-
A simple breadcrumb navigation with three levels.
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
<Canvas of={Breadcrumbs.BasicExample} />
|
|
74
|
-
```
|
|
68
|
+
## Features
|
|
75
69
|
|
|
76
70
|
### Sizes
|
|
77
71
|
|
|
78
|
-
Breadcrumbs
|
|
72
|
+
Breadcrumbs are available in three sizes -- `sm`, `md`, and `lg` -- allowing you to match the surrounding layout density.
|
|
79
73
|
|
|
80
74
|
```tsx
|
|
81
75
|
<div>
|
|
@@ -85,17 +79,9 @@ Breadcrumbs support different sizes.
|
|
|
85
79
|
</div>
|
|
86
80
|
```
|
|
87
81
|
|
|
88
|
-
###
|
|
89
|
-
|
|
90
|
-
Customize the separator between breadcrumb items.
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
<Canvas of={Breadcrumbs.CustomSeparator} />
|
|
94
|
-
```
|
|
82
|
+
### Custom Separator
|
|
95
83
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
For deep hierarchies, breadcrumbs can be collapsed with an ellipsis.
|
|
84
|
+
The separator character between items can be customized via the `separator` prop. Common choices include `/`, `>`, and `-`.
|
|
99
85
|
|
|
100
86
|
```tsx
|
|
101
87
|
<Breadcrumbs
|
|
@@ -128,34 +114,13 @@ For deep hierarchies, breadcrumbs can be collapsed with an ellipsis.
|
|
|
128
114
|
type: 'text'
|
|
129
115
|
}]}
|
|
130
116
|
collapsed
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Collapsed Variants
|
|
135
|
-
|
|
136
|
-
Configure how many items show at the start and end when collapsed.
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
<Canvas of={Breadcrumbs.CollapsedVariants} />
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Single Crumb
|
|
143
|
-
|
|
144
|
-
When there's only one item in the path.
|
|
145
|
-
|
|
146
|
-
```tsx
|
|
147
|
-
<Breadcrumbs
|
|
148
|
-
crumbs={[{
|
|
149
|
-
label: 'Home',
|
|
150
|
-
type: 'text'
|
|
151
|
-
}]}
|
|
152
|
-
collapsed
|
|
117
|
+
separator="-"
|
|
153
118
|
/>
|
|
154
119
|
```
|
|
155
120
|
|
|
156
121
|
### Expanded View
|
|
157
122
|
|
|
158
|
-
|
|
123
|
+
Setting `collapsed={false}` renders every item in the path without collapsing.
|
|
159
124
|
|
|
160
125
|
```tsx
|
|
161
126
|
<Breadcrumbs
|
|
@@ -191,105 +156,56 @@ Show all breadcrumb items without collapsing.
|
|
|
191
156
|
/>
|
|
192
157
|
```
|
|
193
158
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
### ✅ Good Use Cases
|
|
197
|
-
|
|
198
|
-
- **Deep hierarchies**: Sites with 3+ levels of navigation depth
|
|
199
|
-
- **E-commerce sites**: Product category → subcategory → product navigation
|
|
200
|
-
- **Documentation sites**: Section → topic → article navigation
|
|
201
|
-
- **File management**: Folder → subfolder → file navigation
|
|
202
|
-
- **Admin dashboards**: Module → submodule → detail view
|
|
203
|
-
- **Content management**: Categories and nested content structures
|
|
159
|
+
### Single Crumb
|
|
204
160
|
|
|
205
|
-
|
|
161
|
+
The component handles the edge case of a single breadcrumb item gracefully.
|
|
206
162
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
163
|
+
```tsx
|
|
164
|
+
<Breadcrumbs
|
|
165
|
+
crumbs={[{
|
|
166
|
+
label: 'Home',
|
|
167
|
+
type: 'text'
|
|
168
|
+
}]}
|
|
169
|
+
collapsed
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
212
172
|
|
|
213
173
|
## Common Use Cases
|
|
214
174
|
|
|
215
|
-
###
|
|
175
|
+
### Service Detail Page
|
|
216
176
|
|
|
217
177
|
```tsx
|
|
218
|
-
function
|
|
178
|
+
function ServiceDetailPage({ service, category }) {
|
|
219
179
|
const crumbs = [
|
|
220
180
|
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
221
|
-
{ label: '
|
|
222
|
-
{ label: category.name, type: 'link', linkHref: `/
|
|
223
|
-
{ label:
|
|
224
|
-
{ label: product.name, type: 'text' },
|
|
181
|
+
{ label: 'Services', type: 'link', linkHref: '/services' },
|
|
182
|
+
{ label: category.name, type: 'link', linkHref: `/services/${category.slug}` },
|
|
183
|
+
{ label: service.name, type: 'text' },
|
|
225
184
|
];
|
|
226
185
|
|
|
227
186
|
return (
|
|
228
187
|
<Box>
|
|
229
188
|
<Breadcrumbs crumbs={crumbs} />
|
|
230
|
-
<
|
|
231
|
-
</Box>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Documentation Page
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
function DocPage({ section, topic, article }) {
|
|
240
|
-
const crumbs = [
|
|
241
|
-
{ label: 'Docs', type: 'link', linkHref: '/docs' },
|
|
242
|
-
{ label: section.title, type: 'link', linkHref: `/docs/${section.slug}` },
|
|
243
|
-
{ label: topic.title, type: 'link', linkHref: `/docs/${section.slug}/${topic.slug}` },
|
|
244
|
-
{ label: article.title, type: 'text' },
|
|
245
|
-
];
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<Stack spacing={2}>
|
|
249
|
-
<Breadcrumbs crumbs={crumbs} size="sm" />
|
|
250
|
-
<Typography level="h1">{article.title}</Typography>
|
|
251
|
-
<ArticleContent content={article.content} />
|
|
252
|
-
</Stack>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### File Browser
|
|
258
|
-
|
|
259
|
-
```tsx
|
|
260
|
-
function FileBrowser({ path }) {
|
|
261
|
-
const crumbs = [
|
|
262
|
-
{ label: 'Root', type: 'link', linkHref: '/files' },
|
|
263
|
-
...path.map((folder, index) => ({
|
|
264
|
-
label: folder.name,
|
|
265
|
-
type: index === path.length - 1 ? 'text' : 'link',
|
|
266
|
-
linkHref: `/files/${path.slice(0, index + 1).map(f => f.id).join('/')}`,
|
|
267
|
-
})),
|
|
268
|
-
];
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<Box>
|
|
272
|
-
<Breadcrumbs crumbs={crumbs} collapsed={path.length > 4} />
|
|
273
|
-
<FileList folderId={path[path.length - 1].id} />
|
|
189
|
+
<ServiceDetails service={service} />
|
|
274
190
|
</Box>
|
|
275
191
|
);
|
|
276
192
|
}
|
|
277
193
|
```
|
|
278
194
|
|
|
279
|
-
###
|
|
195
|
+
### Account Settings
|
|
280
196
|
|
|
281
197
|
```tsx
|
|
282
|
-
function
|
|
198
|
+
function AccountSettingsPage() {
|
|
283
199
|
const crumbs = [
|
|
284
|
-
{ label: '
|
|
285
|
-
{ label: '
|
|
286
|
-
{ label:
|
|
200
|
+
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
201
|
+
{ label: 'Account', type: 'link', linkHref: '/account' },
|
|
202
|
+
{ label: 'Settings', type: 'text' },
|
|
287
203
|
];
|
|
288
204
|
|
|
289
205
|
return (
|
|
290
206
|
<Box>
|
|
291
|
-
<Breadcrumbs crumbs={crumbs} />
|
|
292
|
-
<
|
|
207
|
+
<Breadcrumbs crumbs={crumbs} size="sm" />
|
|
208
|
+
<SettingsForm />
|
|
293
209
|
</Box>
|
|
294
210
|
);
|
|
295
211
|
}
|
|
@@ -300,222 +216,55 @@ function UserDetailPage({ user }) {
|
|
|
300
216
|
```tsx
|
|
301
217
|
function DynamicBreadcrumbs() {
|
|
302
218
|
const location = useLocation();
|
|
303
|
-
const pathnames = location.pathname.split('/').filter(
|
|
219
|
+
const pathnames = location.pathname.split('/').filter(Boolean);
|
|
304
220
|
|
|
305
221
|
const crumbs = [
|
|
306
222
|
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
307
|
-
...pathnames.map((value, index) => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
label: formatLabel(value),
|
|
313
|
-
type: isLast ? 'text' : 'link',
|
|
314
|
-
linkHref: href,
|
|
315
|
-
};
|
|
316
|
-
}),
|
|
223
|
+
...pathnames.map((value, index) => ({
|
|
224
|
+
label: formatLabel(value),
|
|
225
|
+
type: index === pathnames.length - 1 ? 'text' : 'link',
|
|
226
|
+
linkHref: `/${pathnames.slice(0, index + 1).join('/')}`,
|
|
227
|
+
})),
|
|
317
228
|
];
|
|
318
229
|
|
|
319
230
|
return <Breadcrumbs crumbs={crumbs} />;
|
|
320
231
|
}
|
|
321
232
|
```
|
|
322
233
|
|
|
323
|
-
## Props and Customization
|
|
324
|
-
|
|
325
|
-
### Key Props
|
|
326
|
-
|
|
327
|
-
| Prop | Type | Default | Description |
|
|
328
|
-
| ----------------- | ---------------------- | ------- | ----------------------------------------------- |
|
|
329
|
-
| `crumbs` | `Crumb[]` | `[]` | Array of breadcrumb items |
|
|
330
|
-
| `collapsed` | `boolean` | `true` | Collapse middle items with ellipsis |
|
|
331
|
-
| `startCrumbCount` | `number` | `1` | Number of items to show at start when collapsed |
|
|
332
|
-
| `endCrumbCount` | `number` | `3` | Number of items to show at end when collapsed |
|
|
333
|
-
| `separator` | `string` | `'/'` | Separator between items |
|
|
334
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the breadcrumbs |
|
|
335
|
-
|
|
336
|
-
### Crumb Type
|
|
337
|
-
|
|
338
|
-
```tsx
|
|
339
|
-
interface Crumb {
|
|
340
|
-
label: string; // Display text
|
|
341
|
-
type: 'link' | 'text'; // Link or static text
|
|
342
|
-
linkHref?: string; // URL for link type
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Collapsed Behavior
|
|
347
|
-
|
|
348
|
-
When `collapsed` is true and there are more items than `startCrumbCount + endCrumbCount`, middle items are replaced with an ellipsis:
|
|
349
|
-
|
|
350
|
-
```tsx
|
|
351
|
-
// With startCrumbCount=1, endCrumbCount=2, and 6 crumbs:
|
|
352
|
-
// Home / ... / Category / Product
|
|
353
|
-
|
|
354
|
-
<Breadcrumbs
|
|
355
|
-
crumbs={crumbs}
|
|
356
|
-
collapsed={true}
|
|
357
|
-
startCrumbCount={1}
|
|
358
|
-
endCrumbCount={2}
|
|
359
|
-
/>
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### Custom Styling
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
<Breadcrumbs
|
|
366
|
-
crumbs={crumbs}
|
|
367
|
-
sx={{
|
|
368
|
-
'& .MuiBreadcrumbs-separator': {
|
|
369
|
-
mx: 1,
|
|
370
|
-
color: 'neutral.400',
|
|
371
|
-
},
|
|
372
|
-
'& a': {
|
|
373
|
-
color: 'primary.500',
|
|
374
|
-
textDecoration: 'none',
|
|
375
|
-
'&:hover': {
|
|
376
|
-
textDecoration: 'underline',
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
}}
|
|
380
|
-
/>
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
## Accessibility
|
|
384
|
-
|
|
385
|
-
Breadcrumbs include important accessibility features:
|
|
386
|
-
|
|
387
|
-
### Semantic HTML
|
|
388
|
-
|
|
389
|
-
- Uses `<nav>` element with `aria-label="breadcrumb"`
|
|
390
|
-
- Structured as an ordered list (`<ol>`) to indicate sequence
|
|
391
|
-
- Current page marked with `aria-current="page"`
|
|
392
|
-
|
|
393
|
-
### ARIA Attributes
|
|
394
|
-
|
|
395
|
-
```tsx
|
|
396
|
-
// Generated HTML structure
|
|
397
|
-
<nav aria-label="breadcrumb">
|
|
398
|
-
<ol>
|
|
399
|
-
<li><a href="/">Home</a></li>
|
|
400
|
-
<li><a href="/products">Products</a></li>
|
|
401
|
-
<li aria-current="page">Current Page</li>
|
|
402
|
-
</ol>
|
|
403
|
-
</nav>
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Keyboard Navigation
|
|
407
|
-
|
|
408
|
-
- **Tab**: Navigate through breadcrumb links
|
|
409
|
-
- **Enter**: Activate the focused link
|
|
410
|
-
|
|
411
|
-
### Screen Reader Support
|
|
412
|
-
|
|
413
|
-
- Screen readers announce "breadcrumb navigation"
|
|
414
|
-
- Each link is announced with its position in the sequence
|
|
415
|
-
- Current page is identified as the current location
|
|
416
|
-
|
|
417
|
-
### SEO Benefits
|
|
418
|
-
|
|
419
|
-
Breadcrumbs provide:
|
|
420
|
-
|
|
421
|
-
- Clear site structure for search engines
|
|
422
|
-
- Enhanced search result display (rich snippets)
|
|
423
|
-
- Improved internal linking
|
|
424
|
-
|
|
425
234
|
## Best Practices
|
|
426
235
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
1. **Start with Home**: Always begin with a link to the homepage
|
|
236
|
+
- **Always start with a root item.** Begin the trail with "Home" or an equivalent top-level link so users can always navigate back to the starting point.
|
|
430
237
|
|
|
431
238
|
```tsx
|
|
432
|
-
//
|
|
239
|
+
// Good
|
|
433
240
|
const crumbs = [
|
|
434
241
|
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
435
|
-
{ label: '
|
|
436
|
-
{ label: 'Laptops', type: 'text' },
|
|
242
|
+
{ label: 'Settings', type: 'text' },
|
|
437
243
|
];
|
|
244
|
+
|
|
245
|
+
// Bad -- missing root item
|
|
246
|
+
const crumbs = [{ label: 'Settings', type: 'text' }];
|
|
438
247
|
```
|
|
439
248
|
|
|
440
|
-
|
|
249
|
+
- **Make the current page non-clickable.** The last item in the trail should use `type: 'text'` since clicking the current page serves no purpose.
|
|
441
250
|
|
|
442
251
|
```tsx
|
|
443
|
-
//
|
|
252
|
+
// Good
|
|
444
253
|
{ label: 'Current Page', type: 'text' }
|
|
445
254
|
|
|
446
|
-
//
|
|
255
|
+
// Bad
|
|
447
256
|
{ label: 'Current Page', type: 'link', linkHref: '/current' }
|
|
448
257
|
```
|
|
449
258
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
4. **Maintain consistency**: Use the same labels as page titles or navigation
|
|
259
|
+
- **Use collapsing for deep hierarchies.** When paths exceed four or five levels, enable `collapsed` to keep the UI clean and avoid horizontal overflow.
|
|
453
260
|
|
|
454
|
-
|
|
261
|
+
- **Keep labels concise.** Breadcrumb labels should be short but descriptive. Match them to the corresponding page titles for consistency.
|
|
455
262
|
|
|
456
|
-
|
|
263
|
+
- **Do not use breadcrumbs as primary navigation.** They are a supplementary wayfinding aid, not a replacement for the main navigation menu.
|
|
457
264
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
2. **Don't duplicate with page title**: If the current page title is visible, breadcrumbs can end with the parent
|
|
461
|
-
|
|
462
|
-
```tsx
|
|
463
|
-
// Consider ending at parent if title is displayed
|
|
464
|
-
const crumbs = [
|
|
465
|
-
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
466
|
-
{ label: 'Products', type: 'link', linkHref: '/products' },
|
|
467
|
-
// Omit if "Laptops" is shown as page title
|
|
468
|
-
];
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
3. **Don't use for flat navigation**: Sites with only one or two levels don't need breadcrumbs
|
|
472
|
-
|
|
473
|
-
4. **Don't hide on mobile carelessly**: Either show a simplified version or omit entirely
|
|
474
|
-
|
|
475
|
-
## Performance Considerations
|
|
476
|
-
|
|
477
|
-
### Memoize Crumbs
|
|
478
|
-
|
|
479
|
-
When breadcrumbs are computed from props or state, memoize them:
|
|
480
|
-
|
|
481
|
-
```tsx
|
|
482
|
-
const crumbs = useMemo(() => [
|
|
483
|
-
{ label: 'Home', type: 'link', linkHref: '/' },
|
|
484
|
-
{ label: category.name, type: 'link', linkHref: `/category/${category.id}` },
|
|
485
|
-
{ label: product.name, type: 'text' },
|
|
486
|
-
], [category, product]);
|
|
487
|
-
|
|
488
|
-
<Breadcrumbs crumbs={crumbs} />
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### Use Collapsed for Deep Hierarchies
|
|
492
|
-
|
|
493
|
-
For paths with many levels, use collapsing to reduce DOM elements:
|
|
494
|
-
|
|
495
|
-
```tsx
|
|
496
|
-
<Breadcrumbs
|
|
497
|
-
crumbs={deepHierarchy}
|
|
498
|
-
collapsed={true}
|
|
499
|
-
startCrumbCount={1}
|
|
500
|
-
endCrumbCount={2}
|
|
501
|
-
/>
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### Lazy Load for Dynamic Breadcrumbs
|
|
505
|
-
|
|
506
|
-
If breadcrumb data requires fetching, ensure it doesn't block page render:
|
|
507
|
-
|
|
508
|
-
```tsx
|
|
509
|
-
function Page() {
|
|
510
|
-
const { data: breadcrumbData, isLoading } = useBreadcrumbPath();
|
|
511
|
-
|
|
512
|
-
return (
|
|
513
|
-
<Box>
|
|
514
|
-
{!isLoading && <Breadcrumbs crumbs={breadcrumbData} />}
|
|
515
|
-
<PageContent />
|
|
516
|
-
</Box>
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
```
|
|
265
|
+
## Accessibility
|
|
520
266
|
|
|
521
|
-
|
|
267
|
+
- **Semantic HTML**: The component renders a `<nav>` element with `aria-label="breadcrumb"` and an ordered list (`<ol>`) to convey the sequential nature of the trail.
|
|
268
|
+
- **Current page indicator**: The last item is automatically marked with `aria-current="page"` so screen readers announce the user's current location.
|
|
269
|
+
- **Keyboard navigation**: All link items are focusable with `Tab` and activated with `Enter`, following standard keyboard interaction patterns.
|
|
270
|
+
- **Screen readers**: The navigation landmark is announced as "breadcrumb navigation" and each link communicates its position within the path hierarchy.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
+
The Drawer component is a panel that slides in from the edge of the screen, overlaying the main content to present secondary information, navigation, or actions. Built on top of Joy UI's Drawer with Framer Motion animation, it provides smooth entrance and exit transitions out of the box.
|
|
6
|
+
|
|
7
|
+
Drawer supports three size presets (`sm`, `md`, `lg`) which control the panel width on horizontal anchors and height on vertical anchors. On smaller viewports (below `md` breakpoint), the drawer automatically expands to full width for an optimized mobile experience.
|
|
8
|
+
|
|
5
9
|
```tsx
|
|
6
10
|
<Drawer
|
|
7
11
|
open
|
|
@@ -24,4 +28,180 @@
|
|
|
24
28
|
|
|
25
29
|
```tsx
|
|
26
30
|
import { Drawer } from '@ceed/cds';
|
|
31
|
+
|
|
32
|
+
function MyComponent() {
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<Button onClick={() => setOpen(true)}>Open Drawer</Button>
|
|
38
|
+
<Drawer open={open} onClose={() => setOpen(false)}>
|
|
39
|
+
<Box sx={{ p: 2, backgroundColor: 'white', height: '100%' }}>
|
|
40
|
+
<Typography level="title-lg">Drawer Content</Typography>
|
|
41
|
+
<Typography level="body-md">
|
|
42
|
+
Place your navigation, detail view, or form content here.
|
|
43
|
+
</Typography>
|
|
44
|
+
</Box>
|
|
45
|
+
</Drawer>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Default
|
|
52
|
+
|
|
53
|
+
The default Drawer displays a panel sliding in from the left with the `md` size preset.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Drawer
|
|
57
|
+
open
|
|
58
|
+
children={<Box sx={{
|
|
59
|
+
width: '100%',
|
|
60
|
+
height: '100%',
|
|
61
|
+
backgroundColor: 'white',
|
|
62
|
+
boxShadow: 'var(--ceed-shadowRing, 0 0 #000),0px 2px 8px -2px rgba(var(--ceed-shadowChannel, 21 21 21) / var(--ceed-shadowOpacity, 0.08)),0px 6px 12px -2px rgba(var(--ceed-shadowChannel, 21 21 21) / var(--ceed-shadowOpacity, 0.08))'
|
|
63
|
+
}}>
|
|
64
|
+
asdas
|
|
65
|
+
</Box>}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Common Use Cases
|
|
70
|
+
|
|
71
|
+
### Navigation Drawer
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
function NavigationDrawer({ open, onClose }) {
|
|
75
|
+
const navItems = [
|
|
76
|
+
{ label: 'Home', icon: <HomeIcon />, path: '/' },
|
|
77
|
+
{ label: 'Orders', icon: <ListIcon />, path: '/orders' },
|
|
78
|
+
{ label: 'Account', icon: <PersonIcon />, path: '/account' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Drawer open={open} onClose={onClose} size="sm">
|
|
83
|
+
<Box sx={{ p: 2, backgroundColor: 'white', height: '100%' }}>
|
|
84
|
+
<Typography level="title-lg" sx={{ mb: 2 }}>Menu</Typography>
|
|
85
|
+
<List>
|
|
86
|
+
{navItems.map((item) => (
|
|
87
|
+
<ListItem key={item.path}>
|
|
88
|
+
<ListItemButton onClick={() => { navigate(item.path); onClose(); }}>
|
|
89
|
+
<ListItemDecorator>{item.icon}</ListItemDecorator>
|
|
90
|
+
{item.label}
|
|
91
|
+
</ListItemButton>
|
|
92
|
+
</ListItem>
|
|
93
|
+
))}
|
|
94
|
+
</List>
|
|
95
|
+
</Box>
|
|
96
|
+
</Drawer>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
27
99
|
```
|
|
100
|
+
|
|
101
|
+
### Detail View
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
function DetailDrawer({ open, onClose, selectedItem }) {
|
|
105
|
+
return (
|
|
106
|
+
<Drawer open={open} onClose={onClose} anchor="right" size="lg">
|
|
107
|
+
<Box
|
|
108
|
+
sx={{
|
|
109
|
+
p: 3,
|
|
110
|
+
backgroundColor: 'white',
|
|
111
|
+
height: '100%',
|
|
112
|
+
display: 'flex',
|
|
113
|
+
flexDirection: 'column',
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
|
117
|
+
<Typography level="title-lg">{selectedItem?.name}</Typography>
|
|
118
|
+
<IconButton onClick={onClose}><CloseIcon /></IconButton>
|
|
119
|
+
</Box>
|
|
120
|
+
<Divider />
|
|
121
|
+
<Box sx={{ flex: 1, overflow: 'auto', mt: 2 }}>
|
|
122
|
+
{/* Detail content */}
|
|
123
|
+
</Box>
|
|
124
|
+
</Box>
|
|
125
|
+
</Drawer>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Form Drawer
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
function FormDrawer({ open, onClose, onSubmit }) {
|
|
134
|
+
return (
|
|
135
|
+
<Drawer open={open} onClose={onClose} anchor="right" size="md">
|
|
136
|
+
<Box
|
|
137
|
+
sx={{
|
|
138
|
+
p: 3,
|
|
139
|
+
backgroundColor: 'white',
|
|
140
|
+
height: '100%',
|
|
141
|
+
display: 'flex',
|
|
142
|
+
flexDirection: 'column',
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<Typography level="title-lg" sx={{ mb: 2 }}>Create New Item</Typography>
|
|
146
|
+
<Divider />
|
|
147
|
+
<Box sx={{ flex: 1, overflow: 'auto', my: 2 }}>
|
|
148
|
+
<Stack gap={2}>
|
|
149
|
+
<Input label="Name" placeholder="Enter name" />
|
|
150
|
+
<Textarea label="Description" placeholder="Enter description" minRows={3} />
|
|
151
|
+
</Stack>
|
|
152
|
+
</Box>
|
|
153
|
+
<Divider />
|
|
154
|
+
<Stack direction="row" gap={1} sx={{ mt: 2, justifyContent: 'flex-end' }}>
|
|
155
|
+
<Button variant="outlined" color="neutral" onClick={onClose}>Cancel</Button>
|
|
156
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
157
|
+
</Stack>
|
|
158
|
+
</Box>
|
|
159
|
+
</Drawer>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Best Practices
|
|
165
|
+
|
|
166
|
+
1. **Choose the right size**: Use `sm` (360px) for simple navigation, `md` (600px) for moderate content, and `lg` (900px) for complex detail views or forms.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// ✅ Good: Size matches content complexity
|
|
170
|
+
<Drawer size="sm"> {/* Simple nav list */}
|
|
171
|
+
<Drawer size="md"> {/* Filter panel or form */}
|
|
172
|
+
<Drawer size="lg"> {/* Detailed information view */}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
2. **Provide visible close affordances**: Always include a close button or cancel action inside the Drawer content, in addition to the backdrop click.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
// ✅ Good: Close button inside the drawer
|
|
179
|
+
<Drawer open={open} onClose={onClose}>
|
|
180
|
+
<Box>
|
|
181
|
+
<IconButton onClick={onClose}><CloseIcon /></IconButton>
|
|
182
|
+
{/* Content */}
|
|
183
|
+
</Box>
|
|
184
|
+
</Drawer>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
3. **Apply proper background and shadow styling**: The Drawer renders children directly, so you should apply background color and shadow to your content container for visual clarity.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// ✅ Good: Styled content container
|
|
191
|
+
<Drawer open={open}>
|
|
192
|
+
<Box sx={{ backgroundColor: 'white', height: '100%', boxShadow: 'md', p: 2 }}>
|
|
193
|
+
{/* Content */}
|
|
194
|
+
</Box>
|
|
195
|
+
</Drawer>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
4. **Do not use Drawer for confirmations**: For simple yes/no prompts, use Dialog instead. Drawer is designed for richer, scrollable content.
|
|
199
|
+
|
|
200
|
+
5. **Structure content with header, body, and footer**: Use a flex column layout to keep the header and footer fixed while allowing the body to scroll.
|
|
201
|
+
|
|
202
|
+
## Accessibility
|
|
203
|
+
|
|
204
|
+
- The Drawer overlay traps focus within the panel when open, preventing keyboard users from interacting with background content.
|
|
205
|
+
- Pressing **Escape** closes the Drawer. **Tab** and **Shift+Tab** cycle through focusable elements within the Drawer content.
|
|
206
|
+
- On mobile viewports, the Drawer automatically expands to full width, ensuring touch targets remain accessible and content is not clipped.
|
|
207
|
+
- Provide an `aria-label` or visible heading within the Drawer content to describe the panel's purpose for screen reader users.
|