{{-- One item in an ``: an action, a link, or a choice. `label`, a leading `icon` (`icon-class` adds classes to it), an `icon-right`, a `description` under the label and a `shortcut` at the end (M3's trailing supporting text: "⌘C"). `link` makes it an anchor, with `wire:navigate` unless `external` or `no-wire-navigate`. `selected` (true or false) makes it a `menuitemcheckbox` with `aria-checked`; a selected item takes Expressive's selected shape and tertiary-container, and a `check` at its end unless it has an `icon-right` of its own — M3 asks for a cue beyond the colour and the shape. `current` is for a menu of places rather than choices — a section picker — and marks the page you are on: `aria-current="page"`, the selected shape in secondary-container, the colour M3 gives the navigation indicator. `badge` draws `` at the end of the row: `true` for a dot, or a count. `disabled` keeps it in the list, reachable by the keyboard but not selectable — M3 keeps a disabled item focusable so a person can find out it is there. `keep-open` leaves the menu open when it is activated — for a choice the person may want to change twice. `submenu` turns the item into a menu of its own: the slot holds ``s instead of a label, and they open in a second popover beside this one, on the item's end, flipping to its start where the window has no room. The item says so — `aria-haspopup="menu"`, `aria-expanded`, and a chevron at its end — and keeps the APG menu keyboard: Right, Enter or Space open it on its first item, Left or Escape close it and come back here, and on a fine pointer resting on the item opens it. Choosing anything inside closes the whole menu, as it would from the outer list. In the bottom sheet of an `` on a compact window the same markup opens in place instead: the list stands under the item, inset, and the chevron turns to point down at it (menu.css) — a sheet has no room beside an item, and M3 calls submenus "best suited to large screens" (docs/reference/m3/components-actions-communication-containment.md § Menus → Behaviour). It is never shown as a popover there, so menu.js only flips `aria-expanded`, and the keyboard stays the one above: Right, Enter or Space open it, Left or Escape close it and come back to the item, and Escape closes nothing more. `icon-class` is for an icon whose colour means something of its own, a sport's glyph in the sport's colour (`icon-class="text-sport-run"`). A colour there paints the icon, a selected item's too: the icon's own colour then carries no specificity, because which of two colour utilities wins depends on the order Tailwind emits them. A disabled item's icon stays disabled. 48px tall, body-large label, 20px icons, 16px either side (SegmentedMenuTokens' Expressive spacing), 4px corners that open to 12px at the ends of the list — the corner on the spatial spring and the colour on the effects one (`state-transition-fast`). The row is 48px rather than SegmentedMenuTokens' 44px: M3's menu page publishes 48dp and asks for ≥48×48 targets inside an item's slots, and the library's own select and choices menus are 48px, so the more binding source wins over the more specific one. --}} @props([ 'label' => null, 'icon' => null, 'iconClass' => null, 'iconRight' => null, 'description' => null, 'shortcut' => null, 'link' => null, 'external' => false, 'noWireNavigate' => false, 'selected' => null, 'current' => false, 'badge' => null, 'disabled' => false, 'keepOpen' => false, 'submenu' => false, ]) @php $isLink = filled($link); $tag = $isLink ? 'a' : 'button'; // A submenu's own popover, named like the menu's: a new id and a new anchor name with every // render, matched through a morph by the key rather than by either of them. $key = $submenu ? \Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10)) : null; $anchor = $submenu ? "--material-submenu-{$key}" : null; $attributes = $attributes ->class([ 'group/item state-layer flex w-full min-h-12 cursor-pointer items-center gap-3 px-4 text-start outline-none', 'rounded-corner-xs first:rounded-t-corner-md last:rounded-b-corner-md', 'state-transition-fast', 'focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-secondary', 'py-2' => filled($description), 'rounded-corner-md bg-tertiary-container text-on-tertiary-container' => $selected === true, 'rounded-corner-md bg-secondary-container text-on-secondary-container' => $current && $selected !== true, 'pointer-events-none text-on-surface/38' => $disabled, ]) ->merge(array_filter([ 'role' => $selected === null ? 'menuitem' : 'menuitemcheckbox', 'aria-checked' => $selected === null ? null : ($selected ? 'true' : 'false'), 'aria-current' => $current ? 'page' : null, 'aria-disabled' => $disabled ? 'true' : null, 'tabindex' => '-1', 'x-ref' => $submenu ? 'trigger' : null, 'style' => $submenu ? "anchor-name: {$anchor}" : null, 'aria-haspopup' => $submenu ? 'menu' : null, 'aria-expanded' => $submenu ? 'false' : null, 'aria-controls' => $submenu ? "material-submenu-{$key}" : null, 'x-on:click' => $submenu ? "toggle('first')" : null, 'type' => $isLink ? null : 'button', 'href' => $isLink ? $link : null, 'target' => $isLink && $external ? '_blank' : null, 'rel' => $isLink && $external ? 'noopener' : null, 'wire:navigate' => $isLink && ! $external && ! $noWireNavigate && ! $attributes->has('wire:navigate') ? true : null, // Opening a submenu is not choosing anything: the outer menu stays where it was. 'data-keep-open' => $keepOpen || $submenu ? true : null, ], fn ($value): bool => $value !== null)); $iconInk = match (true) { $disabled => 'text-on-surface/38', $selected === true => 'text-on-tertiary-container', $current => 'text-on-secondary-container', default => 'text-on-surface-variant', }; $leadingIcon = match (true) { blank($iconClass) => 'size-5 '.$iconInk, $disabled => \Illuminate\Support\Arr::toCssClasses(['size-5', $iconClass, 'text-on-surface/38!']), $selected === true => \Illuminate\Support\Arr::toCssClasses(['size-5 [:where(&)]:text-on-tertiary-container', $iconClass]), $current => \Illuminate\Support\Arr::toCssClasses(['size-5 [:where(&)]:text-on-secondary-container', $iconClass]), default => \Illuminate\Support\Arr::toCssClasses(['size-5 [:where(&)]:text-on-surface-variant', $iconClass]), }; @endphp @if ($submenu)
@endif <{{ $tag }} {{ $attributes }}> @if ($icon) @endif {{ $submenu ? $label : ($label ?? $slot) }} @if ($description) {{ $description }} @endif @if ($badge !== null && $badge !== false && $badge !== '') @endif @if ($shortcut) {{ $shortcut }} @endif @if ($iconRight) @elseif ($submenu) {{-- M3's submenu marker: it points the way the list opens, and turns over in an RTL page. --}} @elseif ($selected === true) {{-- The third cue M3 recommends, so a chosen item is not told by colour and shape alone. --}} @endif @if ($submenu)
'material-submenu-'.substr(md5((string) $label), 0, 10)]) }} id="material-submenu-{{ $key }}" popover="auto" role="menu" data-submenu aria-label="{{ $label }}" tabindex="-1" style="position-anchor: {{ $anchor }}" x-on:keydown.stop="navigate($event)" x-on:click="activate($event)" class="m-0 mx-1 min-w-28 max-w-70 max-h-[min(18rem,calc(100dvh-2rem))] origin-top overflow-y-auto border-0 p-1 rounded-corner-lg shadow-elevation-2 popover-transition [inset:auto] [position-area:inline-end_span-block-end] [position-try-fallbacks:flip-inline]" > {{ $slot }}
@endif