# MenuButton

## Introduction

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.

```tsx
<MenuButton
  buttonText="Dashboard..."
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
/>
```

| Field                | Description | Default                                                               |
| -------------------- | ----------- | --------------------------------------------------------------------- |
| buttonText           | —           | "Dashboard..."                                                        |
| buttonComponent      | —           | —                                                                     |
| buttonComponentProps | —           | —                                                                     |
| items                | —           | \[\{ text: 'Profile' }, \{ text: 'My account' }, \{ text: 'Logout' }] |
| showIcon             | —           | true                                                                  |
| startDecorator       | —           | —                                                                     |
| endDecorator         | —           | —                                                                     |
| placement            | —           | —                                                                     |
| variant              | —           | "solid"                                                               |
| color                | —           | "primary"                                                             |
| size                 | —           | —                                                                     |

> ⚠️ **Usage Warning** ⚠️
>
> Choose the right component for your use case:
>
> - **MenuButton**: For action menus triggered by a labeled button
> - **IconMenuButton**: For action menus triggered by an icon-only button
> - **Dropdown**: For more complex menu structures with custom triggers
> - **Select**: For form inputs where user selects a value (not actions)

## Usage

```tsx
import { MenuButton } from '@ceed/cds';

function UserMenu() {
  return (
    <MenuButton
      buttonText="Account"
      items={[
        { text: 'Profile', onClick: () => navigate('/profile') },
        { text: 'Settings', onClick: () => navigate('/settings') },
        { text: 'Logout', onClick: handleLogout },
      ]}
    />
  );
}
```

## Examples

### Playground

Interactive example with all controls.

```tsx
<MenuButton
  buttonText="Dashboard..."
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
/>
```

### Standalone (Without Icon)

Use as a simple navigation link without the dropdown icon.

```tsx
<MenuButton
  buttonText="Dashboard"
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon={false}
  variant="solid"
  color="primary"
  buttonComponent={Link}
  buttonComponentProps={{
  to: '/'
}}
/>
```

### With End Decorator

Add badges, chips, or icons at the end of the button.

```tsx
<MenuButton
  buttonText="Dashboard"
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
  endDecorator={<Chip color="primary">New</Chip>}
/>
```

### Placement: Bottom Start

Menu aligns to the left edge of the button.

```tsx
<MenuButton
  buttonText="Hi"
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
  placement="bottom-start"
/>
```

### Placement: Bottom

Menu centers below the button.

```tsx
<MenuButton
  buttonText="Hi"
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
  placement="bottom"
/>
```

### Placement: Bottom End

Menu aligns to the right edge of the button.

```tsx
<MenuButton
  buttonText="Hi"
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  showIcon
  variant="solid"
  color="primary"
  placement="bottom-end"
/>
```

## When to Use

### ✅ Good Use Cases

- **User account menus**: Profile, settings, logout actions
- **Action grouping**: Related actions in a toolbar
- **Navigation shortcuts**: Quick access to related pages
- **Overflow menus**: When space is limited
- **Context actions**: Page or section-specific actions
- **Export options**: Download, export, or share actions

### ❌ When Not to Use

- **Form value selection**: Use Select for picking values in forms
- **Single action**: Use Button for single actions
- **Icon-only trigger**: Use IconMenuButton for minimal space
- **Complex menus**: Use Dropdown for nested menus or custom content
- **Primary navigation**: Use Navigator or Tabs for main navigation

## Common Use Cases

### User Account Menu

```tsx
function AccountMenu({ user, onLogout }) {
  return (
    <MenuButton
      buttonText={user.name}
      startDecorator={<Avatar src={user.avatar} size="sm" />}
      variant="plain"
      color="neutral"
      items={[
        { text: 'Profile', onClick: () => navigate('/profile') },
        { text: 'Account Settings', onClick: () => navigate('/settings') },
        { text: 'Billing', onClick: () => navigate('/billing') },
        { text: 'Logout', onClick: onLogout },
      ]}
    />
  );
}
```

### Export Options Menu

```tsx
function ExportMenu({ data, onExport }) {
  return (
    <MenuButton
      buttonText="Export"
      variant="outlined"
      startDecorator={<DownloadIcon />}
      items={[
        { text: 'Export as CSV', onClick: () => onExport('csv') },
        { text: 'Export as Excel', onClick: () => onExport('xlsx') },
        { text: 'Export as PDF', onClick: () => onExport('pdf') },
        { text: 'Print', onClick: () => window.print() },
      ]}
    />
  );
}
```

### Actions Menu in Toolbar

```tsx
function ToolbarActions({ selectedItems, onAction }) {
  const hasSelection = selectedItems.length > 0;

  return (
    <Stack direction="row" gap={1}>
      <Button onClick={() => onAction('create')}>Create New</Button>
      <MenuButton
        buttonText="Actions"
        variant="outlined"
        color="neutral"
        disabled={!hasSelection}
        items={[
          { text: `Edit (${selectedItems.length})`, onClick: () => onAction('edit') },
          { text: 'Duplicate', onClick: () => onAction('duplicate') },
          { text: 'Move to Folder', onClick: () => onAction('move') },
          { text: 'Archive', onClick: () => onAction('archive') },
          { text: 'Delete', onClick: () => onAction('delete') },
        ]}
      />
    </Stack>
  );
}
```

### Navigation with Dropdown

```tsx
function ProductsMenu() {
  return (
    <MenuButton
      buttonText="Products"
      variant="plain"
      showIcon={true}
      items={[
        { text: 'All Products', onClick: () => navigate('/products') },
        { text: 'Categories', onClick: () => navigate('/products/categories') },
        { text: 'Inventory', onClick: () => navigate('/products/inventory') },
        { text: 'Price Lists', onClick: () => navigate('/products/prices') },
      ]}
    />
  );
}
```

### Status Change Menu

```tsx
function StatusMenu({ currentStatus, onStatusChange }) {
  const statuses = [
    { value: 'draft', label: 'Draft', color: 'neutral' },
    { value: 'pending', label: 'Pending Review', color: 'warning' },
    { value: 'approved', label: 'Approved', color: 'success' },
    { value: 'rejected', label: 'Rejected', color: 'danger' },
  ];

  const current = statuses.find((s) => s.value === currentStatus);

  return (
    <MenuButton
      buttonText={current?.label || 'Set Status'}
      variant="soft"
      color={current?.color || 'neutral'}
      items={statuses.map((status) => ({
        text: status.label,
        onClick: () => onStatusChange(status.value),
      }))}
    />
  );
}
```

### Menu with Decorators

```tsx
function NotificationsMenu({ notifications }) {
  const unreadCount = notifications.filter((n) => !n.read).length;

  return (
    <MenuButton
      buttonText="Notifications"
      startDecorator={<NotificationsIcon />}
      endDecorator={
        unreadCount > 0 && (
          <Chip size="sm" color="danger">
            {unreadCount}
          </Chip>
        )
      }
      variant="soft"
      items={notifications.slice(0, 5).map((n) => ({
        text: n.title,
        onClick: () => markAsRead(n.id),
      }))}
    />
  );
}
```

### Quick Settings Menu

```tsx
function QuickSettings({ settings, onSettingChange }) {
  return (
    <MenuButton
      buttonText="Settings"
      startDecorator={<SettingsIcon />}
      variant="plain"
      color="neutral"
      items={[
        {
          text: settings.darkMode ? '☀️ Light Mode' : '🌙 Dark Mode',
          onClick: () => onSettingChange('darkMode', !settings.darkMode),
        },
        {
          text: settings.compactView ? '📐 Normal View' : '📏 Compact View',
          onClick: () => onSettingChange('compactView', !settings.compactView),
        },
        {
          text: 'Language: ' + settings.language.toUpperCase(),
          onClick: () => openLanguageModal(),
        },
      ]}
    />
  );
}
```

## Props and Customization

### Key Props

| Prop                   | Type                                                           | Default          | Description                       |
| ---------------------- | -------------------------------------------------------------- | ---------------- | --------------------------------- |
| `buttonText`           | `string`                                                       | -                | Text displayed on the button      |
| `items`                | `Array<{ text: string; onClick?: () => void }>`                | -                | Menu items to display             |
| `showIcon`             | `boolean`                                                      | `true`           | Show dropdown indicator icon      |
| `variant`              | `'solid' \| 'soft' \| 'outlined' \| 'plain'`                   | `'solid'`        | Button style variant              |
| `color`                | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'primary'`      | Button color                      |
| `size`                 | `'sm' \| 'md' \| 'lg'`                                         | `'md'`           | Button size                       |
| `placement`            | `'bottom-start' \| 'bottom' \| 'bottom-end'`                   | `'bottom-start'` | Menu position                     |
| `startDecorator`       | `ReactNode`                                                    | -                | Content before button text        |
| `endDecorator`         | `ReactNode`                                                    | -                | Content after button text         |
| `buttonComponent`      | `React.ElementType`                                            | `Button`         | Custom button component           |
| `buttonComponentProps` | `object`                                                       | -                | Props for custom button component |

### Menu Item Structure

```tsx
interface MenuItem {
  text: string;          // Display text
  onClick?: () => void;  // Click handler
  // Additional props can be passed based on implementation
}

// Example items array
const items = [
  { text: 'Edit', onClick: handleEdit },
  { text: 'Delete', onClick: handleDelete },
];
```

### Variant and Color Options

```tsx
// Primary solid (default)
<MenuButton buttonText="Actions" variant="solid" color="primary" />

// Subtle outlined
<MenuButton buttonText="Actions" variant="outlined" color="neutral" />

// Soft background
<MenuButton buttonText="Actions" variant="soft" color="primary" />

// Plain/text only
<MenuButton buttonText="Actions" variant="plain" color="neutral" />

// Danger color
<MenuButton buttonText="Delete" variant="soft" color="danger" />
```

### Size Options

```tsx
// Small
<MenuButton buttonText="Small" size="sm" items={items} />

// Medium (default)
<MenuButton buttonText="Medium" size="md" items={items} />

// Large
<MenuButton buttonText="Large" size="lg" items={items} />
```

### Custom Button Component

```tsx
// Use with custom components like Link
import { Link } from 'react-router-dom';

<MenuButton
  buttonText="Dashboard"
  buttonComponent={Link}
  buttonComponentProps={{ to: '/dashboard' }}
  showIcon={false}
  items={items}
/>
```

### Decorators

```tsx
// Start decorator (icon before text)
<MenuButton
  buttonText="Export"
  startDecorator={<DownloadIcon />}
  items={exportItems}
/>

// End decorator (badge after text)
<MenuButton
  buttonText="Inbox"
  endDecorator={<Chip size="sm">5</Chip>}
  items={inboxItems}
/>

// Both decorators
<MenuButton
  buttonText="User"
  startDecorator={<Avatar size="sm" />}
  endDecorator={<ChevronDownIcon />}
  showIcon={false}
  items={userItems}
/>
```

## Accessibility

MenuButton includes built-in accessibility features:

### ARIA Attributes

- Button has `aria-haspopup="true"` indicating it opens a menu
- Button has `aria-expanded` reflecting menu open state
- Menu has proper `role="menu"` and `role="menuitem"` structure
- Focus is managed between button and menu items

### Keyboard Navigation

- **Enter/Space**: Open menu when button is focused
- **Arrow Down**: Move to next menu item
- **Arrow Up**: Move to previous menu item
- **Escape**: Close menu and return focus to button
- **Tab**: Close menu and move to next focusable element
- **Enter**: Select focused menu item

### Screen Reader Support

```tsx
// Button announces: "Account, menu button, expanded/collapsed"
<MenuButton
  buttonText="Account"
  items={[...]}
/>

// Menu items are announced as: "Profile, menu item, 1 of 3"
```

### Focus Management

- Focus moves to first menu item when menu opens
- Focus returns to button when menu closes
- Menu items are focusable with arrow keys

## Best Practices

### ✅ Do

1. **Use clear, action-oriented labels**: Button text should indicate what the menu contains

```tsx
// ✅ Good: Clear menu purpose
<MenuButton buttonText="Export Options" items={exportItems} />
<MenuButton buttonText="User Actions" items={userItems} />
```

2. **Group related actions**: Keep menu items logically related

```tsx
// ✅ Good: Related file actions
<MenuButton
  buttonText="File"
  items={[
    { text: 'New', onClick: handleNew },
    { text: 'Open', onClick: handleOpen },
    { text: 'Save', onClick: handleSave },
    { text: 'Save As...', onClick: handleSaveAs },
  ]}
/>
```

3. **Use appropriate variants**: Match the button style to its context

```tsx
// ✅ Good: Primary for main actions, plain for navigation
<MenuButton buttonText="Create" variant="solid" color="primary" />
<MenuButton buttonText="Settings" variant="plain" color="neutral" />
```

4. **Limit menu items**: Keep menus scannable (5-7 items max)

### ❌ Don't

1. **Don't use for form inputs**: Use Select for value selection

```tsx
// ❌ Bad: Using MenuButton for form selection
<MenuButton buttonText={selectedOption} items={options} />

// ✅ Good: Use Select for forms
<Select value={selectedOption} onChange={handleChange}>
  {options.map((opt) => <Option key={opt.value} value={opt.value}>{opt.label}</Option>)}
</Select>
```

2. **Don't use vague labels**: Be specific about menu contents

```tsx
// ❌ Bad: Unclear what "More" contains
<MenuButton buttonText="More" items={items} />

// ✅ Good: Clear menu purpose
<MenuButton buttonText="More Actions" items={actionItems} />
```

3. **Don't overload menus**: Split large menus into categories

```tsx
// ❌ Bad: Too many unrelated items
<MenuButton
  buttonText="Options"
  items={[
    { text: 'Edit' },
    { text: 'Delete' },
    { text: 'Share' },
    { text: 'Settings' },
    { text: 'Help' },
    { text: 'About' },
    // ... many more items
  ]}
/>
```

4. **Don't hide primary actions**: Important actions should be visible

## Performance Considerations

### Memoize Items Array

Prevent unnecessary re-renders by memoizing the items array:

```tsx
const menuItems = useMemo(
  () => [
    { text: 'Edit', onClick: handleEdit },
    { text: 'Delete', onClick: handleDelete },
  ],
  [handleEdit, handleDelete]
);

<MenuButton buttonText="Actions" items={menuItems} />
```

### Memoize Click Handlers

```tsx
const handleExport = useCallback(
  (format: string) => {
    exportData(data, format);
  },
  [data]
);

const exportItems = useMemo(
  () => [
    { text: 'CSV', onClick: () => handleExport('csv') },
    { text: 'Excel', onClick: () => handleExport('xlsx') },
  ],
  [handleExport]
);
```

### Lazy Load Heavy Actions

For actions that trigger heavy operations:

```tsx
const items = useMemo(
  () => [
    {
      text: 'Generate Report',
      onClick: async () => {
        setLoading(true);
        await generateReport();
        setLoading(false);
      },
    },
  ],
  []
);
```

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.
