@ceed/ads 1.23.3 → 1.23.5
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/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- 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/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- 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 +136 -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 +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- 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/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# MenuButton
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MenuButton is a high-level compound component that combines a labeled button trigger with a dropdown menu in a single, prop-driven API. Pass `buttonText` and an `items` array and the component handles the Dropdown, Menu, and MenuItem composition internally. It is the recommended choice when you need a simple text-triggered menu without custom content inside the menu panel.
|
|
4
4
|
|
|
5
|
-
MenuButton
|
|
5
|
+
MenuButton belongs to the **menu component family** alongside Dropdown, Menu, MenuItem, and IconMenuButton. For icon-only triggers, use IconMenuButton. For menus that need custom content (headers, dividers, mixed elements), use the lower-level Dropdown + Menu composition instead.
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
<MenuButton
|
|
@@ -34,15 +34,6 @@ MenuButton is a compound component that combines a button with a dropdown menu,
|
|
|
34
34
|
| color | — | "primary" |
|
|
35
35
|
| size | — | — |
|
|
36
36
|
|
|
37
|
-
> ⚠️ **Usage Warning** ⚠️
|
|
38
|
-
>
|
|
39
|
-
> Choose the right component for your use case:
|
|
40
|
-
>
|
|
41
|
-
> - **MenuButton**: For action menus triggered by a labeled button
|
|
42
|
-
> - **IconMenuButton**: For action menus triggered by an icon-only button
|
|
43
|
-
> - **Dropdown**: For more complex menu structures with custom triggers
|
|
44
|
-
> - **Select**: For form inputs where user selects a value (not actions)
|
|
45
|
-
|
|
46
37
|
## Usage
|
|
47
38
|
|
|
48
39
|
```tsx
|
|
@@ -62,31 +53,19 @@ function UserMenu() {
|
|
|
62
53
|
}
|
|
63
54
|
```
|
|
64
55
|
|
|
65
|
-
##
|
|
56
|
+
## When to Use Which
|
|
66
57
|
|
|
67
|
-
|
|
58
|
+
| Component | Trigger | Content | Use when |
|
|
59
|
+
| ------------------- | ----------- | ---------------- | ------------------------------------------------------------ |
|
|
60
|
+
| **MenuButton** | Text button | Simple item list | You need a labeled trigger with a flat list of text options |
|
|
61
|
+
| **IconMenuButton** | Icon button | Simple item list | Space is limited (tables, cards, toolbars) |
|
|
62
|
+
| **Dropdown + Menu** | Any element | Rich content | You need dividers, headers, icons in items, or custom layout |
|
|
68
63
|
|
|
69
|
-
|
|
64
|
+
## Features
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
<MenuButton
|
|
73
|
-
buttonText="Dashboard..."
|
|
74
|
-
items={[{
|
|
75
|
-
text: 'Profile'
|
|
76
|
-
}, {
|
|
77
|
-
text: 'My account'
|
|
78
|
-
}, {
|
|
79
|
-
text: 'Logout'
|
|
80
|
-
}]}
|
|
81
|
-
showIcon
|
|
82
|
-
variant="solid"
|
|
83
|
-
color="primary"
|
|
84
|
-
/>
|
|
85
|
-
```
|
|
66
|
+
### Standalone (Without Dropdown Icon)
|
|
86
67
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Use as a simple navigation link without the dropdown icon.
|
|
68
|
+
Use `showIcon={false}` and a custom `buttonComponent` to render MenuButton as a standalone navigation link without the dropdown indicator.
|
|
90
69
|
|
|
91
70
|
```tsx
|
|
92
71
|
<MenuButton
|
|
@@ -110,7 +89,7 @@ Use as a simple navigation link without the dropdown icon.
|
|
|
110
89
|
|
|
111
90
|
### With End Decorator
|
|
112
91
|
|
|
113
|
-
Add badges, chips, or icons
|
|
92
|
+
Add badges, chips, or icons after the button text using `endDecorator`.
|
|
114
93
|
|
|
115
94
|
```tsx
|
|
116
95
|
<MenuButton
|
|
@@ -131,7 +110,7 @@ Add badges, chips, or icons at the end of the button.
|
|
|
131
110
|
|
|
132
111
|
### Placement: Bottom Start
|
|
133
112
|
|
|
134
|
-
Menu aligns to the left edge of the button.
|
|
113
|
+
Menu aligns to the left edge of the trigger button.
|
|
135
114
|
|
|
136
115
|
```tsx
|
|
137
116
|
<MenuButton
|
|
@@ -152,7 +131,7 @@ Menu aligns to the left edge of the button.
|
|
|
152
131
|
|
|
153
132
|
### Placement: Bottom
|
|
154
133
|
|
|
155
|
-
Menu centers below the button.
|
|
134
|
+
Menu centers below the trigger button.
|
|
156
135
|
|
|
157
136
|
```tsx
|
|
158
137
|
<MenuButton
|
|
@@ -173,7 +152,7 @@ Menu centers below the button.
|
|
|
173
152
|
|
|
174
153
|
### Placement: Bottom End
|
|
175
154
|
|
|
176
|
-
Menu aligns to the right edge of the button.
|
|
155
|
+
Menu aligns to the right edge of the trigger button.
|
|
177
156
|
|
|
178
157
|
```tsx
|
|
179
158
|
<MenuButton
|
|
@@ -192,25 +171,6 @@ Menu aligns to the right edge of the button.
|
|
|
192
171
|
/>
|
|
193
172
|
```
|
|
194
173
|
|
|
195
|
-
## When to Use
|
|
196
|
-
|
|
197
|
-
### ✅ Good Use Cases
|
|
198
|
-
|
|
199
|
-
- **User account menus**: Profile, settings, logout actions
|
|
200
|
-
- **Action grouping**: Related actions in a toolbar
|
|
201
|
-
- **Navigation shortcuts**: Quick access to related pages
|
|
202
|
-
- **Overflow menus**: When space is limited
|
|
203
|
-
- **Context actions**: Page or section-specific actions
|
|
204
|
-
- **Export options**: Download, export, or share actions
|
|
205
|
-
|
|
206
|
-
### ❌ When Not to Use
|
|
207
|
-
|
|
208
|
-
- **Form value selection**: Use Select for picking values in forms
|
|
209
|
-
- **Single action**: Use Button for single actions
|
|
210
|
-
- **Icon-only trigger**: Use IconMenuButton for minimal space
|
|
211
|
-
- **Complex menus**: Use Dropdown for nested menus or custom content
|
|
212
|
-
- **Primary navigation**: Use Navigator or Tabs for main navigation
|
|
213
|
-
|
|
214
174
|
## Common Use Cases
|
|
215
175
|
|
|
216
176
|
### User Account Menu
|
|
@@ -226,7 +186,6 @@ function AccountMenu({ user, onLogout }) {
|
|
|
226
186
|
items={[
|
|
227
187
|
{ text: 'Profile', onClick: () => navigate('/profile') },
|
|
228
188
|
{ text: 'Account Settings', onClick: () => navigate('/settings') },
|
|
229
|
-
{ text: 'Billing', onClick: () => navigate('/billing') },
|
|
230
189
|
{ text: 'Logout', onClick: onLogout },
|
|
231
190
|
]}
|
|
232
191
|
/>
|
|
@@ -234,10 +193,10 @@ function AccountMenu({ user, onLogout }) {
|
|
|
234
193
|
}
|
|
235
194
|
```
|
|
236
195
|
|
|
237
|
-
### Export Options
|
|
196
|
+
### Export Options
|
|
238
197
|
|
|
239
198
|
```tsx
|
|
240
|
-
function ExportMenu({
|
|
199
|
+
function ExportMenu({ onExport }) {
|
|
241
200
|
return (
|
|
242
201
|
<MenuButton
|
|
243
202
|
buttonText="Export"
|
|
@@ -247,425 +206,73 @@ function ExportMenu({ data, onExport }) {
|
|
|
247
206
|
{ text: 'Export as CSV', onClick: () => onExport('csv') },
|
|
248
207
|
{ text: 'Export as Excel', onClick: () => onExport('xlsx') },
|
|
249
208
|
{ text: 'Export as PDF', onClick: () => onExport('pdf') },
|
|
250
|
-
{ text: 'Print', onClick: () => window.print() },
|
|
251
209
|
]}
|
|
252
210
|
/>
|
|
253
211
|
);
|
|
254
212
|
}
|
|
255
213
|
```
|
|
256
214
|
|
|
257
|
-
### Actions
|
|
215
|
+
### Toolbar Actions
|
|
258
216
|
|
|
259
217
|
```tsx
|
|
260
218
|
function ToolbarActions({ selectedItems, onAction }) {
|
|
261
|
-
const hasSelection = selectedItems.length > 0;
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<Stack direction="row" gap={1}>
|
|
265
|
-
<Button onClick={() => onAction('create')}>Create New</Button>
|
|
266
|
-
<MenuButton
|
|
267
|
-
buttonText="Actions"
|
|
268
|
-
variant="outlined"
|
|
269
|
-
color="neutral"
|
|
270
|
-
disabled={!hasSelection}
|
|
271
|
-
items={[
|
|
272
|
-
{ text: `Edit (${selectedItems.length})`, onClick: () => onAction('edit') },
|
|
273
|
-
{ text: 'Duplicate', onClick: () => onAction('duplicate') },
|
|
274
|
-
{ text: 'Move to Folder', onClick: () => onAction('move') },
|
|
275
|
-
{ text: 'Archive', onClick: () => onAction('archive') },
|
|
276
|
-
{ text: 'Delete', onClick: () => onAction('delete') },
|
|
277
|
-
]}
|
|
278
|
-
/>
|
|
279
|
-
</Stack>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Navigation with Dropdown
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
function ProductsMenu() {
|
|
288
219
|
return (
|
|
289
220
|
<MenuButton
|
|
290
|
-
buttonText="
|
|
291
|
-
variant="
|
|
292
|
-
showIcon={true}
|
|
293
|
-
items={[
|
|
294
|
-
{ text: 'All Products', onClick: () => navigate('/products') },
|
|
295
|
-
{ text: 'Categories', onClick: () => navigate('/products/categories') },
|
|
296
|
-
{ text: 'Inventory', onClick: () => navigate('/products/inventory') },
|
|
297
|
-
{ text: 'Price Lists', onClick: () => navigate('/products/prices') },
|
|
298
|
-
]}
|
|
299
|
-
/>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Status Change Menu
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
function StatusMenu({ currentStatus, onStatusChange }) {
|
|
308
|
-
const statuses = [
|
|
309
|
-
{ value: 'draft', label: 'Draft', color: 'neutral' },
|
|
310
|
-
{ value: 'pending', label: 'Pending Review', color: 'warning' },
|
|
311
|
-
{ value: 'approved', label: 'Approved', color: 'success' },
|
|
312
|
-
{ value: 'rejected', label: 'Rejected', color: 'danger' },
|
|
313
|
-
];
|
|
314
|
-
|
|
315
|
-
const current = statuses.find((s) => s.value === currentStatus);
|
|
316
|
-
|
|
317
|
-
return (
|
|
318
|
-
<MenuButton
|
|
319
|
-
buttonText={current?.label || 'Set Status'}
|
|
320
|
-
variant="soft"
|
|
321
|
-
color={current?.color || 'neutral'}
|
|
322
|
-
items={statuses.map((status) => ({
|
|
323
|
-
text: status.label,
|
|
324
|
-
onClick: () => onStatusChange(status.value),
|
|
325
|
-
}))}
|
|
326
|
-
/>
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Menu with Decorators
|
|
332
|
-
|
|
333
|
-
```tsx
|
|
334
|
-
function NotificationsMenu({ notifications }) {
|
|
335
|
-
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
336
|
-
|
|
337
|
-
return (
|
|
338
|
-
<MenuButton
|
|
339
|
-
buttonText="Notifications"
|
|
340
|
-
startDecorator={<NotificationsIcon />}
|
|
341
|
-
endDecorator={
|
|
342
|
-
unreadCount > 0 && (
|
|
343
|
-
<Chip size="sm" color="danger">
|
|
344
|
-
{unreadCount}
|
|
345
|
-
</Chip>
|
|
346
|
-
)
|
|
347
|
-
}
|
|
348
|
-
variant="soft"
|
|
349
|
-
items={notifications.slice(0, 5).map((n) => ({
|
|
350
|
-
text: n.title,
|
|
351
|
-
onClick: () => markAsRead(n.id),
|
|
352
|
-
}))}
|
|
353
|
-
/>
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
### Quick Settings Menu
|
|
359
|
-
|
|
360
|
-
```tsx
|
|
361
|
-
function QuickSettings({ settings, onSettingChange }) {
|
|
362
|
-
return (
|
|
363
|
-
<MenuButton
|
|
364
|
-
buttonText="Settings"
|
|
365
|
-
startDecorator={<SettingsIcon />}
|
|
366
|
-
variant="plain"
|
|
221
|
+
buttonText="Actions"
|
|
222
|
+
variant="outlined"
|
|
367
223
|
color="neutral"
|
|
224
|
+
disabled={selectedItems.length === 0}
|
|
368
225
|
items={[
|
|
369
|
-
{
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
text: settings.compactView ? '📐 Normal View' : '📏 Compact View',
|
|
375
|
-
onClick: () => onSettingChange('compactView', !settings.compactView),
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
text: 'Language: ' + settings.language.toUpperCase(),
|
|
379
|
-
onClick: () => openLanguageModal(),
|
|
380
|
-
},
|
|
226
|
+
{ text: `Edit (${selectedItems.length})`, onClick: () => onAction('edit') },
|
|
227
|
+
{ text: 'Duplicate', onClick: () => onAction('duplicate') },
|
|
228
|
+
{ text: 'Archive', onClick: () => onAction('archive') },
|
|
229
|
+
{ text: 'Delete', onClick: () => onAction('delete') },
|
|
381
230
|
]}
|
|
382
231
|
/>
|
|
383
232
|
);
|
|
384
233
|
}
|
|
385
234
|
```
|
|
386
235
|
|
|
387
|
-
## Props and Customization
|
|
388
|
-
|
|
389
|
-
### Key Props
|
|
390
|
-
|
|
391
|
-
| Prop | Type | Default | Description |
|
|
392
|
-
| ---------------------- | -------------------------------------------------------------- | ---------------- | --------------------------------- |
|
|
393
|
-
| `buttonText` | `string` | - | Text displayed on the button |
|
|
394
|
-
| `items` | `Array<{ text: string; onClick?: () => void }>` | - | Menu items to display |
|
|
395
|
-
| `showIcon` | `boolean` | `true` | Show dropdown indicator icon |
|
|
396
|
-
| `variant` | `'solid' \| 'soft' \| 'outlined' \| 'plain'` | `'solid'` | Button style variant |
|
|
397
|
-
| `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'primary'` | Button color |
|
|
398
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
399
|
-
| `placement` | `'bottom-start' \| 'bottom' \| 'bottom-end'` | `'bottom-start'` | Menu position |
|
|
400
|
-
| `startDecorator` | `ReactNode` | - | Content before button text |
|
|
401
|
-
| `endDecorator` | `ReactNode` | - | Content after button text |
|
|
402
|
-
| `buttonComponent` | `React.ElementType` | `Button` | Custom button component |
|
|
403
|
-
| `buttonComponentProps` | `object` | - | Props for custom button component |
|
|
404
|
-
|
|
405
|
-
### Menu Item Structure
|
|
406
|
-
|
|
407
|
-
```tsx
|
|
408
|
-
interface MenuItem {
|
|
409
|
-
text: string; // Display text
|
|
410
|
-
onClick?: () => void; // Click handler
|
|
411
|
-
// Additional props can be passed based on implementation
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Example items array
|
|
415
|
-
const items = [
|
|
416
|
-
{ text: 'Edit', onClick: handleEdit },
|
|
417
|
-
{ text: 'Delete', onClick: handleDelete },
|
|
418
|
-
];
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Variant and Color Options
|
|
422
|
-
|
|
423
|
-
```tsx
|
|
424
|
-
// Primary solid (default)
|
|
425
|
-
<MenuButton buttonText="Actions" variant="solid" color="primary" />
|
|
426
|
-
|
|
427
|
-
// Subtle outlined
|
|
428
|
-
<MenuButton buttonText="Actions" variant="outlined" color="neutral" />
|
|
429
|
-
|
|
430
|
-
// Soft background
|
|
431
|
-
<MenuButton buttonText="Actions" variant="soft" color="primary" />
|
|
432
|
-
|
|
433
|
-
// Plain/text only
|
|
434
|
-
<MenuButton buttonText="Actions" variant="plain" color="neutral" />
|
|
435
|
-
|
|
436
|
-
// Danger color
|
|
437
|
-
<MenuButton buttonText="Delete" variant="soft" color="danger" />
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### Size Options
|
|
441
|
-
|
|
442
|
-
```tsx
|
|
443
|
-
// Small
|
|
444
|
-
<MenuButton buttonText="Small" size="sm" items={items} />
|
|
445
|
-
|
|
446
|
-
// Medium (default)
|
|
447
|
-
<MenuButton buttonText="Medium" size="md" items={items} />
|
|
448
|
-
|
|
449
|
-
// Large
|
|
450
|
-
<MenuButton buttonText="Large" size="lg" items={items} />
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### Custom Button Component
|
|
454
|
-
|
|
455
|
-
```tsx
|
|
456
|
-
// Use with custom components like Link
|
|
457
|
-
import { Link } from 'react-router-dom';
|
|
458
|
-
|
|
459
|
-
<MenuButton
|
|
460
|
-
buttonText="Dashboard"
|
|
461
|
-
buttonComponent={Link}
|
|
462
|
-
buttonComponentProps={{ to: '/dashboard' }}
|
|
463
|
-
showIcon={false}
|
|
464
|
-
items={items}
|
|
465
|
-
/>
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### Decorators
|
|
469
|
-
|
|
470
|
-
```tsx
|
|
471
|
-
// Start decorator (icon before text)
|
|
472
|
-
<MenuButton
|
|
473
|
-
buttonText="Export"
|
|
474
|
-
startDecorator={<DownloadIcon />}
|
|
475
|
-
items={exportItems}
|
|
476
|
-
/>
|
|
477
|
-
|
|
478
|
-
// End decorator (badge after text)
|
|
479
|
-
<MenuButton
|
|
480
|
-
buttonText="Inbox"
|
|
481
|
-
endDecorator={<Chip size="sm">5</Chip>}
|
|
482
|
-
items={inboxItems}
|
|
483
|
-
/>
|
|
484
|
-
|
|
485
|
-
// Both decorators
|
|
486
|
-
<MenuButton
|
|
487
|
-
buttonText="User"
|
|
488
|
-
startDecorator={<Avatar size="sm" />}
|
|
489
|
-
endDecorator={<ChevronDownIcon />}
|
|
490
|
-
showIcon={false}
|
|
491
|
-
items={userItems}
|
|
492
|
-
/>
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
## Accessibility
|
|
496
|
-
|
|
497
|
-
MenuButton includes built-in accessibility features:
|
|
498
|
-
|
|
499
|
-
### ARIA Attributes
|
|
500
|
-
|
|
501
|
-
- Button has `aria-haspopup="true"` indicating it opens a menu
|
|
502
|
-
- Button has `aria-expanded` reflecting menu open state
|
|
503
|
-
- Menu has proper `role="menu"` and `role="menuitem"` structure
|
|
504
|
-
- Focus is managed between button and menu items
|
|
505
|
-
|
|
506
|
-
### Keyboard Navigation
|
|
507
|
-
|
|
508
|
-
- **Enter/Space**: Open menu when button is focused
|
|
509
|
-
- **Arrow Down**: Move to next menu item
|
|
510
|
-
- **Arrow Up**: Move to previous menu item
|
|
511
|
-
- **Escape**: Close menu and return focus to button
|
|
512
|
-
- **Tab**: Close menu and move to next focusable element
|
|
513
|
-
- **Enter**: Select focused menu item
|
|
514
|
-
|
|
515
|
-
### Screen Reader Support
|
|
516
|
-
|
|
517
|
-
```tsx
|
|
518
|
-
// Button announces: "Account, menu button, expanded/collapsed"
|
|
519
|
-
<MenuButton
|
|
520
|
-
buttonText="Account"
|
|
521
|
-
items={[...]}
|
|
522
|
-
/>
|
|
523
|
-
|
|
524
|
-
// Menu items are announced as: "Profile, menu item, 1 of 3"
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
### Focus Management
|
|
528
|
-
|
|
529
|
-
- Focus moves to first menu item when menu opens
|
|
530
|
-
- Focus returns to button when menu closes
|
|
531
|
-
- Menu items are focusable with arrow keys
|
|
532
|
-
|
|
533
236
|
## Best Practices
|
|
534
237
|
|
|
535
|
-
|
|
238
|
+
- **Use descriptive button text.** The button label should clearly indicate what kind of options the menu contains. Avoid vague labels like "More" or "Options" -- prefer specific labels like "Export Options" or "User Actions".
|
|
536
239
|
|
|
537
|
-
|
|
240
|
+
```tsx
|
|
241
|
+
{/* ✅ Good: Clear purpose */}
|
|
242
|
+
<MenuButton buttonText="Export Options" items={exportItems} />
|
|
538
243
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
<MenuButton buttonText="User Actions" items={userItems} />
|
|
543
|
-
```
|
|
244
|
+
{/* ❌ Avoid: Vague label */}
|
|
245
|
+
<MenuButton buttonText="More" items={items} />
|
|
246
|
+
```
|
|
544
247
|
|
|
545
|
-
|
|
248
|
+
- **Keep the items list short and focused.** Aim for 3-7 items that are logically related. If you need more items or mixed content, switch to the Dropdown + Menu composition pattern.
|
|
546
249
|
|
|
547
|
-
|
|
548
|
-
// ✅ Good: Related file actions
|
|
549
|
-
<MenuButton
|
|
550
|
-
buttonText="File"
|
|
551
|
-
items={[
|
|
552
|
-
{ text: 'New', onClick: handleNew },
|
|
553
|
-
{ text: 'Open', onClick: handleOpen },
|
|
554
|
-
{ text: 'Save', onClick: handleSave },
|
|
555
|
-
{ text: 'Save As...', onClick: handleSaveAs },
|
|
556
|
-
]}
|
|
557
|
-
/>
|
|
558
|
-
```
|
|
250
|
+
- **Match variant to context.** Use `variant="solid"` for primary action menus, `variant="outlined"` or `variant="plain"` for secondary or navigation menus. Be consistent within the same toolbar or header.
|
|
559
251
|
|
|
560
|
-
|
|
252
|
+
```tsx
|
|
253
|
+
{/* ✅ Good: Appropriate variants */}
|
|
254
|
+
<MenuButton buttonText="Create" variant="solid" color="primary" items={createItems} />
|
|
255
|
+
<MenuButton buttonText="Settings" variant="plain" color="neutral" items={settingsItems} />
|
|
256
|
+
```
|
|
561
257
|
|
|
562
|
-
|
|
563
|
-
// ✅ Good: Primary for main actions, plain for navigation
|
|
564
|
-
<MenuButton buttonText="Create" variant="solid" color="primary" />
|
|
565
|
-
<MenuButton buttonText="Settings" variant="plain" color="neutral" />
|
|
566
|
-
```
|
|
258
|
+
- **Do not use MenuButton for form value selection.** MenuButton triggers actions -- it is not a Select replacement. For form inputs where the user picks a value, use the Select component.
|
|
567
259
|
|
|
568
|
-
|
|
260
|
+
```tsx
|
|
261
|
+
{/* ❌ Bad: Form selection */}
|
|
262
|
+
<MenuButton buttonText={selectedValue} items={options} />
|
|
569
263
|
|
|
570
|
-
|
|
264
|
+
{/* ✅ Good: Use Select for forms */}
|
|
265
|
+
<Select value={selectedValue} onChange={handleChange}>
|
|
266
|
+
<Option value="a">Option A</Option>
|
|
267
|
+
<Option value="b">Option B</Option>
|
|
268
|
+
</Select>
|
|
269
|
+
```
|
|
571
270
|
|
|
572
|
-
|
|
271
|
+
- **Use `placement` to prevent overflow.** When the trigger is near the right edge of the viewport, use `placement="bottom-end"` so the menu does not extend beyond the screen.
|
|
573
272
|
|
|
574
|
-
|
|
575
|
-
// ❌ Bad: Using MenuButton for form selection
|
|
576
|
-
<MenuButton buttonText={selectedOption} items={options} />
|
|
577
|
-
|
|
578
|
-
// ✅ Good: Use Select for forms
|
|
579
|
-
<Select value={selectedOption} onChange={handleChange}>
|
|
580
|
-
{options.map((opt) => <Option key={opt.value} value={opt.value}>{opt.label}</Option>)}
|
|
581
|
-
</Select>
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
2. **Don't use vague labels**: Be specific about menu contents
|
|
585
|
-
|
|
586
|
-
```tsx
|
|
587
|
-
// ❌ Bad: Unclear what "More" contains
|
|
588
|
-
<MenuButton buttonText="More" items={items} />
|
|
589
|
-
|
|
590
|
-
// ✅ Good: Clear menu purpose
|
|
591
|
-
<MenuButton buttonText="More Actions" items={actionItems} />
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
3. **Don't overload menus**: Split large menus into categories
|
|
595
|
-
|
|
596
|
-
```tsx
|
|
597
|
-
// ❌ Bad: Too many unrelated items
|
|
598
|
-
<MenuButton
|
|
599
|
-
buttonText="Options"
|
|
600
|
-
items={[
|
|
601
|
-
{ text: 'Edit' },
|
|
602
|
-
{ text: 'Delete' },
|
|
603
|
-
{ text: 'Share' },
|
|
604
|
-
{ text: 'Settings' },
|
|
605
|
-
{ text: 'Help' },
|
|
606
|
-
{ text: 'About' },
|
|
607
|
-
// ... many more items
|
|
608
|
-
]}
|
|
609
|
-
/>
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
4. **Don't hide primary actions**: Important actions should be visible
|
|
613
|
-
|
|
614
|
-
## Performance Considerations
|
|
615
|
-
|
|
616
|
-
### Memoize Items Array
|
|
617
|
-
|
|
618
|
-
Prevent unnecessary re-renders by memoizing the items array:
|
|
619
|
-
|
|
620
|
-
```tsx
|
|
621
|
-
const menuItems = useMemo(
|
|
622
|
-
() => [
|
|
623
|
-
{ text: 'Edit', onClick: handleEdit },
|
|
624
|
-
{ text: 'Delete', onClick: handleDelete },
|
|
625
|
-
],
|
|
626
|
-
[handleEdit, handleDelete]
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
<MenuButton buttonText="Actions" items={menuItems} />
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### Memoize Click Handlers
|
|
633
|
-
|
|
634
|
-
```tsx
|
|
635
|
-
const handleExport = useCallback(
|
|
636
|
-
(format: string) => {
|
|
637
|
-
exportData(data, format);
|
|
638
|
-
},
|
|
639
|
-
[data]
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
const exportItems = useMemo(
|
|
643
|
-
() => [
|
|
644
|
-
{ text: 'CSV', onClick: () => handleExport('csv') },
|
|
645
|
-
{ text: 'Excel', onClick: () => handleExport('xlsx') },
|
|
646
|
-
],
|
|
647
|
-
[handleExport]
|
|
648
|
-
);
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
### Lazy Load Heavy Actions
|
|
652
|
-
|
|
653
|
-
For actions that trigger heavy operations:
|
|
654
|
-
|
|
655
|
-
```tsx
|
|
656
|
-
const items = useMemo(
|
|
657
|
-
() => [
|
|
658
|
-
{
|
|
659
|
-
text: 'Generate Report',
|
|
660
|
-
onClick: async () => {
|
|
661
|
-
setLoading(true);
|
|
662
|
-
await generateReport();
|
|
663
|
-
setLoading(false);
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
],
|
|
667
|
-
[]
|
|
668
|
-
);
|
|
669
|
-
```
|
|
273
|
+
## Accessibility
|
|
670
274
|
|
|
671
|
-
|
|
275
|
+
- **ARIA attributes**: The trigger button automatically receives `aria-haspopup="true"` and `aria-expanded` that reflects the open/closed state. The menu panel has `role="menu"` and each item has `role="menuitem"`.
|
|
276
|
+
- **Keyboard navigation**: Enter or Space opens the menu. Arrow Down/Up moves between items. Escape closes the menu and returns focus to the button. Tab closes the menu and moves focus to the next element.
|
|
277
|
+
- **Screen reader support**: The button is announced as "\[buttonText], menu button, expanded/collapsed". Menu items are announced with their position (e.g., "Profile, menu item, 1 of 3").
|
|
278
|
+
- **Focus management**: Focus moves to the first menu item when the menu opens and returns to the trigger button when the menu closes.
|