@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
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
ProfileMenu is a user account dropdown component that displays the current user's profile information and provides quick access to account-related actions. It combines an avatar button with a dropdown menu containing the user's name, optional badge (chip), caption (
|
|
5
|
+
ProfileMenu is a user account dropdown component that displays the current user's profile information and provides quick access to account-related actions. It combines an avatar trigger button with a dropdown menu containing the user's name, an optional badge (chip), caption text (such as email), and customizable menu items.
|
|
6
|
+
|
|
7
|
+
ProfileMenu is typically placed in the application header or top navigation bar. It supports both controlled and uncontrolled open state, avatar image or auto-generated initials, multiple size variants, and flexible menu item configuration.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<ProfileMenu
|
|
@@ -52,11 +54,11 @@ function Header() {
|
|
|
52
54
|
}
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
##
|
|
57
|
+
## Profile Variations
|
|
56
58
|
|
|
57
59
|
### Default
|
|
58
60
|
|
|
59
|
-
ProfileMenu with full profile information including name, chip, and caption.
|
|
61
|
+
ProfileMenu with full profile information including name, chip badge, and caption.
|
|
60
62
|
|
|
61
63
|
```tsx
|
|
62
64
|
<ProfileMenu
|
|
@@ -81,7 +83,7 @@ ProfileMenu with full profile information including name, chip, and caption.
|
|
|
81
83
|
|
|
82
84
|
### Without Caption
|
|
83
85
|
|
|
84
|
-
Profile showing only the name and chip badge.
|
|
86
|
+
Profile showing only the name and chip badge, without a secondary caption line.
|
|
85
87
|
|
|
86
88
|
```tsx
|
|
87
89
|
<ProfileMenu
|
|
@@ -105,7 +107,7 @@ Profile showing only the name and chip badge.
|
|
|
105
107
|
|
|
106
108
|
### Without Chip
|
|
107
109
|
|
|
108
|
-
Profile showing name and caption without a badge.
|
|
110
|
+
Profile showing the name and caption, without a badge chip.
|
|
109
111
|
|
|
110
112
|
```tsx
|
|
111
113
|
<ProfileMenu
|
|
@@ -129,7 +131,7 @@ Profile showing name and caption without a badge.
|
|
|
129
131
|
|
|
130
132
|
### Only Name
|
|
131
133
|
|
|
132
|
-
Minimal profile with just the user's name.
|
|
134
|
+
Minimal profile display with just the user's name. Useful for compact layouts where additional context is not needed.
|
|
133
135
|
|
|
134
136
|
```tsx
|
|
135
137
|
<ProfileMenu
|
|
@@ -152,7 +154,7 @@ Minimal profile with just the user's name.
|
|
|
152
154
|
|
|
153
155
|
### Without Menu Items
|
|
154
156
|
|
|
155
|
-
Profile card without any menu
|
|
157
|
+
Profile card without any action menu items. The dropdown displays only the user information.
|
|
156
158
|
|
|
157
159
|
```tsx
|
|
158
160
|
<ProfileMenu
|
|
@@ -166,9 +168,9 @@ Profile card without any menu actions.
|
|
|
166
168
|
/>
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
## Sizes
|
|
170
172
|
|
|
171
|
-
ProfileMenu supports
|
|
173
|
+
ProfileMenu supports `md` (default) and `sm` size variants for use in different layout densities.
|
|
172
174
|
|
|
173
175
|
```tsx
|
|
174
176
|
<Stack direction="row" gap="150px">
|
|
@@ -203,9 +205,11 @@ ProfileMenu supports different sizes for various layouts.
|
|
|
203
205
|
</Stack>
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
## Avatar
|
|
209
|
+
|
|
206
210
|
### With Profile Image
|
|
207
211
|
|
|
208
|
-
|
|
212
|
+
When a user has a profile picture, pass the `image` property with `src` and `alt`. The image replaces the auto-generated initials.
|
|
209
213
|
|
|
210
214
|
```tsx
|
|
211
215
|
<ProfileMenu
|
|
@@ -233,7 +237,7 @@ Display a custom avatar image instead of generated initials.
|
|
|
233
237
|
|
|
234
238
|
### With Korean Name
|
|
235
239
|
|
|
236
|
-
ProfileMenu automatically generates initials from various name formats.
|
|
240
|
+
ProfileMenu automatically generates initials from various name formats, including Korean names.
|
|
237
241
|
|
|
238
242
|
```tsx
|
|
239
243
|
<>
|
|
@@ -250,9 +254,11 @@ ProfileMenu automatically generates initials from various name formats.
|
|
|
250
254
|
</>
|
|
251
255
|
```
|
|
252
256
|
|
|
257
|
+
## Controlled vs Uncontrolled
|
|
258
|
+
|
|
253
259
|
### Controlled
|
|
254
260
|
|
|
255
|
-
|
|
261
|
+
Use the `open` and `onOpenChange` props to control the menu's open state programmatically.
|
|
256
262
|
|
|
257
263
|
```tsx
|
|
258
264
|
<ProfileMenu {...args} open={open} onOpenChange={setOpen} />
|
|
@@ -260,7 +266,7 @@ Programmatically control the menu's open state.
|
|
|
260
266
|
|
|
261
267
|
### Uncontrolled
|
|
262
268
|
|
|
263
|
-
|
|
269
|
+
Use `defaultOpen` to set the initial state, then let the component manage its own open/close behavior internally.
|
|
264
270
|
|
|
265
271
|
```tsx
|
|
266
272
|
<ProfileMenu
|
|
@@ -283,24 +289,6 @@ Let the component manage its own open state internally.
|
|
|
283
289
|
/>
|
|
284
290
|
```
|
|
285
291
|
|
|
286
|
-
## When to Use
|
|
287
|
-
|
|
288
|
-
### ✅ Good Use Cases
|
|
289
|
-
|
|
290
|
-
- **Application header**: Display current user and account actions
|
|
291
|
-
- **Admin dashboards**: Quick access to user settings and logout
|
|
292
|
-
- **Multi-user applications**: Show which account is active
|
|
293
|
-
- **Settings access**: Provide shortcuts to profile and preferences
|
|
294
|
-
- **Authentication flows**: Display logged-in user with sign-out option
|
|
295
|
-
|
|
296
|
-
### ❌ When Not to Use
|
|
297
|
-
|
|
298
|
-
- **Anonymous users**: Don't show ProfileMenu for non-authenticated users
|
|
299
|
-
- **Simple navigation**: Use regular dropdown for non-profile menus
|
|
300
|
-
- **Primary actions**: Critical actions belong in the main UI, not hidden in profile
|
|
301
|
-
- **Complex forms**: Don't put forms or heavy content in the dropdown
|
|
302
|
-
- **Mobile navigation**: Consider slide-out drawer or full-screen menu instead
|
|
303
|
-
|
|
304
292
|
## Common Use Cases
|
|
305
293
|
|
|
306
294
|
### Header Navigation
|
|
@@ -310,7 +298,7 @@ function AppHeader() {
|
|
|
310
298
|
const { user, signOut } = useAuth();
|
|
311
299
|
|
|
312
300
|
return (
|
|
313
|
-
<header>
|
|
301
|
+
<header style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
314
302
|
<Logo />
|
|
315
303
|
<Navigation />
|
|
316
304
|
<ProfileMenu
|
|
@@ -323,7 +311,6 @@ function AppHeader() {
|
|
|
323
311
|
menuItems={[
|
|
324
312
|
{ label: 'My Profile', onClick: () => navigate('/profile') },
|
|
325
313
|
{ label: 'Account Settings', onClick: () => navigate('/settings') },
|
|
326
|
-
{ label: 'Help & Support', onClick: () => window.open('/help') },
|
|
327
314
|
{ label: 'Sign Out', onClick: signOut },
|
|
328
315
|
]}
|
|
329
316
|
/>
|
|
@@ -335,25 +322,18 @@ function AppHeader() {
|
|
|
335
322
|
### Multi-Role User
|
|
336
323
|
|
|
337
324
|
```tsx
|
|
338
|
-
function RoleBasedProfileMenu({ user, currentRole, switchRole }) {
|
|
325
|
+
function RoleBasedProfileMenu({ user, currentRole, switchRole, signOut }) {
|
|
339
326
|
const menuItems = [
|
|
340
327
|
{ label: 'My Profile', onClick: () => navigate('/profile') },
|
|
341
328
|
{ label: 'Settings', onClick: () => navigate('/settings') },
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// Add role switching if user has multiple roles
|
|
345
|
-
if (user.roles.length > 1) {
|
|
346
|
-
user.roles
|
|
329
|
+
...user.roles
|
|
347
330
|
.filter((role) => role !== currentRole)
|
|
348
|
-
.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
menuItems.push({ label: 'Sign Out', onClick: () => signOut() });
|
|
331
|
+
.map((role) => ({
|
|
332
|
+
label: `Switch to ${role}`,
|
|
333
|
+
onClick: () => switchRole(role),
|
|
334
|
+
})),
|
|
335
|
+
{ label: 'Sign Out', onClick: signOut },
|
|
336
|
+
];
|
|
357
337
|
|
|
358
338
|
return (
|
|
359
339
|
<ProfileMenu
|
|
@@ -391,249 +371,46 @@ function OrgProfileMenu({ user, organization }) {
|
|
|
391
371
|
}
|
|
392
372
|
```
|
|
393
373
|
|
|
394
|
-
### With Custom Initial Generation
|
|
395
|
-
|
|
396
|
-
```tsx
|
|
397
|
-
function CustomInitialsProfileMenu({ user }) {
|
|
398
|
-
// Custom function to generate initials
|
|
399
|
-
const getInitial = (name) => {
|
|
400
|
-
// For Korean names, use first character
|
|
401
|
-
if (/[\uAC00-\uD7AF]/.test(name)) {
|
|
402
|
-
return name.charAt(0);
|
|
403
|
-
}
|
|
404
|
-
// For English names, use first letters of first and last name
|
|
405
|
-
const parts = name.split(' ');
|
|
406
|
-
if (parts.length >= 2) {
|
|
407
|
-
return `${parts[0][0]}${parts[parts.length - 1][0]}`;
|
|
408
|
-
}
|
|
409
|
-
return name.substring(0, 2).toUpperCase();
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
return (
|
|
413
|
-
<ProfileMenu
|
|
414
|
-
profile={{
|
|
415
|
-
name: user.name,
|
|
416
|
-
caption: user.email,
|
|
417
|
-
}}
|
|
418
|
-
menuItems={[
|
|
419
|
-
{ label: 'Profile', onClick: () => navigate('/profile') },
|
|
420
|
-
{ label: 'Sign Out', onClick: () => signOut() },
|
|
421
|
-
]}
|
|
422
|
-
getInitial={getInitial}
|
|
423
|
-
/>
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
## Props and Customization
|
|
429
|
-
|
|
430
|
-
### Key Props
|
|
431
|
-
|
|
432
|
-
| Prop | Type | Default | Description |
|
|
433
|
-
| -------------- | -------------------------- | ------- | ---------------------------------- |
|
|
434
|
-
| `profile` | `Profile` | - | User profile information |
|
|
435
|
-
| `menuItems` | `MenuItem[]` | `[]` | Array of menu actions |
|
|
436
|
-
| `open` | `boolean` | - | Controlled open state |
|
|
437
|
-
| `defaultOpen` | `boolean` | `false` | Default open state (uncontrolled) |
|
|
438
|
-
| `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
|
|
439
|
-
| `size` | `'sm' \| 'md'` | `'md'` | Size variant |
|
|
440
|
-
| `getInitial` | `(name: string) => string` | - | Custom initial generation function |
|
|
441
|
-
|
|
442
|
-
### Profile Type
|
|
443
|
-
|
|
444
|
-
```tsx
|
|
445
|
-
interface Profile {
|
|
446
|
-
name: string; // User's display name (required)
|
|
447
|
-
caption?: string; // Secondary text (email, role description)
|
|
448
|
-
chip?: string; // Badge text (role, status)
|
|
449
|
-
image?: {
|
|
450
|
-
src: string; // Avatar image URL
|
|
451
|
-
alt: string; // Alt text for accessibility
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### MenuItem Type
|
|
457
|
-
|
|
458
|
-
```tsx
|
|
459
|
-
interface MenuItem {
|
|
460
|
-
label: string; // Menu item text
|
|
461
|
-
onClick?: () => void; // Click handler
|
|
462
|
-
// Plus any other MenuItem props from Joy UI
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Controlled vs Uncontrolled
|
|
467
|
-
|
|
468
|
-
```tsx
|
|
469
|
-
// Uncontrolled - component manages state
|
|
470
|
-
<ProfileMenu
|
|
471
|
-
defaultOpen={false}
|
|
472
|
-
profile={profile}
|
|
473
|
-
menuItems={menuItems}
|
|
474
|
-
/>
|
|
475
|
-
|
|
476
|
-
// Controlled - you manage state
|
|
477
|
-
const [open, setOpen] = useState(false);
|
|
478
|
-
<ProfileMenu
|
|
479
|
-
open={open}
|
|
480
|
-
onOpenChange={setOpen}
|
|
481
|
-
profile={profile}
|
|
482
|
-
menuItems={menuItems}
|
|
483
|
-
/>
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
## Accessibility
|
|
487
|
-
|
|
488
|
-
ProfileMenu includes built-in accessibility features:
|
|
489
|
-
|
|
490
|
-
### ARIA Attributes
|
|
491
|
-
|
|
492
|
-
- Button has proper `aria-haspopup` and `aria-expanded` attributes
|
|
493
|
-
- Menu uses `role="menu"` with proper `role="menuitem"` for items
|
|
494
|
-
- Focus management when opening and closing
|
|
495
|
-
|
|
496
|
-
### Keyboard Navigation
|
|
497
|
-
|
|
498
|
-
- **Tab**: Focus the profile button
|
|
499
|
-
- **Enter/Space**: Open the menu
|
|
500
|
-
- **Arrow Down**: Move to next menu item
|
|
501
|
-
- **Arrow Up**: Move to previous menu item
|
|
502
|
-
- **Enter**: Activate focused menu item
|
|
503
|
-
- **Escape**: Close the menu
|
|
504
|
-
|
|
505
|
-
### Screen Reader Support
|
|
506
|
-
|
|
507
|
-
```tsx
|
|
508
|
-
// Avatar announces the user name
|
|
509
|
-
<ProfileMenu
|
|
510
|
-
profile={{
|
|
511
|
-
name: 'John Doe', // Avatar reads "JD, John Doe"
|
|
512
|
-
image: { src: '...', alt: 'John Doe profile picture' },
|
|
513
|
-
}}
|
|
514
|
-
/>
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### Focus Management
|
|
518
|
-
|
|
519
|
-
- Focus moves to first menu item when opened
|
|
520
|
-
- Focus returns to button when menu closes
|
|
521
|
-
- Click outside closes menu and returns focus
|
|
522
|
-
|
|
523
374
|
## Best Practices
|
|
524
375
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
1. **Include essential actions**: Always include profile and sign-out options
|
|
376
|
+
1. **Always include essential actions**: At minimum, provide access to profile viewing and sign-out.
|
|
528
377
|
|
|
529
378
|
```tsx
|
|
530
|
-
// ✅ Good: Essential menu items
|
|
379
|
+
// ✅ Good: Essential menu items present
|
|
531
380
|
<ProfileMenu
|
|
532
381
|
menuItems={[
|
|
533
382
|
{ label: 'Profile', onClick: handleProfile },
|
|
534
|
-
{ label: 'Settings', onClick: handleSettings },
|
|
535
383
|
{ label: 'Sign Out', onClick: handleSignOut },
|
|
536
384
|
]}
|
|
537
385
|
/>
|
|
538
|
-
```
|
|
539
386
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
```tsx
|
|
543
|
-
// ✅ Good: Clear user context
|
|
544
|
-
<ProfileMenu
|
|
545
|
-
profile={{
|
|
546
|
-
name: 'Jane Smith',
|
|
547
|
-
caption: 'jane@company.com',
|
|
548
|
-
chip: 'Admin', // Shows current role
|
|
549
|
-
}}
|
|
550
|
-
/>
|
|
387
|
+
// ❌ Bad: Missing sign-out option
|
|
388
|
+
<ProfileMenu menuItems={[{ label: 'Profile', onClick: handleProfile }]} />
|
|
551
389
|
```
|
|
552
390
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
4. **Provide avatar images**: When available, show actual profile photos
|
|
556
|
-
|
|
557
|
-
### ❌ Don't
|
|
558
|
-
|
|
559
|
-
1. **Don't overload with items**: Keep menu items to 5-7 maximum
|
|
391
|
+
2. **Show user context**: Use the `chip` prop to display the user's current role or status so they know which account context they are operating in.
|
|
560
392
|
|
|
561
393
|
```tsx
|
|
562
|
-
//
|
|
394
|
+
// ✅ Good: Clear user context
|
|
563
395
|
<ProfileMenu
|
|
564
|
-
|
|
565
|
-
{ label: 'Profile' },
|
|
566
|
-
{ label: 'Settings' },
|
|
567
|
-
{ label: 'Billing' },
|
|
568
|
-
{ label: 'Team' },
|
|
569
|
-
{ label: 'Integrations' },
|
|
570
|
-
{ label: 'API Keys' },
|
|
571
|
-
{ label: 'Audit Log' },
|
|
572
|
-
{ label: 'Help' },
|
|
573
|
-
{ label: 'Sign Out' },
|
|
574
|
-
]}
|
|
396
|
+
profile={{ name: 'Jane Smith', caption: 'jane@company.com', chip: 'Admin' }}
|
|
575
397
|
/>
|
|
576
398
|
```
|
|
577
399
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
3. **Don't use for non-user content**: ProfileMenu is specifically for user accounts
|
|
581
|
-
|
|
582
|
-
4. **Don't leave profile empty**: Always provide at least the name
|
|
583
|
-
|
|
584
|
-
## Performance Considerations
|
|
585
|
-
|
|
586
|
-
### Lazy Load Menu Content
|
|
587
|
-
|
|
588
|
-
For dynamic menu items, fetch data when needed:
|
|
400
|
+
3. **Keep menu items concise**: Limit the number of menu items to 5-7 maximum. Move less common actions to a dedicated settings page.
|
|
589
401
|
|
|
590
402
|
```tsx
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const handleOpenChange = async (isOpen) => {
|
|
595
|
-
if (isOpen && !menuItems.includes(dynamicItems)) {
|
|
596
|
-
const items = await fetchUserMenuItems(user.id);
|
|
597
|
-
setMenuItems([...baseMenuItems, ...items]);
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
return (
|
|
602
|
-
<ProfileMenu
|
|
603
|
-
profile={user}
|
|
604
|
-
menuItems={menuItems}
|
|
605
|
-
onOpenChange={handleOpenChange}
|
|
606
|
-
/>
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### Memoize Menu Items
|
|
612
|
-
|
|
613
|
-
When menu items are computed, memoize them:
|
|
614
|
-
|
|
615
|
-
```tsx
|
|
616
|
-
const menuItems = useMemo(() => [
|
|
617
|
-
{ label: 'Profile', onClick: handleProfile },
|
|
618
|
-
{ label: 'Settings', onClick: handleSettings },
|
|
619
|
-
...(user.isAdmin ? [{ label: 'Admin', onClick: handleAdmin }] : []),
|
|
620
|
-
{ label: 'Sign Out', onClick: handleSignOut },
|
|
621
|
-
], [user.isAdmin, handleProfile, handleSettings, handleAdmin, handleSignOut]);
|
|
403
|
+
// ❌ Bad: Too many items in the dropdown
|
|
404
|
+
<ProfileMenu menuItems={[item1, item2, item3, item4, item5, item6, item7, item8, item9]} />
|
|
622
405
|
```
|
|
623
406
|
|
|
624
|
-
|
|
407
|
+
4. **Provide avatar images when available**: Real profile photos help users quickly identify the active account, especially in multi-user or shared environments.
|
|
625
408
|
|
|
626
|
-
For
|
|
409
|
+
5. **Do not use for non-user content**: ProfileMenu is specifically designed for user account contexts. For general navigation menus, use a regular Dropdown or Menu.
|
|
627
410
|
|
|
628
|
-
|
|
629
|
-
<ProfileMenu
|
|
630
|
-
profile={{
|
|
631
|
-
name: user.name,
|
|
632
|
-
image: user.avatarUrl
|
|
633
|
-
? { src: `${user.avatarUrl}?size=64`, alt: user.name }
|
|
634
|
-
: undefined,
|
|
635
|
-
}}
|
|
636
|
-
/>
|
|
637
|
-
```
|
|
411
|
+
## Accessibility
|
|
638
412
|
|
|
639
|
-
|
|
413
|
+
- The avatar trigger button has `aria-haspopup` and `aria-expanded` attributes, informing assistive technology that it controls a menu.
|
|
414
|
+
- The dropdown menu uses `role="menu"` with `role="menuitem"` for each action, following WAI-ARIA menu button pattern.
|
|
415
|
+
- Keyboard navigation is fully supported: **Enter** or **Space** opens the menu, **Arrow Down/Up** navigates items, **Enter** activates a focused item, and **Escape** closes the menu.
|
|
416
|
+
- Focus returns to the trigger button when the menu is closed, maintaining a predictable focus flow for keyboard and screen reader users.
|