Files
livewire-material/resources/views/components/menu-item.blade.php
T
Andreas ReinholdandClaude Opus 5 9828c9258a Put the menu on M3's Expressive spacing and behaviour
Items were 44px tall with the baseline menu's 12px sides, the separator had half
its 8dp padding, a disabled item was skipped by the keyboard entirely, a chosen
item was told by colour and shape alone, and the list simply faded in place. Now:
48px rows with 16px either side, an 8px separator, disabled items focusable but
never activatable, a trailing check on a selected item, and an enter-exit that
scales the list out of the corner nearest its trigger — the scale on the spatial
spring, the fade on the effects one. Plan step 18, actions.md ACT-11, ACT-12,
ACT-13, ACT-26, ACT-27, ACT-28.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:04:03 +02:00

118 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{-- One item in an `<x-menu>`: 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 `<x-badge>` 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.
`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,
])
@php
$isLink = filled($link);
$tag = $isLink ? 'a' : 'button';
$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',
'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,
'data-keep-open' => $keepOpen ? 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
<{{ $tag }} {{ $attributes }}>
@if ($icon)
<x-livewire-material::icon :name="$icon" optical="20" :filled="$selected === true || $current" :class="$leadingIcon" />
@endif
<span class="min-w-0 flex-1">
<span class="block truncate type-body-lg">{{ $label ?? $slot }}</span>
@if ($description)
<span @class(['block type-body-md', $iconInk])>{{ $description }}</span>
@endif
</span>
@if ($badge !== null && $badge !== false && $badge !== '')
<x-livewire-material::badge :value="$badge === true ? null : $badge" class="shrink-0" />
@endif
@if ($shortcut)
<span @class(['shrink-0 type-label-sm', $iconInk])>{{ $shortcut }}</span>
@endif
@if ($iconRight)
<x-livewire-material::icon :name="$iconRight" optical="20" :class="'size-5 '.$iconInk" />
@elseif ($selected === true)
{{-- The third cue M3 recommends, so a chosen item is not told by colour and shape alone. --}}
<x-livewire-material::icon name="check" optical="20" :class="'size-5 '.$iconInk" />
@endif
</{{ $tag }}>