@ceed/ads 1.29.0 → 1.30.0-next.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/CurrencyInput/CurrencyInput.d.ts +1 -1
- package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
- package/dist/components/DataTable/hooks.d.ts +2 -1
- package/dist/components/DataTable/utils.d.ts +1 -0
- package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
- package/dist/components/SearchBar/SearchBar.d.ts +21 -0
- package/dist/components/SearchBar/index.d.ts +3 -0
- package/dist/components/data-display/Badge.md +39 -71
- package/dist/components/data-display/DataTable.md +1 -1
- package/dist/components/data-display/InfoSign.md +98 -74
- package/dist/components/data-display/Typography.md +97 -363
- package/dist/components/feedback/Dialog.md +62 -76
- package/dist/components/feedback/Modal.md +44 -259
- package/dist/components/feedback/llms.txt +0 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/components/inputs/Autocomplete.md +107 -356
- package/dist/components/inputs/ButtonGroup.md +106 -115
- package/dist/components/inputs/Calendar.md +459 -98
- package/dist/components/inputs/CurrencyInput.md +5 -183
- package/dist/components/inputs/DatePicker.md +431 -108
- package/dist/components/inputs/DateRangePicker.md +492 -131
- package/dist/components/inputs/FilterMenu.md +19 -169
- package/dist/components/inputs/FilterableCheckboxGroup.md +23 -123
- package/dist/components/inputs/IconButton.md +88 -137
- package/dist/components/inputs/Input.md +0 -5
- package/dist/components/inputs/MonthPicker.md +422 -95
- package/dist/components/inputs/MonthRangePicker.md +466 -89
- package/dist/components/inputs/PercentageInput.md +16 -185
- package/dist/components/inputs/RadioButton.md +35 -163
- package/dist/components/inputs/RadioTileGroup.md +61 -150
- package/dist/components/inputs/SearchBar.md +44 -0
- package/dist/components/inputs/Select.md +326 -222
- package/dist/components/inputs/Switch.md +376 -136
- package/dist/components/inputs/Textarea.md +10 -213
- package/dist/components/inputs/Uploader/Uploader.md +66 -145
- package/dist/components/inputs/llms.txt +1 -3
- package/dist/components/navigation/Breadcrumbs.md +322 -80
- package/dist/components/navigation/Dropdown.md +221 -92
- package/dist/components/navigation/IconMenuButton.md +502 -40
- package/dist/components/navigation/InsetDrawer.md +738 -68
- package/dist/components/navigation/Link.md +298 -39
- package/dist/components/navigation/Menu.md +285 -92
- package/dist/components/navigation/MenuButton.md +448 -55
- package/dist/components/navigation/Pagination.md +338 -47
- package/dist/components/navigation/ProfileMenu.md +268 -45
- package/dist/components/navigation/Stepper.md +28 -160
- package/dist/components/navigation/Tabs.md +316 -57
- package/dist/components/surfaces/Sheet.md +334 -151
- package/dist/index.browser.js +15 -13
- package/dist/index.browser.js.map +4 -4
- package/dist/index.cjs +313 -291
- package/dist/index.d.ts +1 -1
- package/dist/index.js +450 -372
- package/dist/llms.txt +1 -8
- package/framer/index.js +1 -1
- package/package.json +16 -15
- package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
- package/dist/components/feedback/CircularProgress.md +0 -257
- package/dist/components/feedback/Skeleton.md +0 -280
- package/dist/components/inputs/FormControl.md +0 -361
- package/dist/components/inputs/RadioList.md +0 -241
- package/dist/components/inputs/Slider.md +0 -334
- package/dist/guides/ThemeProvider.md +0 -116
- package/dist/guides/llms.txt +0 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# MenuButton
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Introduction
|
|
4
4
|
|
|
5
|
-
MenuButton
|
|
5
|
+
MenuButton is a compound component that combines a button with a dropdown menu, allowing users to trigger a list of actions or navigation options from a single interactive element. It displays a button with text and an optional dropdown indicator icon that, when clicked, reveals a menu of selectable items. MenuButton is commonly used for action menus, navigation shortcuts, and grouped operations in toolbars and headers.
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
8
|
<MenuButton
|
|
@@ -34,6 +34,15 @@ MenuButton belongs to the **menu component family** alongside Dropdown, Menu, Me
|
|
|
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
|
+
|
|
37
46
|
## Usage
|
|
38
47
|
|
|
39
48
|
```tsx
|
|
@@ -53,19 +62,31 @@ function UserMenu() {
|
|
|
53
62
|
}
|
|
54
63
|
```
|
|
55
64
|
|
|
56
|
-
##
|
|
65
|
+
## Examples
|
|
57
66
|
|
|
58
|
-
|
|
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 |
|
|
67
|
+
### Playground
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
Interactive example with all controls.
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
```tsx
|
|
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
|
+
```
|
|
67
86
|
|
|
68
|
-
|
|
87
|
+
### Standalone (Without Icon)
|
|
88
|
+
|
|
89
|
+
Use as a simple navigation link without the dropdown icon.
|
|
69
90
|
|
|
70
91
|
```tsx
|
|
71
92
|
<MenuButton
|
|
@@ -89,7 +110,7 @@ Use `showIcon={false}` and a custom `buttonComponent` to render MenuButton as a
|
|
|
89
110
|
|
|
90
111
|
### With End Decorator
|
|
91
112
|
|
|
92
|
-
Add badges, chips, or icons
|
|
113
|
+
Add badges, chips, or icons at the end of the button.
|
|
93
114
|
|
|
94
115
|
```tsx
|
|
95
116
|
<MenuButton
|
|
@@ -110,7 +131,7 @@ Add badges, chips, or icons after the button text using `endDecorator`.
|
|
|
110
131
|
|
|
111
132
|
### Placement: Bottom Start
|
|
112
133
|
|
|
113
|
-
Menu aligns to the left edge of the
|
|
134
|
+
Menu aligns to the left edge of the button.
|
|
114
135
|
|
|
115
136
|
```tsx
|
|
116
137
|
<MenuButton
|
|
@@ -131,7 +152,7 @@ Menu aligns to the left edge of the trigger button.
|
|
|
131
152
|
|
|
132
153
|
### Placement: Bottom
|
|
133
154
|
|
|
134
|
-
Menu centers below the
|
|
155
|
+
Menu centers below the button.
|
|
135
156
|
|
|
136
157
|
```tsx
|
|
137
158
|
<MenuButton
|
|
@@ -152,7 +173,7 @@ Menu centers below the trigger button.
|
|
|
152
173
|
|
|
153
174
|
### Placement: Bottom End
|
|
154
175
|
|
|
155
|
-
Menu aligns to the right edge of the
|
|
176
|
+
Menu aligns to the right edge of the button.
|
|
156
177
|
|
|
157
178
|
```tsx
|
|
158
179
|
<MenuButton
|
|
@@ -171,6 +192,25 @@ Menu aligns to the right edge of the trigger button.
|
|
|
171
192
|
/>
|
|
172
193
|
```
|
|
173
194
|
|
|
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
|
+
|
|
174
214
|
## Common Use Cases
|
|
175
215
|
|
|
176
216
|
### User Account Menu
|
|
@@ -186,6 +226,7 @@ function AccountMenu({ user, onLogout }) {
|
|
|
186
226
|
items={[
|
|
187
227
|
{ text: 'Profile', onClick: () => navigate('/profile') },
|
|
188
228
|
{ text: 'Account Settings', onClick: () => navigate('/settings') },
|
|
229
|
+
{ text: 'Billing', onClick: () => navigate('/billing') },
|
|
189
230
|
{ text: 'Logout', onClick: onLogout },
|
|
190
231
|
]}
|
|
191
232
|
/>
|
|
@@ -193,10 +234,10 @@ function AccountMenu({ user, onLogout }) {
|
|
|
193
234
|
}
|
|
194
235
|
```
|
|
195
236
|
|
|
196
|
-
### Export Options
|
|
237
|
+
### Export Options Menu
|
|
197
238
|
|
|
198
239
|
```tsx
|
|
199
|
-
function ExportMenu({ onExport }) {
|
|
240
|
+
function ExportMenu({ data, onExport }) {
|
|
200
241
|
return (
|
|
201
242
|
<MenuButton
|
|
202
243
|
buttonText="Export"
|
|
@@ -206,73 +247,425 @@ function ExportMenu({ onExport }) {
|
|
|
206
247
|
{ text: 'Export as CSV', onClick: () => onExport('csv') },
|
|
207
248
|
{ text: 'Export as Excel', onClick: () => onExport('xlsx') },
|
|
208
249
|
{ text: 'Export as PDF', onClick: () => onExport('pdf') },
|
|
250
|
+
{ text: 'Print', onClick: () => window.print() },
|
|
209
251
|
]}
|
|
210
252
|
/>
|
|
211
253
|
);
|
|
212
254
|
}
|
|
213
255
|
```
|
|
214
256
|
|
|
215
|
-
### Toolbar
|
|
257
|
+
### Actions Menu in Toolbar
|
|
216
258
|
|
|
217
259
|
```tsx
|
|
218
260
|
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() {
|
|
219
288
|
return (
|
|
220
289
|
<MenuButton
|
|
221
|
-
buttonText="
|
|
222
|
-
variant="
|
|
290
|
+
buttonText="Products"
|
|
291
|
+
variant="plain"
|
|
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"
|
|
223
367
|
color="neutral"
|
|
224
|
-
disabled={selectedItems.length === 0}
|
|
225
368
|
items={[
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
369
|
+
{
|
|
370
|
+
text: settings.darkMode ? '☀️ Light Mode' : '🌙 Dark Mode',
|
|
371
|
+
onClick: () => onSettingChange('darkMode', !settings.darkMode),
|
|
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
|
+
},
|
|
230
381
|
]}
|
|
231
382
|
/>
|
|
232
383
|
);
|
|
233
384
|
}
|
|
234
385
|
```
|
|
235
386
|
|
|
236
|
-
##
|
|
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
|
|
237
441
|
|
|
238
|
-
|
|
442
|
+
```tsx
|
|
443
|
+
// Small
|
|
444
|
+
<MenuButton buttonText="Small" size="sm" items={items} />
|
|
239
445
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<MenuButton buttonText="Export Options" items={exportItems} />
|
|
446
|
+
// Medium (default)
|
|
447
|
+
<MenuButton buttonText="Medium" size="md" items={items} />
|
|
243
448
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
449
|
+
// Large
|
|
450
|
+
<MenuButton buttonText="Large" size="lg" items={items} />
|
|
451
|
+
```
|
|
247
452
|
|
|
248
|
-
|
|
453
|
+
### Custom Button Component
|
|
249
454
|
|
|
250
|
-
|
|
455
|
+
```tsx
|
|
456
|
+
// Use with custom components like Link
|
|
457
|
+
import { Link } from 'react-router-dom';
|
|
251
458
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
459
|
+
<MenuButton
|
|
460
|
+
buttonText="Dashboard"
|
|
461
|
+
buttonComponent={Link}
|
|
462
|
+
buttonComponentProps={{ to: '/dashboard' }}
|
|
463
|
+
showIcon={false}
|
|
464
|
+
items={items}
|
|
465
|
+
/>
|
|
466
|
+
```
|
|
257
467
|
|
|
258
|
-
|
|
468
|
+
### Decorators
|
|
259
469
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
470
|
+
```tsx
|
|
471
|
+
// Start decorator (icon before text)
|
|
472
|
+
<MenuButton
|
|
473
|
+
buttonText="Export"
|
|
474
|
+
startDecorator={<DownloadIcon />}
|
|
475
|
+
items={exportItems}
|
|
476
|
+
/>
|
|
263
477
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
478
|
+
// End decorator (badge after text)
|
|
479
|
+
<MenuButton
|
|
480
|
+
buttonText="Inbox"
|
|
481
|
+
endDecorator={<Chip size="sm">5</Chip>}
|
|
482
|
+
items={inboxItems}
|
|
483
|
+
/>
|
|
270
484
|
|
|
271
|
-
|
|
485
|
+
// Both decorators
|
|
486
|
+
<MenuButton
|
|
487
|
+
buttonText="User"
|
|
488
|
+
startDecorator={<Avatar size="sm" />}
|
|
489
|
+
endDecorator={<ChevronDownIcon />}
|
|
490
|
+
showIcon={false}
|
|
491
|
+
items={userItems}
|
|
492
|
+
/>
|
|
493
|
+
```
|
|
272
494
|
|
|
273
495
|
## Accessibility
|
|
274
496
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
## Best Practices
|
|
534
|
+
|
|
535
|
+
### ✅ Do
|
|
536
|
+
|
|
537
|
+
1. **Use clear, action-oriented labels**: Button text should indicate what the menu contains
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
// ✅ Good: Clear menu purpose
|
|
541
|
+
<MenuButton buttonText="Export Options" items={exportItems} />
|
|
542
|
+
<MenuButton buttonText="User Actions" items={userItems} />
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
2. **Group related actions**: Keep menu items logically related
|
|
546
|
+
|
|
547
|
+
```tsx
|
|
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
|
+
```
|
|
559
|
+
|
|
560
|
+
3. **Use appropriate variants**: Match the button style to its context
|
|
561
|
+
|
|
562
|
+
```tsx
|
|
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
|
+
```
|
|
567
|
+
|
|
568
|
+
4. **Limit menu items**: Keep menus scannable (5-7 items max)
|
|
569
|
+
|
|
570
|
+
### ❌ Don't
|
|
571
|
+
|
|
572
|
+
1. **Don't use for form inputs**: Use Select for value selection
|
|
573
|
+
|
|
574
|
+
```tsx
|
|
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
|
+
```
|
|
670
|
+
|
|
671
|
+
MenuButton provides a convenient way to group related actions behind a single button trigger. Use it for account menus, action groups, and navigation shortcuts while keeping the menu contents focused and scannable. For icon-only triggers in space-constrained areas, consider IconMenuButton instead.
|