# Context Menu

Displays a menu located at the pointer, triggered by a right click.

- Install: `php artisan blatui:add context-menu`
- Source: https://blatui.remix-it.com/r/context-menu.json
- Composer: `composer require mallardduck/blade-lucide-icons gehrisandro/tailwind-merge-laravel`
- npm: `npm install -D @alpinejs/anchor`

## resources/views/components/ui/context-menu.blade.php

```blade
<div
    data-slot="context-menu"
    x-data="blatMenu()"
    {{ $attributes }}
>
    {{ $slot }}
</div>
```

## resources/views/components/ui/context-menu-checkbox-item.blade.php

```blade
@props([
    'checked' => false,
    'disabled' => false,
    'closeOnSelect' => false,
])

<x-ui.menu-checkbox-item
    :data-slot="'context-menu-checkbox-item'"
    :checked="$checked"
    :disabled="$disabled"
    :close-on-select="$closeOnSelect"
    {{ $attributes }}
>{{ $slot }}</x-ui.menu-checkbox-item>
```

## resources/views/components/ui/context-menu-content.blade.php

```blade
<template x-teleport="body" wire:ignore>
    <div
        x-blat-dialog-layer
        x-show="open"
        x-cloak
        x-ref="menu"
        x-init="_menu = $el"
        @click.outside="closeMenu(false)"
        {{-- The menu is pinned to fixed viewport coords captured at open, so close it on
             page scroll/resize instead of leaving it stranded (matches Radix). The menu's
             own overflow scroll does not bubble to window, so it stays open while scrolling
             inside it. --}}
        @scroll.window="closeMenu(false)"
        @resize.window="closeMenu(false)"
        @keydown.escape.prevent.stop="closeMenu()"
        @keydown.tab.prevent.stop="closeMenu()"
        @keydown="$blatNav($event); $blatType($event)"
        :style="`position: fixed; left: ${x}px; top: ${y}px`"
        role="menu"
        aria-orientation="vertical"
        tabindex="-1"
        data-slot="context-menu-content"
        :data-state="open ? 'open' : 'closed'"
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100"
        x-transition:leave-end="opacity-0 scale-95"
        {{ $attributes->twMerge('bg-popover text-popover-foreground z-50 max-h-96 min-w-[8rem] origin-top-left overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none') }}
    >
        {{ $slot }}
    </div>
</template>
```

## resources/views/components/ui/context-menu-group.blade.php

```blade
<x-ui.menu-group :data-slot="'context-menu-group'" :label-slot="'context-menu-label'" {{ $attributes }}>{{ $slot }}</x-ui.menu-group>
```

## resources/views/components/ui/context-menu-item.blade.php

```blade
@props([
    'href' => null,
    'variant' => 'default',
    'inset' => false,
    'disabled' => false,
])

@php
    // context-menu-item never exposed `type`/`closeOnSelect`; it always submits as a
    // button and always closes on select. Drop any caller-supplied values for those so
    // the shared primitive's props can't change behaviour.
    $attributes = $attributes->except(['type', 'closeOnSelect', 'close-on-select']);
@endphp

<x-ui.menu-item
    :data-slot="'context-menu-item'"
    :click-conditional="false"
    :href="$href"
    :variant="$variant"
    :inset="$inset"
    :disabled="$disabled"
    {{ $attributes }}
>{{ $slot }}</x-ui.menu-item>
```

## resources/views/components/ui/context-menu-label.blade.php

```blade
@props(['inset' => false])

<x-ui.menu-label
    :data-slot="'context-menu-label'"
    :classes="'text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8'"
    :inset="$inset"
    {{ $attributes }}
>{{ $slot }}</x-ui.menu-label>
```

## resources/views/components/ui/context-menu-radio-group.blade.php

```blade
@props(['value' => ''])

<div
    data-slot="context-menu-radio-group"
    role="group"
    x-data="{ radioValue: @js((string) $value) }"
    {{ $attributes }}
>
    {{ $slot }}
</div>
```

## resources/views/components/ui/context-menu-radio-item.blade.php

```blade
@props([
    'value' => '',
    'closeOnSelect' => false,
])

<x-ui.menu-radio-item
    :data-slot="'context-menu-radio-item'"
    :value="$value"
    :close-on-select="$closeOnSelect"
    {{ $attributes }}
>{{ $slot }}</x-ui.menu-radio-item>
```

## resources/views/components/ui/context-menu-separator.blade.php

```blade
<x-ui.menu-separator :data-slot="'context-menu-separator'" {{ $attributes }} />
```

## resources/views/components/ui/context-menu-shortcut.blade.php

```blade
<x-ui.menu-shortcut :data-slot="'context-menu-shortcut'" {{ $attributes }}>{{ $slot }}</x-ui.menu-shortcut>
```

## resources/views/components/ui/context-menu-sub.blade.php

```blade
<div
    data-slot="context-menu-sub"
    x-data="blatMenu()"
    x-id="['blat-ctx-submenu', 'blat-ctx-submenu-trigger']"
    @mouseenter="open = true; cancelClose()"
    @mouseleave="closeSoon()"
    {{ $attributes->twMerge('relative') }}
>
    {{ $slot }}
</div>
```

## resources/views/components/ui/context-menu-sub-content.blade.php

```blade
<template x-teleport="body" wire:ignore>
    <div
        x-blat-dialog-layer
        x-show="open"
        x-cloak
        x-ref="submenu"
        x-init="_menu = $el"
        x-anchor.fixed.right-start.offset.4="$refs.subtrigger"
        @mouseenter="cancelClose()"
        @mouseleave="closeSoon()"
        @keydown.escape.prevent.stop="closeMenu()"
        @keydown.left.prevent.stop="closeMenu()"
        @keydown.stop="$blatNav($event); $blatType($event)"
        :id="$id('blat-ctx-submenu')"
        :aria-labelledby="$id('blat-ctx-submenu-trigger')"
        role="menu"
        aria-orientation="vertical"
        tabindex="-1"
        data-slot="context-menu-sub-content"
        :data-state="open ? 'open' : 'closed'"
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100"
        x-transition:leave-end="opacity-0 scale-95"
        {{ $attributes->twMerge('bg-popover text-popover-foreground fixed z-50 min-w-[8rem] origin-top-left overflow-hidden rounded-md border p-1 shadow-lg outline-none') }}
    >
        {{ $slot }}
    </div>
</template>
```

## resources/views/components/ui/context-menu-sub-trigger.blade.php

```blade
@props(['inset' => false])

<div
    role="menuitem"
    tabindex="-1"
    x-ref="subtrigger"
    x-init="_trigger = $el"
    data-slot="context-menu-sub-trigger"
    aria-haspopup="menu"
    :aria-expanded="open"
    :aria-controls="$id('blat-ctx-submenu')"
    :id="$id('blat-ctx-submenu-trigger')"
    @click="toggleMenu()"
    @keydown.right.prevent.stop="openMenu('first')"
    @keydown.enter.prevent.stop="openMenu('first')"
    @keydown.space.prevent.stop="openMenu('first')"
    @if ($inset) data-inset @endif
    :data-state="open ? 'open' : 'closed'"
    {{ $attributes->twMerge("focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4") }}
>
    {{ $slot }}
    <x-lucide-chevron-right class="ml-auto size-4" aria-hidden="true" />
</div>
```

## resources/views/components/ui/context-menu-trigger.blade.php

```blade
<div
    data-slot="context-menu-trigger"
    x-init="_trigger = $el"
    tabindex="-1"
    @contextmenu="openAt($event)"
    {{ $attributes->twMerge('outline-none') }}
>
    {{ $slot }}
</div>
```
