# ProfileMenu

## Introduction

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.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon',
  chip: 'PDT',
  caption: 'j.gordon@haulla.com'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

| Field                        | Description | Default |
| ---------------------------- | ----------- | ------- |
| Controls resolved at runtime | —           | —       |

## Usage

```tsx
import { ProfileMenu } from '@ceed/ads';

function Header() {
  return (
    <ProfileMenu
      profile={{
        name: 'John Doe',
        caption: 'john@example.com',
        chip: 'Admin',
      }}
      menuItems={[
        { label: 'Profile', onClick: () => navigate('/profile') },
        { label: 'Settings', onClick: () => navigate('/settings') },
        { label: 'Sign Out', onClick: () => signOut() },
      ]}
    />
  );
}
```

## Examples

### Default

ProfileMenu with full profile information including name, chip, and caption.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon',
  chip: 'PDT',
  caption: 'j.gordon@haulla.com'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

### Without Caption

Profile showing only the name and chip badge.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon',
  chip: 'PDT'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

### Without Chip

Profile showing name and caption without a badge.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon',
  caption: 'j.gordon@haulla.com'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

### Only Name

Minimal profile with just the user's name.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

### Without Menu Items

Profile card without any menu actions.

```tsx
<ProfileMenu
  open
  profile={{
  name: 'John Gordon',
  chip: 'PDT',
  caption: 'j.gordon@haulla.com'
}}
  menuItems={[]}
/>
```

### Sizes

ProfileMenu supports different sizes for various layouts.

```tsx
<Stack direction="row" gap="150px">
  <ProfileMenu {...args} size="md" profile={{
    name: 'John Gordon',
    chip: 'PDT',
    caption: 'j.gordon@haulla.com'
  }} menuItems={[{
    label: 'Menu Item1',
    onClick: fn()
  }, {
    label: 'Menu Item2',
    onClick: fn()
  }, {
    label: 'Menu Item3',
    onClick: fn()
  }]} />
  <ProfileMenu {...args} size="sm" profile={{
    name: 'John Gordon',
    chip: 'PDT',
    caption: 'j.gordon@haulla.com'
  }} menuItems={[{
    label: 'Menu Item1',
    onClick: fn()
  }, {
    label: 'Menu Item2',
    onClick: fn()
  }, {
    label: 'Menu Item3',
    onClick: fn()
  }]} />
</Stack>
```

### With Profile Image

Display a custom avatar image instead of generated initials.

```tsx
<ProfileMenu
  profile={{
  name: 'John Gordon',
  chip: 'PDT',
  caption: 'j.gordon@haulla.com',
  image: {
    src: 'https://i.pravatar.cc/300?u=test_profile',
    alt: 'User Avatar'
  }
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

### With Korean Name

ProfileMenu automatically generates initials from various name formats.

```tsx
<>
  <ProfileMenu {...args} profile={{
    name: '홍길동',
    chip: 'PDT',
    caption: 'j.gordon@haulla.com'
  }} />
  <ProfileMenu {...args} profile={{
    name: '제갈공명',
    chip: 'PDT',
    caption: 'j.gordon@haulla.com'
  }} />
</>
```

### Controlled

Programmatically control the menu's open state.

```tsx
<ProfileMenu {...args} open={open} onOpenChange={setOpen} />
```

### Uncontrolled

Let the component manage its own open state internally.

```tsx
<ProfileMenu
  defaultOpen={false}
  profile={{
  name: 'John Gordon',
  chip: 'PDT',
  caption: 'j.gordon@haulla.com'
}}
  menuItems={[{
  label: 'Menu Item1',
  onClick: fn()
}, {
  label: 'Menu Item2',
  onClick: fn()
}, {
  label: 'Menu Item3',
  onClick: fn()
}]}
/>
```

## When to Use

### ✅ Good Use Cases

- **Application header**: Display current user and account actions
- **Admin dashboards**: Quick access to user settings and logout
- **Multi-user applications**: Show which account is active
- **Settings access**: Provide shortcuts to profile and preferences
- **Authentication flows**: Display logged-in user with sign-out option

### ❌ When Not to Use

- **Anonymous users**: Don't show ProfileMenu for non-authenticated users
- **Simple navigation**: Use regular dropdown for non-profile menus
- **Primary actions**: Critical actions belong in the main UI, not hidden in profile
- **Complex forms**: Don't put forms or heavy content in the dropdown
- **Mobile navigation**: Consider slide-out drawer or full-screen menu instead

## Common Use Cases

### Header Navigation

```tsx
function AppHeader() {
  const { user, signOut } = useAuth();

  return (
    <header>
      <Logo />
      <Navigation />
      <ProfileMenu
        profile={{
          name: user.name,
          caption: user.email,
          chip: user.role,
          image: user.avatar ? { src: user.avatar, alt: user.name } : undefined,
        }}
        menuItems={[
          { label: 'My Profile', onClick: () => navigate('/profile') },
          { label: 'Account Settings', onClick: () => navigate('/settings') },
          { label: 'Help & Support', onClick: () => window.open('/help') },
          { label: 'Sign Out', onClick: signOut },
        ]}
      />
    </header>
  );
}
```

### Multi-Role User

```tsx
function RoleBasedProfileMenu({ user, currentRole, switchRole }) {
  const menuItems = [
    { label: 'My Profile', onClick: () => navigate('/profile') },
    { label: 'Settings', onClick: () => navigate('/settings') },
  ];

  // Add role switching if user has multiple roles
  if (user.roles.length > 1) {
    user.roles
      .filter((role) => role !== currentRole)
      .forEach((role) => {
        menuItems.push({
          label: `Switch to ${role}`,
          onClick: () => switchRole(role),
        });
      });
  }

  menuItems.push({ label: 'Sign Out', onClick: () => signOut() });

  return (
    <ProfileMenu
      profile={{
        name: user.name,
        caption: user.email,
        chip: currentRole,
      }}
      menuItems={menuItems}
    />
  );
}
```

### Organization Context

```tsx
function OrgProfileMenu({ user, organization }) {
  return (
    <ProfileMenu
      profile={{
        name: user.name,
        caption: organization.name,
        chip: user.organizationRole,
        image: user.avatar ? { src: user.avatar, alt: user.name } : undefined,
      }}
      menuItems={[
        { label: 'My Account', onClick: () => navigate('/account') },
        { label: 'Organization Settings', onClick: () => navigate('/org/settings') },
        { label: 'Switch Organization', onClick: () => openOrgSwitcher() },
        { label: 'Sign Out', onClick: () => signOut() },
      ]}
    />
  );
}
```

### With Custom Initial Generation

```tsx
function CustomInitialsProfileMenu({ user }) {
  // Custom function to generate initials
  const getInitial = (name) => {
    // For Korean names, use first character
    if (/[\uAC00-\uD7AF]/.test(name)) {
      return name.charAt(0);
    }
    // For English names, use first letters of first and last name
    const parts = name.split(' ');
    if (parts.length >= 2) {
      return `${parts[0][0]}${parts[parts.length - 1][0]}`;
    }
    return name.substring(0, 2).toUpperCase();
  };

  return (
    <ProfileMenu
      profile={{
        name: user.name,
        caption: user.email,
      }}
      menuItems={[
        { label: 'Profile', onClick: () => navigate('/profile') },
        { label: 'Sign Out', onClick: () => signOut() },
      ]}
      getInitial={getInitial}
    />
  );
}
```

## Props and Customization

### Key Props

| Prop           | Type                       | Default | Description                        |
| -------------- | -------------------------- | ------- | ---------------------------------- |
| `profile`      | `Profile`                  | -       | User profile information           |
| `menuItems`    | `MenuItem[]`               | `[]`    | Array of menu actions              |
| `open`         | `boolean`                  | -       | Controlled open state              |
| `defaultOpen`  | `boolean`                  | `false` | Default open state (uncontrolled)  |
| `onOpenChange` | `(open: boolean) => void`  | -       | Callback when open state changes   |
| `size`         | `'sm' \| 'md'`             | `'md'`  | Size variant                       |
| `getInitial`   | `(name: string) => string` | -       | Custom initial generation function |

### Profile Type

```tsx
interface Profile {
  name: string;              // User's display name (required)
  caption?: string;          // Secondary text (email, role description)
  chip?: string;             // Badge text (role, status)
  image?: {
    src: string;             // Avatar image URL
    alt: string;             // Alt text for accessibility
  };
}
```

### MenuItem Type

```tsx
interface MenuItem {
  label: string;             // Menu item text
  onClick?: () => void;      // Click handler
  // Plus any other MenuItem props from Joy UI
}
```

### Controlled vs Uncontrolled

```tsx
// Uncontrolled - component manages state
<ProfileMenu
  defaultOpen={false}
  profile={profile}
  menuItems={menuItems}
/>

// Controlled - you manage state
const [open, setOpen] = useState(false);
<ProfileMenu
  open={open}
  onOpenChange={setOpen}
  profile={profile}
  menuItems={menuItems}
/>
```

## Accessibility

ProfileMenu includes built-in accessibility features:

### ARIA Attributes

- Button has proper `aria-haspopup` and `aria-expanded` attributes
- Menu uses `role="menu"` with proper `role="menuitem"` for items
- Focus management when opening and closing

### Keyboard Navigation

- **Tab**: Focus the profile button
- **Enter/Space**: Open the menu
- **Arrow Down**: Move to next menu item
- **Arrow Up**: Move to previous menu item
- **Enter**: Activate focused menu item
- **Escape**: Close the menu

### Screen Reader Support

```tsx
// Avatar announces the user name
<ProfileMenu
  profile={{
    name: 'John Doe',  // Avatar reads "JD, John Doe"
    image: { src: '...', alt: 'John Doe profile picture' },
  }}
/>
```

### Focus Management

- Focus moves to first menu item when opened
- Focus returns to button when menu closes
- Click outside closes menu and returns focus

## Best Practices

### ✅ Do

1. **Include essential actions**: Always include profile and sign-out options

```tsx
// ✅ Good: Essential menu items
<ProfileMenu
  menuItems={[
    { label: 'Profile', onClick: handleProfile },
    { label: 'Settings', onClick: handleSettings },
    { label: 'Sign Out', onClick: handleSignOut },
  ]}
/>
```

2. **Show user context**: Display role or organization context in the chip

```tsx
// ✅ Good: Clear user context
<ProfileMenu
  profile={{
    name: 'Jane Smith',
    caption: 'jane@company.com',
    chip: 'Admin',  // Shows current role
  }}
/>
```

3. **Use descriptive labels**: Menu items should clearly describe actions

4. **Provide avatar images**: When available, show actual profile photos

### ❌ Don't

1. **Don't overload with items**: Keep menu items to 5-7 maximum

```tsx
// ❌ Bad: Too many items
<ProfileMenu
  menuItems={[
    { label: 'Profile' },
    { label: 'Settings' },
    { label: 'Billing' },
    { label: 'Team' },
    { label: 'Integrations' },
    { label: 'API Keys' },
    { label: 'Audit Log' },
    { label: 'Help' },
    { label: 'Sign Out' },
  ]}
/>
```

2. **Don't hide critical actions**: Important actions should be in main navigation

3. **Don't use for non-user content**: ProfileMenu is specifically for user accounts

4. **Don't leave profile empty**: Always provide at least the name

## Performance Considerations

### Lazy Load Menu Content

For dynamic menu items, fetch data when needed:

```tsx
function ProfileMenuWithDynamicItems({ user }) {
  const [menuItems, setMenuItems] = useState(baseMenuItems);

  const handleOpenChange = async (isOpen) => {
    if (isOpen && !menuItems.includes(dynamicItems)) {
      const items = await fetchUserMenuItems(user.id);
      setMenuItems([...baseMenuItems, ...items]);
    }
  };

  return (
    <ProfileMenu
      profile={user}
      menuItems={menuItems}
      onOpenChange={handleOpenChange}
    />
  );
}
```

### Memoize Menu Items

When menu items are computed, memoize them:

```tsx
const menuItems = useMemo(() => [
  { label: 'Profile', onClick: handleProfile },
  { label: 'Settings', onClick: handleSettings },
  ...(user.isAdmin ? [{ label: 'Admin', onClick: handleAdmin }] : []),
  { label: 'Sign Out', onClick: handleSignOut },
], [user.isAdmin, handleProfile, handleSettings, handleAdmin, handleSignOut]);
```

### Optimize Avatar Loading

For profile images, ensure proper caching and loading:

```tsx
<ProfileMenu
  profile={{
    name: user.name,
    image: user.avatarUrl
      ? { src: `${user.avatarUrl}?size=64`, alt: user.name }
      : undefined,
  }}
/>
```

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.
