diff --git a/resources/css/components/menu-item.css b/resources/css/components/menu-item.css index adc948e1..2d942e90 100644 --- a/resources/css/components/menu-item.css +++ b/resources/css/components/menu-item.css @@ -16,7 +16,11 @@ * (`ItemTrailingSupportingTextFont`) sits at the end. Every quiet part of the row — the icon, the * description, the trailing text — inks together as `--md-menu-item-ink`; corner and colour are * two springs, not one (§ ACT-19), and `icon-class` paints only the leading icon, over that ink - * but never over disabled. + * but never over disabled. The row renders the foundation's `md-state-layer` and `md-focus-ring` + * (foundation/interaction.css) — no `md-touch-target`, since the row is already 48px tall — and + * keeps only one refinement: the ring reads inward (-3px), because a row sits edge to edge in the + * list and an outward one would run past it. A disabled item stays focusable and keeps its ring, + * because M3 keeps it reachable, but the class already withholds the layer from `aria-disabled`. * * `submenu` turns the row into the WAI-ARIA menu button for a second `role="menu"` popover beside * it, anchored to its end and flipping to the start where the window has no room @@ -37,8 +41,6 @@ [data-md-menu-item] { --md-menu-item-ink: var(--md-sys-color-on-surface-variant); - position: relative; - isolation: isolate; display: flex; inline-size: 100%; min-block-size: var(--md-sys-measurement-space600); @@ -50,7 +52,6 @@ color: var(--md-sys-color-on-surface); text-align: start; cursor: pointer; - outline: none; -webkit-user-select: none; user-select: none; transition-property: border-radius, background-color, color; @@ -61,41 +62,10 @@ var(--md-sys-motion-spatial-fast), var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast); - &::before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - border-radius: inherit; - background-color: currentColor; - opacity: 0; - pointer-events: none; - transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast); - } - - @media (hover: hover) { - &:hover::before { - opacity: var(--md-sys-state-hover-state-layer-opacity); - } - } - + /* The row sits edge to edge in the list, so the ring reads inward, not past the item. */ &:focus-visible { - outline: 3px solid var(--md-sys-color-secondary); outline-offset: -3px; } - - &:focus-visible::before { - opacity: var(--md-sys-state-focus-state-layer-opacity); - } - - &:active::before { - opacity: var(--md-sys-state-pressed-state-layer-opacity); - } - - /* A disabled item stays focusable and keeps its focus ring, but has no state layer. */ - &[aria-disabled='true']::before { - display: none; - } } [data-md-menu-item]:first-child { diff --git a/resources/views/components/menu-item.blade.php b/resources/views/components/menu-item.blade.php index 00f2098d..86cd4391 100644 --- a/resources/views/components/menu-item.blade.php +++ b/resources/views/components/menu-item.blade.php @@ -90,7 +90,7 @@ '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-md-keep-open' => $keepOpen || $submenu ? true : null, - ], fn ($value): bool => $value !== null)); + ], fn ($value): bool => $value !== null))->class(['md-state-layer', 'md-focus-ring']); @endphp @if ($submenu) diff --git a/tests/Feature/Components/MenuTest.php b/tests/Feature/Components/MenuTest.php index 529e299a..ba310015 100644 --- a/tests/Feature/Components/MenuTest.php +++ b/tests/Feature/Components/MenuTest.php @@ -130,8 +130,11 @@ it('links an item, and keeps a disabled one out of reach', function () { ->assertSee('aria-disabled="true"', false) ->assertSee('data-md-menu-item', false); + // A disabled item stays focusable and keeps its ring, but the foundation's md-state-layer class + // (which the item renders, not a copy of its own) already withholds the layer on aria-disabled. expect(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true']"))->toMatchArray(['pointer-events' => 'none']) - ->and(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true']::before"))->toBe(['display' => 'none']); + ->and(ComponentStylesheet::read('menu-item')->has('[data-md-menu-item]::before'))->toBeFalse() + ->and((string) $this->blade(''))->toContain('class="md-state-layer md-focus-ring"'); }); it('separates and labels groups', function () {