# IconMenuButton

## Introduction

IconMenuButton is a compact menu trigger component that displays an icon-only button to reveal a dropdown menu of actions. Unlike MenuButton which shows text, IconMenuButton uses minimal screen space by displaying only an icon (commonly a "more" or kebab icon). It's ideal for table row actions, card menus, and any context where space is limited but multiple actions need to be accessible.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
/>
```

| Field                | Description | Default                                                               |
| -------------------- | ----------- | --------------------------------------------------------------------- |
| icon                 | —           | \<MoreVert />                                                         |
| buttonComponent      | —           | —                                                                     |
| buttonComponentProps | —           | —                                                                     |
| items                | —           | \[\{ text: 'Profile' }, \{ text: 'My account' }, \{ text: 'Logout' }] |
| placement            | —           | —                                                                     |
| variant              | —           | "solid"                                                               |
| color                | —           | "primary"                                                             |
| size                 | —           | —                                                                     |

> ⚠️ **Usage Warning** ⚠️
>
> Accessibility consideration for icon-only buttons:
>
> - **Always provide `aria-label`**: Screen readers need text context
> - **Use familiar icons**: MoreVert (⋮), MoreHoriz (⋯) are commonly recognized
> - **Consider MenuButton**: When space allows, text labels improve clarity
> - **Tooltip support**: Consider adding tooltips for sighted users

## Usage

```tsx
import { IconMenuButton } from '@ceed/cds';
import MoreVertIcon from '@mui/icons-material/MoreVert';

function RowActions({ onEdit, onDelete }) {
  return (
    <IconMenuButton
      icon={<MoreVertIcon />}
      buttonComponentProps={{ 'aria-label': 'Row actions' }}
      items={[
        { text: 'Edit', onClick: onEdit },
        { text: 'Delete', onClick: onDelete },
      ]}
    />
  );
}
```

## Examples

### Playground

Interactive example with all controls.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
/>
```

### Standalone

Icon button without dropdown functionality.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
  buttonComponent={Link}
  buttonComponentProps={{
  to: '/'
}}
/>
```

### Placement: Bottom Start

Menu aligns to the left edge of the button.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
  placement="bottom-start"
  buttonComponentProps={{
  'aria-label': 'Hi'
}}
/>
```

### Placement: Bottom

Menu centers below the button.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
  placement="bottom"
  buttonComponentProps={{
  'aria-label': 'Hi'
}}
/>
```

### Placement: Bottom End

Menu aligns to the right edge of the button.

```tsx
<IconMenuButton
  icon={<MoreVert />}
  items={[{
  text: 'Profile'
}, {
  text: 'My account'
}, {
  text: 'Logout'
}]}
  variant="solid"
  color="primary"
  placement="bottom-end"
  buttonComponentProps={{
  'aria-label': 'Hi'
}}
/>
```

## When to Use

### ✅ Good Use Cases

- **Table row actions**: Edit, delete, view details actions for each row
- **Card menus**: Action menus on cards or list items
- **Toolbar overflow**: Secondary actions in space-constrained toolbars
- **Mobile interfaces**: Compact action triggers on mobile screens
- **Grid item actions**: Actions for items in a grid layout
- **List item actions**: Contextual actions for list entries

### ❌ When Not to Use

- **Primary actions**: Important actions should be visible buttons
- **When space allows text**: Use MenuButton for clearer labeling
- **Form submissions**: Use standard buttons for form actions
- **Navigation**: Use proper navigation components for routing
- **Single action**: Use IconButton for single-action scenarios

## Common Use Cases

### Table Row Actions

```tsx
function DataTableRow({ row, onEdit, onDelete, onDuplicate }) {
  return (
    <tr>
      <td>{row.name}</td>
      <td>{row.status}</td>
      <td>{row.date}</td>
      <td>
        <IconMenuButton
          icon={<MoreVertIcon />}
          variant="plain"
          color="neutral"
          buttonComponentProps={{ 'aria-label': `Actions for ${row.name}` }}
          items={[
            { text: 'Edit', onClick: () => onEdit(row.id) },
            { text: 'Duplicate', onClick: () => onDuplicate(row.id) },
            { text: 'Delete', onClick: () => onDelete(row.id) },
          ]}
        />
      </td>
    </tr>
  );
}
```

### Card Actions Menu

```tsx
function ProductCard({ product, onAction }) {
  return (
    <Card>
      <CardOverflow>
        <AspectRatio>
          <img src={product.image} alt={product.name} />
        </AspectRatio>
      </CardOverflow>
      <CardContent>
        <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
          <Typography level="title-md">{product.name}</Typography>
          <IconMenuButton
            icon={<MoreVertIcon />}
            variant="plain"
            size="sm"
            buttonComponentProps={{ 'aria-label': `Actions for ${product.name}` }}
            items={[
              { text: 'View Details', onClick: () => onAction('view', product.id) },
              { text: 'Edit', onClick: () => onAction('edit', product.id) },
              { text: 'Add to Cart', onClick: () => onAction('cart', product.id) },
              { text: 'Share', onClick: () => onAction('share', product.id) },
            ]}
          />
        </Box>
        <Typography level="body-sm">${product.price}</Typography>
      </CardContent>
    </Card>
  );
}
```

### Comment Actions

```tsx
function Comment({ comment, currentUserId, onAction }) {
  const isOwner = comment.authorId === currentUserId;

  return (
    <Box sx={{ display: 'flex', gap: 2 }}>
      <Avatar src={comment.authorAvatar} />
      <Box sx={{ flex: 1 }}>
        <Stack direction="row" justifyContent="space-between" alignItems="flex-start">
          <Box>
            <Typography level="title-sm">{comment.authorName}</Typography>
            <Typography level="body-xs" color="neutral">
              {comment.timestamp}
            </Typography>
          </Box>
          <IconMenuButton
            icon={<MoreHorizIcon />}
            variant="plain"
            color="neutral"
            size="sm"
            buttonComponentProps={{ 'aria-label': 'Comment actions' }}
            items={[
              { text: 'Reply', onClick: () => onAction('reply', comment.id) },
              ...(isOwner
                ? [
                    { text: 'Edit', onClick: () => onAction('edit', comment.id) },
                    { text: 'Delete', onClick: () => onAction('delete', comment.id) },
                  ]
                : [{ text: 'Report', onClick: () => onAction('report', comment.id) }]),
            ]}
          />
        </Stack>
        <Typography level="body-sm" sx={{ mt: 1 }}>
          {comment.content}
        </Typography>
      </Box>
    </Box>
  );
}
```

### Notification Item Actions

```tsx
function NotificationItem({ notification, onAction }) {
  return (
    <ListItem
      sx={{
        bgcolor: notification.read ? 'transparent' : 'primary.softBg',
      }}
    >
      <ListItemDecorator>
        <Avatar src={notification.avatar} />
      </ListItemDecorator>
      <ListItemContent>
        <Typography level="title-sm">{notification.title}</Typography>
        <Typography level="body-xs">{notification.time}</Typography>
      </ListItemContent>
      <IconMenuButton
        icon={<MoreVertIcon />}
        variant="plain"
        color="neutral"
        size="sm"
        buttonComponentProps={{ 'aria-label': 'Notification actions' }}
        items={[
          {
            text: notification.read ? 'Mark as Unread' : 'Mark as Read',
            onClick: () => onAction('toggleRead', notification.id),
          },
          { text: 'Dismiss', onClick: () => onAction('dismiss', notification.id) },
          { text: 'Turn Off', onClick: () => onAction('turnOff', notification.type) },
        ]}
      />
    </ListItem>
  );
}
```

### File Item Actions

```tsx
function FileItem({ file, onAction }) {
  const getFileIcon = () => {
    switch (file.type) {
      case 'folder':
        return <FolderIcon />;
      case 'image':
        return <ImageIcon />;
      case 'document':
        return <DescriptionIcon />;
      default:
        return <InsertDriveFileIcon />;
    }
  };

  return (
    <ListItem>
      <ListItemDecorator>{getFileIcon()}</ListItemDecorator>
      <ListItemContent>
        <Typography level="title-sm">{file.name}</Typography>
        <Typography level="body-xs" color="neutral">
          {file.size} • Modified {file.modified}
        </Typography>
      </ListItemContent>
      <IconMenuButton
        icon={<MoreVertIcon />}
        variant="plain"
        color="neutral"
        size="sm"
        placement="bottom-end"
        buttonComponentProps={{ 'aria-label': `Actions for ${file.name}` }}
        items={[
          { text: 'Download', onClick: () => onAction('download', file.id) },
          { text: 'Share', onClick: () => onAction('share', file.id) },
          { text: 'Rename', onClick: () => onAction('rename', file.id) },
          { text: 'Move', onClick: () => onAction('move', file.id) },
          { text: 'Delete', onClick: () => onAction('delete', file.id) },
        ]}
      />
    </ListItem>
  );
}
```

### Toolbar Overflow Menu

```tsx
function EditorToolbar({ onAction }) {
  return (
    <Stack direction="row" gap={1} alignItems="center">
      <IconButton onClick={() => onAction('bold')}>
        <FormatBoldIcon />
      </IconButton>
      <IconButton onClick={() => onAction('italic')}>
        <FormatItalicIcon />
      </IconButton>
      <IconButton onClick={() => onAction('underline')}>
        <FormatUnderlinedIcon />
      </IconButton>
      <Divider orientation="vertical" />
      <IconMenuButton
        icon={<MoreHorizIcon />}
        variant="plain"
        color="neutral"
        buttonComponentProps={{ 'aria-label': 'More formatting options' }}
        items={[
          { text: 'Strikethrough', onClick: () => onAction('strikethrough') },
          { text: 'Superscript', onClick: () => onAction('superscript') },
          { text: 'Subscript', onClick: () => onAction('subscript') },
          { text: 'Code', onClick: () => onAction('code') },
          { text: 'Clear Formatting', onClick: () => onAction('clear') },
        ]}
      />
    </Stack>
  );
}
```

### User List Item

```tsx
function UserListItem({ user, onAction }) {
  return (
    <ListItem>
      <ListItemDecorator>
        <Avatar src={user.avatar}>{user.initials}</Avatar>
      </ListItemDecorator>
      <ListItemContent>
        <Typography level="title-sm">{user.name}</Typography>
        <Typography level="body-xs">{user.email}</Typography>
      </ListItemContent>
      <Chip size="sm" color={user.active ? 'success' : 'neutral'}>
        {user.active ? 'Active' : 'Inactive'}
      </Chip>
      <IconMenuButton
        icon={<MoreVertIcon />}
        variant="plain"
        color="neutral"
        size="sm"
        buttonComponentProps={{ 'aria-label': `Actions for ${user.name}` }}
        items={[
          { text: 'View Profile', onClick: () => onAction('view', user.id) },
          { text: 'Send Message', onClick: () => onAction('message', user.id) },
          {
            text: user.active ? 'Deactivate' : 'Activate',
            onClick: () => onAction('toggleActive', user.id),
          },
          { text: 'Remove', onClick: () => onAction('remove', user.id) },
        ]}
      />
    </ListItem>
  );
}
```

## Props and Customization

### Key Props

| Prop                   | Type                                                           | Default          | Description                                        |
| ---------------------- | -------------------------------------------------------------- | ---------------- | -------------------------------------------------- |
| `icon`                 | `ReactNode`                                                    | -                | Icon element to display (e.g., `<MoreVertIcon />`) |
| `items`                | `Array<{ text: string; onClick?: () => void }>`                | -                | Menu items to display                              |
| `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                                      |
| `buttonComponent`      | `React.ElementType`                                            | `IconButton`     | Custom button component                            |
| `buttonComponentProps` | `object`                                                       | -                | Props for button (include `aria-label`)            |

### Common Icon Options

```tsx
import MoreVertIcon from '@mui/icons-material/MoreVert';     // Vertical dots (⋮)
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';   // Horizontal dots (⋯)
import SettingsIcon from '@mui/icons-material/Settings';     // Gear icon
import MenuIcon from '@mui/icons-material/Menu';             // Hamburger menu

// Vertical dots (most common)
<IconMenuButton icon={<MoreVertIcon />} items={items} />

// Horizontal dots
<IconMenuButton icon={<MoreHorizIcon />} items={items} />

// Custom icon
<IconMenuButton icon={<SettingsIcon />} items={settingsItems} />
```

### Variant and Color Options

```tsx
// Solid (default) - high emphasis
<IconMenuButton icon={<MoreVertIcon />} variant="solid" color="primary" />

// Plain - minimal emphasis (recommended for tables/lists)
<IconMenuButton icon={<MoreVertIcon />} variant="plain" color="neutral" />

// Soft - subtle background
<IconMenuButton icon={<MoreVertIcon />} variant="soft" color="primary" />

// Outlined - bordered
<IconMenuButton icon={<MoreVertIcon />} variant="outlined" color="neutral" />
```

### Size Options

```tsx
// Small - for compact lists
<IconMenuButton icon={<MoreVertIcon />} size="sm" items={items} />

// Medium (default)
<IconMenuButton icon={<MoreVertIcon />} size="md" items={items} />

// Large - for prominent actions
<IconMenuButton icon={<MoreVertIcon />} size="lg" items={items} />
```

### Accessibility Props

```tsx
// Always provide aria-label for accessibility
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{
    'aria-label': 'More actions for item',
  }}
  items={items}
/>

// Include context in aria-label
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{
    'aria-label': `Actions for ${item.name}`,
  }}
  items={items}
/>
```

## Accessibility

IconMenuButton requires additional accessibility consideration since it lacks visible text:

### ARIA Requirements

```tsx
// ✅ Required: Always provide aria-label
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{ 'aria-label': 'More actions' }}
  items={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 element

### Screen Reader Announcements

```tsx
// Button announces: "More actions, menu button"
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{ 'aria-label': 'More actions' }}
/>

// Contextual label: "Actions for Product XYZ, menu button"
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{ 'aria-label': `Actions for ${product.name}` }}
/>
```

### Focus Visibility

- Clear focus ring on button when focused
- Menu items have visible focus indicators
- Focus is properly managed between button and menu

## Best Practices

### ✅ Do

1. **Always include `aria-label`**: Essential for screen readers

```tsx
// ✅ Good: Descriptive aria-label
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{ 'aria-label': 'File actions' }}
  items={items}
/>
```

2. **Use familiar icons**: Stick to recognizable patterns

```tsx
// ✅ Good: Widely recognized menu icons
<IconMenuButton icon={<MoreVertIcon />} />  // Vertical dots
<IconMenuButton icon={<MoreHorizIcon />} /> // Horizontal dots
```

3. **Use plain variant in lists/tables**: Reduces visual clutter

```tsx
// ✅ Good: Subtle appearance in data rows
<IconMenuButton
  icon={<MoreVertIcon />}
  variant="plain"
  color="neutral"
  size="sm"
/>
```

4. **Position menus appropriately**: Prevent overflow issues

```tsx
// ✅ Good: Right-aligned menu for right-side buttons
<IconMenuButton
  icon={<MoreVertIcon />}
  placement="bottom-end"
/>
```

### ❌ Don't

1. **Don't skip aria-label**: Makes button inaccessible

```tsx
// ❌ Bad: No accessible name
<IconMenuButton icon={<MoreVertIcon />} items={items} />

// ✅ Good: Has aria-label
<IconMenuButton
  icon={<MoreVertIcon />}
  buttonComponentProps={{ 'aria-label': 'Actions' }}
  items={items}
/>
```

2. **Don't use unfamiliar icons**: Users may not recognize them

```tsx
// ❌ Bad: Unclear what icon means
<IconMenuButton icon={<StarIcon />} items={items} />

// ✅ Good: Standard menu icon
<IconMenuButton icon={<MoreVertIcon />} items={items} />
```

3. **Don't use for primary actions**: Important actions should be visible

```tsx
// ❌ Bad: Hiding primary action in icon menu
<IconMenuButton
  icon={<MoreVertIcon />}
  items={[{ text: 'Submit Form', onClick: handleSubmit }]}
/>

// ✅ Good: Primary action is visible
<Button onClick={handleSubmit}>Submit</Button>
```

4. **Don't use too-small sizes**: Must be easily clickable

## Performance Considerations

### Memoize Items Array

```tsx
const rowActions = useMemo(
  () => [
    { text: 'Edit', onClick: () => onEdit(row.id) },
    { text: 'Delete', onClick: () => onDelete(row.id) },
  ],
  [row.id, onEdit, onDelete]
);

<IconMenuButton icon={<MoreVertIcon />} items={rowActions} />
```

### Memoize in List Renders

When rendering many IconMenuButtons in lists/tables:

```tsx
const ActionMenu = memo(({ item, onAction }) => {
  const items = useMemo(
    () => [
      { text: 'Edit', onClick: () => onAction('edit', item.id) },
      { text: 'Delete', onClick: () => onAction('delete', item.id) },
    ],
    [item.id, onAction]
  );

  return (
    <IconMenuButton
      icon={<MoreVertIcon />}
      variant="plain"
      color="neutral"
      size="sm"
      buttonComponentProps={{ 'aria-label': `Actions for ${item.name}` }}
      items={items}
    />
  );
});
```

### Avoid Inline Object Creation

```tsx
// ❌ Bad: Creates new object every render
<IconMenuButton
  buttonComponentProps={{ 'aria-label': 'Actions' }}
/>

// ✅ Good: Stable props object
const buttonProps = useMemo(
  () => ({ 'aria-label': `Actions for ${item.name}` }),
  [item.name]
);
<IconMenuButton buttonComponentProps={buttonProps} />
```

IconMenuButton provides a compact way to offer contextual actions in space-constrained interfaces. Always include proper accessibility attributes and use familiar icons to ensure all users can understand and interact with the menu trigger effectively.
