# Dropdown Menu

Displays a menu of actions or functions, triggered by a button.

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

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

```blade
<div data-slot="dropdown-menu" x-data="blatMenu()" x-id="['blat-menu', 'blat-menu-trigger']" {{ $attributes->twMerge('contents text-left') }}>
    {{ $slot }}
</div>
```

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

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

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

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

```blade
@props([
    'align' => 'start',
    'side' => 'bottom',
    'sideOffset' => 4,
])

@php
    $placement = $side.($align === 'center' ? '' : '-'.$align);
    // Anchor to the inner sidebar menu-action button when present (it is absolutely
    // positioned at the item's top-right), otherwise to the trigger element itself.
    // The trigger span is display:contents (no box), so anchor to its real inner
    // element: the sidebar menu-action button when present, else the first child.
    $anchorRef = '($refs.trigger?.querySelector(\'[data-slot=sidebar-menu-action]\') || $refs.trigger?.firstElementChild || $refs.trigger)';
    $anchorAttr = 'x-blat-anchor.'.$placement.'.offset.'.$sideOffset.'="'.$anchorRef.'"';
@endphp

<template x-teleport="body" wire:ignore>
    <div
        x-blat-dialog-layer
        x-show="open"
        x-cloak
        x-ref="menu"
        x-init="_menu = $el"
        {!! $anchorAttr !!}
        @click.outside="closeMenu(false)"
        @keydown.escape.prevent.stop="closeMenu()"
        @keydown.tab.prevent.stop="closeMenu()"
        @keydown="$blatNav($event); $blatType($event)"
        :id="$id('blat-menu')"
        :aria-labelledby="$id('blat-menu-trigger')"
        role="menu"
        aria-orientation="vertical"
        tabindex="-1"
        data-slot="dropdown-menu-content"
        data-side="{{ $side }}"
        :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 max-h-96 min-w-[8rem] origin-top overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none') }}
    >
        {{ $slot }}
    </div>
</template>
```

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

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

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

```blade
@props([
    'href' => null,
    'variant' => 'default',
    'inset' => false,
    'disabled' => false,
    'closeOnSelect' => true,
    'type' => 'button',   // set type="submit" to submit the surrounding <form> (default button = no submit)
])

<x-ui.menu-item
    :data-slot="'dropdown-menu-item'"
    :href="$href"
    :variant="$variant"
    :inset="$inset"
    :disabled="$disabled"
    :close-on-select="$closeOnSelect"
    :type="$type"
    {{ $attributes }}
>{{ $slot }}</x-ui.menu-item>
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## resources/views/components/ui/dropdown-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-submenu')"
        :aria-labelledby="$id('blat-submenu-trigger')"
        role="menu"
        aria-orientation="vertical"
        tabindex="-1"
        data-slot="dropdown-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/dropdown-menu-sub-trigger.blade.php

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

<div
    role="menuitem"
    tabindex="-1"
    x-ref="subtrigger"
    x-init="_trigger = $el"
    data-slot="dropdown-menu-sub-trigger"
    aria-haspopup="menu"
    :aria-expanded="open"
    :aria-controls="$id('blat-submenu')"
    :id="$id('blat-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 [&_svg:not([class*='text-'])]:text-muted-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/dropdown-menu-trigger.blade.php

```blade
<span
    x-ref="trigger"
    x-init="_trigger = $el.querySelector('button, [href], [role=button], [tabindex]:not([tabindex=\'-1\'])') || $el"
    @click="toggleMenu()"
    @keydown.enter.prevent.stop="openMenu('first')"
    @keydown.space.prevent.stop="openMenu('first')"
    @keydown.down.prevent.stop="openMenu('first')"
    @keydown.up.prevent.stop="openMenu('last')"
    x-blat-trigger="{ haspopup: 'menu', controls: $id('blat-menu'), id: $id('blat-menu-trigger') }"
    data-slot="dropdown-menu-trigger"
    {{ $attributes->twMerge('contents') }}
>
    {{ $slot }}
</span>
```
