npm package: @pras-ui/popup-menu
Description: A headless, accessible, and highly customizable multi purpose component(popup menu, context menu) for React.
Author: PRAS
Latest version: 0.0.1

# @pras-ui/popup-menu

A headless, accessible, and highly customizable multi purpose component(popup menu, context menu) for React.

`@pras-ui/popup-menu` provides the building blocks to create fully-featured popup menu. It handles all the complex logic, like state management, keyboard navigation, accessibility, and positioning, leaving you to focus on styling.

## Features

- **Headless & Unstyled**: Gives you full control over the appearance.
- **Full Keyboard Navigation**: Arrow key navigation and shortcuts are supported out of the box.
- **Context Menu Mode**: Can be triggered by right-clicking.
- **Sub-menus**: Supports infinitely nested sub-menus.
- **Choice Items**: Create radio groups or checkbox groups inside your menu.
- **RTL Support**: Adapts automatically to right-to-left languages.
- **Accessible**: Follows WAI-ARIA patterns for menus.

## Installation

```bash
npm install @pras-ui/popup-menu
```

## Usage

Here's how to create a basic popup menu. You can use any styling solution you prefer; this example uses simple class names.

```tsx
import {
  PopupMenuRoot,
  PopupMenuTrigger,
  PopupMenuContent,
  PopupMenuItem,
  PopupMenuPortal,
} from "@pras-ui/popup-menu";

function MyPopupMenu() {
  return (
    <PopupMenuRoot>
      <PopupMenuTrigger asChild>
        <button className="my-trigger-button">Open Menu</button>
      </PopupMenuTrigger>

      <PopupMenuPortal>
        <PopupMenuContent className="my-menu-content" sideOffset={5}>
          <PopupMenuItem
            className="my-menu-item"
            onSelect={() => alert("New Tab")}
          >
            New Tab
          </PopupMenuItem>
          <PopupMenuItem
            className="my-menu-item"
            onSelect={() => alert("New Window")}
          >
            New Window
          </PopupMenuItem>
          <PopupMenuItem className="my-menu-item" disabled>
            New Incognito Window
          </PopupMenuItem>
        </PopupMenuContent>
      </PopupMenuPortal>
    </PopupMenuRoot>
  );
}
```

## API Reference

### `PopupMenuRoot`

| Prop           | Type                      | Default     | Description                                       |
| -------------- | ------------------------- | ----------- | ------------------------------------------------- |
| `open`         | `boolean`                 |             | The controlled open state of the menu.            |
| `defaultOpen`  | `boolean`                 | `false`     | The initial open state when uncontrolled.         |
| `onOpenChange` | `(open: boolean) => void` |             | Event handler called when the open state changes. |
| `mode`         | `'default' \| 'context'`  | `'default'` | `'context'` mode enables right-click triggering.  |
| `dir`          | `'ltr' \| 'rtl'`          | (inherited) | The reading direction of the menu.                |

### `PopupMenuTrigger`

The button or element that toggles the menu. It should be a direct child of `PopupMenuRoot`. It accepts all props of a standard `button` element.

| Attributes      | Type                | Description                   |
| --------------- | ------------------- | ----------------------------- |
| `data-hovered`  | `true \| undefined` | Whether the item is hovered.  |
| `data-focused`  | `true \| undefined` | Whether the item is focused.  |
| `data-disabled` | `true \| undefined` | Whether the item is disabled. |
| `data-state`    | `opened \| closed`  | The open state of the menu.   |

### `PopupMenuPortal`

Renders the menu content into a React Portal, ensuring it can overlay other elements. This is recommended to avoid z-index and clipping issues.

### `PopupMenuContent`

| Prop                      | Type                                     | Default     | Description                                                              |
| ------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------ |
| `dir`                     | `'ltr' \| 'rtl'`                         | (inherited) | The reading direction of the menu.                                       |
| `side`                    | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'`  | The preferred side of the trigger to render the content on.              |
| `sideOffset`              | `number`                                 | `0`         | The distance in pixels from the trigger.                                 |
| `align`                   | `'start' \| 'center' \| 'end'`           | `'start'`   | The preferred alignment of the content relative to the trigger.          |
| `alignOffset`             | `number`                                 | `0`         | The distance in pixels from the trigger.                                 |
| `collisionBoundary`       | `Boundary \| Boundary[]`                 |             | The boundary of the floating element.                                    |
| `collisionPadding`        | `number \| Record<Side, number>`         | `0`         | The padding of the floating element.                                     |
| `sticky`                  | `'partial' \| 'always'`                  | `'partial'` | Whether to sticky the floating element.                                  |
| `autoHideOnDetach`        | `boolean`                                | `false`     | Whether to auto hide the floating element on detach.                     |
| `removeScroll`            | `boolean`                                | `true`      | Whether to remove scroll on the floating element.                        |
| `position`                | `'absolute' \| 'fixed'`                  | `'fixed'`   | The position of the floating element.                                    |
| `updatePositionFrequency` | `'lazy' \| 'rapid'`                      | `'lazy'`    | The frequency of the floating element position update.                   |
| `alwaysRender`            | `boolean`                                | `false`     | Whether to always render the floating element.                           |
| `loop`                    | `boolean`                                | `true`      | Whether keyboard navigation should loop around.                          |
| `avoidCollisions`         | `boolean`                                | `true`      | Whether to prevent the content from overlapping with the viewport edges. |
| `closeOnScroll`           | `boolean`                                | `true`      | Whether the menu should close when scrolling outside of it.              |
| `navigationUp`            | `Keys[] \| Keys[][]`                     | `[]`        | Custom key(s) to trigger upward navigation through menu items.           |
| `navigationDown`          | `Keys[] \| Keys[][]`                     | `[]`        | Custom key(s) to trigger downward navigation through menu items.         |
| `onEsc`                   | `(e: Event) => void`                     |             | event handler for the `Escape` key press.                                |
| `onOutsideInteraction`    | `(e: Event) => void`                     |             | event handler for clicks or focus outside the menu.                      |

| Attributes   | Type               | Description                 |
| ------------ | ------------------ | --------------------------- |
| `data-state` | `opened \| closed` | The open state of the menu. |

### `PopupMenuItem`

| Prop           | Type                              | Default | Description                                                           |
| -------------- | --------------------------------- | ------- | --------------------------------------------------------------------- |
| `disabled`     | `boolean`                         | `false` | Prevents the item from being selected.                                |
| `onClick`      | `(e: MenuItemClickEvent) => void` |         | Event handler called when the item is selected via click or keyboard. |
| `closeOnClick` | `boolean`                         | `true`  | Whether the menu should close after the item is selected.             |

| Attributes      | Type                | Description                   |
| --------------- | ------------------- | ----------------------------- |
| `data-hovered`  | `true \| undefined` | Whether the item is hovered.  |
| `data-focused`  | `true \| undefined` | Whether the item is focused.  |
| `data-disabled` | `true \| undefined` | Whether the item is disabled. |

---

### `PopupSubMenuRoot`

| Prop           | Type                           | Default  | Description                                                   |
| -------------- | ------------------------------ | -------- | ------------------------------------------------------------- |
| `open`         | `boolean`                      |          | The controlled open state of the sub-menu.                    |
| `defaultOpen`  | `boolean`                      | `false`  | The initial open state when uncontrolled.                     |
| `onOpenChange` | `(open: boolean) => void`      |          | Event handler called when the open state changes.             |
| `openStrategy` | `'hover' \| 'click' \| 'both'` | `'both'` | How the sub-menu should be triggered.                         |
| `delay`        | `number`                       | `0`      | The delay in milliseconds before the sub-menu opens on hover. |

### `PopupSubMenuTrigger`

| Prop       | Type                              | Default | Description                                                           |
| ---------- | --------------------------------- | ------- | --------------------------------------------------------------------- |
| `disabled` | `boolean`                         | `false` | Prevents the item from being selected.                                |
| `onClick`  | `(e: MenuItemClickEvent) => void` |         | Event handler called when the item is selected via click or keyboard. |

| Attributes      | Type                | Description                   |
| --------------- | ------------------- | ----------------------------- |
| `data-hovered`  | `true \| undefined` | Whether the item is hovered.  |
| `data-focused`  | `true \| undefined` | Whether the item is focused.  |
| `data-disabled` | `true \| undefined` | Whether the item is disabled. |

### `PopupSubMenuContent`

The positioned container for the sub-menu's items. It has the same props as [`PopupMenuContent`](#popupmenucontent).

---

### `PopupMenuChoice`

Used to group a set of `PopupMenuChoiceItem` components.

| Prop           | Type                                  | Default    | Description                                          |
| -------------- | ------------------------------------- | ---------- | ---------------------------------------------------- |
| `mode`         | `'single' \| 'multiple'`              | `'single'` | Determines if one or multiple items can be selected. |
| `defaultValue` | `string \| string[]`                  |            | The initial value when uncontrolled.                 |
| `onChange`     | `(value: string \| string[]) => void` |            | Event handler called when the value changes.         |

### `PopupMenuChoiceItem`

A selectable item within a `PopupMenuChoice` group.

| Prop    | Type     | Description                                              |
| ------- | -------- | -------------------------------------------------------- |
| `value` | `string` | The value of the item, used to identify it in the group. |

| Attribute    | Type                     | Description            |
| ------------ | ------------------------ | ---------------------- |
| `data-state` | `selected \| unselected` | The state of the item. |

### `PopupMenuChoiceIndicator`

A component that displays a checkmark or other icon when its parent `PopupMenuChoiceItem` is selected. It will be hidden otherwise.

| Attribute    | Type                     | Description            |
| ------------ | ------------------------ | ---------------------- |
| `data-state` | `selected \| unselected` | The state of the item. |

---

### Other Components

- **`PopupMenuArrow`**: An optional arrow component that can be placed inside `PopupMenuContent` to point towards the trigger.
- **`PopupMenuGroup`**: A utility component to visually group items together.
- **`PopupMenuLabel`**: A non-interactive label for a `PopupMenuGroup`.