@ceed/ads 1.29.1 → 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/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 +289 -288
- package/dist/index.d.ts +1 -1
- package/dist/index.js +426 -369
- 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
|
@@ -2,9 +2,7 @@
|
|
|
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
|
|
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.
|
|
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 (like email), and customizable menu items. ProfileMenu is typically placed in the header or navigation area of an application.
|
|
8
6
|
|
|
9
7
|
```tsx
|
|
10
8
|
<ProfileMenu
|
|
@@ -54,11 +52,11 @@ function Header() {
|
|
|
54
52
|
}
|
|
55
53
|
```
|
|
56
54
|
|
|
57
|
-
##
|
|
55
|
+
## Examples
|
|
58
56
|
|
|
59
57
|
### Default
|
|
60
58
|
|
|
61
|
-
ProfileMenu with full profile information including name, chip
|
|
59
|
+
ProfileMenu with full profile information including name, chip, and caption.
|
|
62
60
|
|
|
63
61
|
```tsx
|
|
64
62
|
<ProfileMenu
|
|
@@ -83,7 +81,7 @@ ProfileMenu with full profile information including name, chip badge, and captio
|
|
|
83
81
|
|
|
84
82
|
### Without Caption
|
|
85
83
|
|
|
86
|
-
Profile showing only the name and chip badge
|
|
84
|
+
Profile showing only the name and chip badge.
|
|
87
85
|
|
|
88
86
|
```tsx
|
|
89
87
|
<ProfileMenu
|
|
@@ -107,7 +105,7 @@ Profile showing only the name and chip badge, without a secondary caption line.
|
|
|
107
105
|
|
|
108
106
|
### Without Chip
|
|
109
107
|
|
|
110
|
-
Profile showing
|
|
108
|
+
Profile showing name and caption without a badge.
|
|
111
109
|
|
|
112
110
|
```tsx
|
|
113
111
|
<ProfileMenu
|
|
@@ -131,7 +129,7 @@ Profile showing the name and caption, without a badge chip.
|
|
|
131
129
|
|
|
132
130
|
### Only Name
|
|
133
131
|
|
|
134
|
-
Minimal profile
|
|
132
|
+
Minimal profile with just the user's name.
|
|
135
133
|
|
|
136
134
|
```tsx
|
|
137
135
|
<ProfileMenu
|
|
@@ -154,7 +152,7 @@ Minimal profile display with just the user's name. Useful for compact layouts wh
|
|
|
154
152
|
|
|
155
153
|
### Without Menu Items
|
|
156
154
|
|
|
157
|
-
Profile card without any
|
|
155
|
+
Profile card without any menu actions.
|
|
158
156
|
|
|
159
157
|
```tsx
|
|
160
158
|
<ProfileMenu
|
|
@@ -168,9 +166,9 @@ Profile card without any action menu items. The dropdown displays only the user
|
|
|
168
166
|
/>
|
|
169
167
|
```
|
|
170
168
|
|
|
171
|
-
|
|
169
|
+
### Sizes
|
|
172
170
|
|
|
173
|
-
ProfileMenu supports
|
|
171
|
+
ProfileMenu supports different sizes for various layouts.
|
|
174
172
|
|
|
175
173
|
```tsx
|
|
176
174
|
<Stack direction="row" gap="150px">
|
|
@@ -205,11 +203,9 @@ ProfileMenu supports `md` (default) and `sm` size variants for use in different
|
|
|
205
203
|
</Stack>
|
|
206
204
|
```
|
|
207
205
|
|
|
208
|
-
## Avatar
|
|
209
|
-
|
|
210
206
|
### With Profile Image
|
|
211
207
|
|
|
212
|
-
|
|
208
|
+
Display a custom avatar image instead of generated initials.
|
|
213
209
|
|
|
214
210
|
```tsx
|
|
215
211
|
<ProfileMenu
|
|
@@ -237,7 +233,7 @@ When a user has a profile picture, pass the `image` property with `src` and `alt
|
|
|
237
233
|
|
|
238
234
|
### With Korean Name
|
|
239
235
|
|
|
240
|
-
ProfileMenu automatically generates initials from various name formats
|
|
236
|
+
ProfileMenu automatically generates initials from various name formats.
|
|
241
237
|
|
|
242
238
|
```tsx
|
|
243
239
|
<>
|
|
@@ -254,11 +250,9 @@ ProfileMenu automatically generates initials from various name formats, includin
|
|
|
254
250
|
</>
|
|
255
251
|
```
|
|
256
252
|
|
|
257
|
-
## Controlled vs Uncontrolled
|
|
258
|
-
|
|
259
253
|
### Controlled
|
|
260
254
|
|
|
261
|
-
|
|
255
|
+
Programmatically control the menu's open state.
|
|
262
256
|
|
|
263
257
|
```tsx
|
|
264
258
|
<ProfileMenu {...args} open={open} onOpenChange={setOpen} />
|
|
@@ -266,7 +260,7 @@ Use the `open` and `onOpenChange` props to control the menu's open state program
|
|
|
266
260
|
|
|
267
261
|
### Uncontrolled
|
|
268
262
|
|
|
269
|
-
|
|
263
|
+
Let the component manage its own open state internally.
|
|
270
264
|
|
|
271
265
|
```tsx
|
|
272
266
|
<ProfileMenu
|
|
@@ -289,6 +283,24 @@ Use `defaultOpen` to set the initial state, then let the component manage its ow
|
|
|
289
283
|
/>
|
|
290
284
|
```
|
|
291
285
|
|
|
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
|
+
|
|
292
304
|
## Common Use Cases
|
|
293
305
|
|
|
294
306
|
### Header Navigation
|
|
@@ -298,7 +310,7 @@ function AppHeader() {
|
|
|
298
310
|
const { user, signOut } = useAuth();
|
|
299
311
|
|
|
300
312
|
return (
|
|
301
|
-
<header
|
|
313
|
+
<header>
|
|
302
314
|
<Logo />
|
|
303
315
|
<Navigation />
|
|
304
316
|
<ProfileMenu
|
|
@@ -311,6 +323,7 @@ function AppHeader() {
|
|
|
311
323
|
menuItems={[
|
|
312
324
|
{ label: 'My Profile', onClick: () => navigate('/profile') },
|
|
313
325
|
{ label: 'Account Settings', onClick: () => navigate('/settings') },
|
|
326
|
+
{ label: 'Help & Support', onClick: () => window.open('/help') },
|
|
314
327
|
{ label: 'Sign Out', onClick: signOut },
|
|
315
328
|
]}
|
|
316
329
|
/>
|
|
@@ -322,19 +335,26 @@ function AppHeader() {
|
|
|
322
335
|
### Multi-Role User
|
|
323
336
|
|
|
324
337
|
```tsx
|
|
325
|
-
function RoleBasedProfileMenu({ user, currentRole, switchRole
|
|
338
|
+
function RoleBasedProfileMenu({ user, currentRole, switchRole }) {
|
|
326
339
|
const menuItems = [
|
|
327
340
|
{ label: 'My Profile', onClick: () => navigate('/profile') },
|
|
328
341
|
{ label: 'Settings', onClick: () => navigate('/settings') },
|
|
329
|
-
...user.roles
|
|
330
|
-
.filter((role) => role !== currentRole)
|
|
331
|
-
.map((role) => ({
|
|
332
|
-
label: `Switch to ${role}`,
|
|
333
|
-
onClick: () => switchRole(role),
|
|
334
|
-
})),
|
|
335
|
-
{ label: 'Sign Out', onClick: signOut },
|
|
336
342
|
];
|
|
337
343
|
|
|
344
|
+
// Add role switching if user has multiple roles
|
|
345
|
+
if (user.roles.length > 1) {
|
|
346
|
+
user.roles
|
|
347
|
+
.filter((role) => role !== currentRole)
|
|
348
|
+
.forEach((role) => {
|
|
349
|
+
menuItems.push({
|
|
350
|
+
label: `Switch to ${role}`,
|
|
351
|
+
onClick: () => switchRole(role),
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
menuItems.push({ label: 'Sign Out', onClick: () => signOut() });
|
|
357
|
+
|
|
338
358
|
return (
|
|
339
359
|
<ProfileMenu
|
|
340
360
|
profile={{
|
|
@@ -371,46 +391,249 @@ function OrgProfileMenu({ user, organization }) {
|
|
|
371
391
|
}
|
|
372
392
|
```
|
|
373
393
|
|
|
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
|
+
|
|
374
523
|
## Best Practices
|
|
375
524
|
|
|
376
|
-
|
|
525
|
+
### ✅ Do
|
|
526
|
+
|
|
527
|
+
1. **Include essential actions**: Always include profile and sign-out options
|
|
377
528
|
|
|
378
529
|
```tsx
|
|
379
|
-
// ✅ Good: Essential menu items
|
|
530
|
+
// ✅ Good: Essential menu items
|
|
380
531
|
<ProfileMenu
|
|
381
532
|
menuItems={[
|
|
382
533
|
{ label: 'Profile', onClick: handleProfile },
|
|
534
|
+
{ label: 'Settings', onClick: handleSettings },
|
|
383
535
|
{ label: 'Sign Out', onClick: handleSignOut },
|
|
384
536
|
]}
|
|
385
537
|
/>
|
|
386
|
-
|
|
387
|
-
// ❌ Bad: Missing sign-out option
|
|
388
|
-
<ProfileMenu menuItems={[{ label: 'Profile', onClick: handleProfile }]} />
|
|
389
538
|
```
|
|
390
539
|
|
|
391
|
-
2. **Show user context**:
|
|
540
|
+
2. **Show user context**: Display role or organization context in the chip
|
|
392
541
|
|
|
393
542
|
```tsx
|
|
394
543
|
// ✅ Good: Clear user context
|
|
395
544
|
<ProfileMenu
|
|
396
|
-
profile={{
|
|
545
|
+
profile={{
|
|
546
|
+
name: 'Jane Smith',
|
|
547
|
+
caption: 'jane@company.com',
|
|
548
|
+
chip: 'Admin', // Shows current role
|
|
549
|
+
}}
|
|
397
550
|
/>
|
|
398
551
|
```
|
|
399
552
|
|
|
400
|
-
3. **
|
|
553
|
+
3. **Use descriptive labels**: Menu items should clearly describe actions
|
|
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
|
|
401
560
|
|
|
402
561
|
```tsx
|
|
403
|
-
// ❌ Bad: Too many items
|
|
404
|
-
<ProfileMenu
|
|
562
|
+
// ❌ Bad: Too many items
|
|
563
|
+
<ProfileMenu
|
|
564
|
+
menuItems={[
|
|
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
|
+
]}
|
|
575
|
+
/>
|
|
405
576
|
```
|
|
406
577
|
|
|
407
|
-
|
|
578
|
+
2. **Don't hide critical actions**: Important actions should be in main navigation
|
|
408
579
|
|
|
409
|
-
|
|
580
|
+
3. **Don't use for non-user content**: ProfileMenu is specifically for user accounts
|
|
410
581
|
|
|
411
|
-
|
|
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:
|
|
589
|
+
|
|
590
|
+
```tsx
|
|
591
|
+
function ProfileMenuWithDynamicItems({ user }) {
|
|
592
|
+
const [menuItems, setMenuItems] = useState(baseMenuItems);
|
|
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]);
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Optimize Avatar Loading
|
|
625
|
+
|
|
626
|
+
For profile images, ensure proper caching and loading:
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
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
|
+
```
|
|
412
638
|
|
|
413
|
-
|
|
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.
|
|
639
|
+
ProfileMenu provides a polished user account interface for admin applications. Place it in your header for easy access to user-related actions and account management.
|