# `PhiaUi.Components.ContextMenu`
[🔗](https://github.com/charlenopires/PhiaUI/blob/v0.1.17/lib/phia_ui/components/overlay/context_menu.ex#L1)

Context menu component triggered by right-click (`contextmenu` browser event).

A context menu provides a contextual set of actions for a specific element —
just like the browser's native right-click menu, but fully customisable and
integrated with your LiveView. Common use cases include file managers,
spreadsheets, canvas editors, data grids, and any UI where per-item actions
should be discoverable on right-click.

Requires the `PhiaContextMenu` JavaScript hook registered in `app.js`. The
hook intercepts the `contextmenu` event on the trigger area, prevents the
native browser menu, and positions the custom panel at the exact cursor
coordinates.

## Hook Registration

Copy the hook via `mix phia.add context_menu`, then register it:

    # assets/js/app.js
    import PhiaContextMenu from "./hooks/context_menu"

    let liveSocket = new LiveSocket("/live", Socket, {
      hooks: { PhiaContextMenu }
    })

## Hook Behaviour

- Right-clicking inside `context_menu_trigger/1` opens the menu at the cursor
- The native browser context menu is suppressed
- Smart viewport-aware positioning: the panel flips if it would overflow the
  viewport edge (right→left, bottom→top)
- Clicking outside the open panel or pressing `Escape` closes it
- `ArrowUp` / `ArrowDown` navigate between items; `Enter` activates the
  focused item
- Only one context menu can be open at a time — opening a new one closes
  the previous

## Sub-components

| Function                       | Element | Purpose                                       |
|--------------------------------|---------|-----------------------------------------------|
| `context_menu/1`               | `div`   | Root container                                |
| `context_menu_trigger/1`       | `div`   | Right-clickable area with hook anchor         |
| `context_menu_content/1`       | `div`   | Floating panel (positioned fixed by hook)     |
| `context_menu_item/1`          | `div`   | Clickable item (`role="menuitem"`)            |
| `context_menu_separator/1`     | `hr`    | Visual divider between item groups            |
| `context_menu_checkbox_item/1` | `div`   | Toggleable item (`role="menuitemcheckbox"`)   |
| `context_menu_label/1`         | `div`   | Non-interactive section heading               |

## Example — File Manager

A classic context menu for file operations:

    <.context_menu id="file-ctx">
      <.context_menu_trigger context_menu_id="file-ctx">
        <div class="flex items-center gap-2 p-3 rounded-md hover:bg-muted cursor-default">
          <.icon name="file" />
          {file.name}
        </div>
      </.context_menu_trigger>
      <.context_menu_content id="file-ctx-content">
        <.context_menu_label>{@file.name}</.context_menu_label>
        <.context_menu_separator />
        <.context_menu_item phx-click="open_file" phx-value-id={@file.id}>
          Open
        </.context_menu_item>
        <.context_menu_item phx-click="rename_file" phx-value-id={@file.id}>
          Rename...
        </.context_menu_item>
        <.context_menu_item phx-click="duplicate_file" phx-value-id={@file.id}>
          Duplicate
        </.context_menu_item>
        <.context_menu_separator />
        <.context_menu_checkbox_item
          checked={@file.starred}
          phx-click="toggle_star"
          phx-value-id={@file.id}>
          Starred
        </.context_menu_checkbox_item>
        <.context_menu_separator />
        <.context_menu_item phx-click="delete_file" phx-value-id={@file.id}
          class="text-destructive focus:text-destructive">
          Move to Trash
        </.context_menu_item>
      </.context_menu_content>
    </.context_menu>

## Example — Data Grid Row

Context menus work well on table rows in data grids:

    <tr :for={row <- @rows}>
      <.context_menu id={"row-ctx-#{row.id}"}>
        <.context_menu_trigger context_menu_id={"row-ctx-#{row.id}"}>
          <td class="px-4 py-2">{row.name}</td>
          <td class="px-4 py-2">{row.status}</td>
        </.context_menu_trigger>
        <.context_menu_content id={"row-ctx-#{row.id}-content"}>
          <.context_menu_item phx-click="view_row" phx-value-id={row.id}>View</.context_menu_item>
          <.context_menu_item phx-click="edit_row" phx-value-id={row.id}>Edit</.context_menu_item>
          <.context_menu_separator />
          <.context_menu_item phx-click="archive_row" phx-value-id={row.id}>Archive</.context_menu_item>
        </.context_menu_content>
      </.context_menu>
    </tr>

## Example — Canvas / Drawing Area

For design tools where the trigger is the entire canvas:

    <.context_menu id="canvas-ctx">
      <.context_menu_trigger context_menu_id="canvas-ctx">
        <div id="drawing-canvas" class="w-full h-96 bg-muted rounded border">
          <%# canvas content %>
        </div>
      </.context_menu_trigger>
      <.context_menu_content id="canvas-ctx-content">
        <.context_menu_item phx-click="paste">Paste</.context_menu_item>
        <.context_menu_item phx-click="select_all">Select All</.context_menu_item>
        <.context_menu_separator />
        <.context_menu_label>View</.context_menu_label>
        <.context_menu_checkbox_item checked={@show_grid} phx-click="toggle_grid">
          Show Grid
        </.context_menu_checkbox_item>
        <.context_menu_checkbox_item checked={@snap_to_grid} phx-click="toggle_snap">
          Snap to Grid
        </.context_menu_checkbox_item>
      </.context_menu_content>
    </.context_menu>

## Accessibility

- `context_menu_trigger/1` has `aria-haspopup="menu"` to signal the
  right-click behaviour to assistive technology
- `context_menu_content/1` has `role="menu"` and `aria-orientation="vertical"`
- Items have `role="menuitem"` (or `menuitemcheckbox` for toggleable items)
- All items have `tabindex="-1"` so keyboard navigation is hook-managed via
  `[data-disabled]` and `focus()` calls, matching the WAI-ARIA menu pattern
- The panel is `position: fixed` (not absolute) so it appears at the cursor
  coordinates regardless of the containing element's scroll position or
  `overflow` setting

# `context_menu`

Renders the context menu root container.

The root is `relative` so child elements can be positioned within it.
The trigger and content are referenced by ID — the root itself is a
layout-only wrapper.

## Attributes

* `id` (`:string`) (required) - Unique context menu ID. Used by `context_menu_trigger/1` to build the
  `data-content-id` attribute that tells the hook which panel to position.
  Must be unique on the page. When rendering in lists, use per-item IDs:
  `"file-ctx-#{file.id}"`.

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the root `<div>`.
## Slots

* `inner_block` (required) - Must contain one `context_menu_trigger/1` and one `context_menu_content/1`.

# `context_menu_checkbox_item`

Renders a toggleable context menu item with a check mark indicator.

Uses `role="menuitemcheckbox"` and `aria-checked` so assistive technology
announces the current state. Toggle the state in your LiveView:

    # LiveView
    def handle_event("toggle_grid", _params, socket) do
      {:noreply, update(socket, :show_grid, &(!&1))}
    end

    # Template
    <.context_menu_checkbox_item
      checked={@show_grid}
      phx-click="toggle_grid">
      Show Grid
    </.context_menu_checkbox_item>

The check mark (`&#10003;`) is rendered only when `:checked` is `true`,
keeping the item label left-aligned whether checked or not.

## Attributes

* `checked` (`:boolean`) - Whether the checkbox item is currently checked. Drives `aria-checked` and
  the visible check mark indicator. Manage state in your LiveView and pass
  the current value here.

  Defaults to `false`.
* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the item `<div>` (e.g. `phx-click`, `phx-value-*`).
## Slots

* `inner_block` (required) - Item label text.

# `context_menu_content`

Renders the context menu content panel.

Initially hidden via `style="display:none; position:fixed;"`. Using
`position: fixed` (not absolute) is critical — it ensures the panel
appears at the cursor's viewport coordinates regardless of the page
scroll position or the trigger's containing block.

The hook reveals the panel and sets `top`/`left` inline styles to the
right-click cursor position, then adjusts if the panel would overflow
the viewport edge.

`tabindex="-1"` allows the panel to receive programmatic focus from the
hook after opening, enabling keyboard navigation to work immediately
without the user needing to Tab into the panel.

## Attributes

* `id` (`:string`) (required) - Element ID for the content panel. **Must follow the pattern
  `"{context_menu_id}-content"`** — this is the ID the hook looks up via
  `data-content-id` on the trigger to position the panel.

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the panel `<div>`.
## Slots

* `inner_block` (required) - Menu items, labels, separators, and checkbox items.

# `context_menu_item`

Renders a context menu item with `role="menuitem"`.

Wire LiveView actions using `phx-click` and pass data via `phx-value-*`:

    <.context_menu_item phx-click="rename" phx-value-id={@file.id}>
      Rename
    </.context_menu_item>

The hook handles `ArrowUp` / `ArrowDown` focus management between items
(via programmatic `focus()`) and `Enter` to activate the focused item.

Disabled items should use `data-disabled` attribute — the hook checks for
this and skips them during keyboard navigation. The `data-[disabled]` CSS
selector applies the visual disabled style.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the item `<div>` (e.g. `phx-click`, `phx-value-*`).
## Slots

* `inner_block` (required) - Menu item content.

# `context_menu_label`

Renders a non-interactive section heading inside the context menu.

Use labels to give context to a group of related items — for example,
showing the name of the right-clicked item at the top of the menu:

    <.context_menu_content id="file-ctx-content">
      <.context_menu_label>{@file.name}</.context_menu_label>
      <.context_menu_separator />
      <.context_menu_item phx-click="open">Open</.context_menu_item>
    </.context_menu_content>

Labels are not focusable and are skipped by the hook's keyboard navigation.

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the label `<div>`.
## Slots

* `inner_block` (required) - Label text content.

# `context_menu_separator`

Renders a horizontal visual separator between groups of context menu items.

Use separators to create clear visual groupings between related and unrelated
actions. For example, separate "Open", "Rename", "Duplicate" from destructive
actions like "Delete" or "Archive".

    <.context_menu_item phx-click="copy">Copy</.context_menu_item>
    <.context_menu_item phx-click="paste">Paste</.context_menu_item>
    <.context_menu_separator />
    <.context_menu_item phx-click="delete" class="text-destructive">
      Delete
    </.context_menu_item>

## Attributes

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the `<hr>`.

# `context_menu_trigger`

Renders the context menu trigger area.

The `PhiaContextMenu` hook mounts on this element (via `phx-hook`) and
intercepts `contextmenu` events (right-click / two-finger tap on macOS).
It prevents the native browser context menu and shows the custom panel at
the exact cursor `(clientX, clientY)` coordinates.

`data-content-id` tells the hook which panel element to position and show.
This must equal `"{context_menu_id}-content"`.

The trigger can wrap any content — a div, a table row, a canvas — as long
as it is a valid container for the right-click area you want to cover.

## Attributes

* `context_menu_id` (`:string`) (required) - ID of the parent `context_menu/1`. The hook uses this to build the content
  panel ID (`context_menu_id <> "-content"`) and show it at the cursor
  position on right-click.

* `class` (`:string`) - Additional CSS classes. Defaults to `nil`.
* Global attributes are accepted. Extra HTML attributes forwarded to the trigger `<div>`.
## Slots

* `inner_block` (required) - The right-clickable area — any content or element.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
