From 5a9211acd45f6ab26ed95a5e46790eae768edc2f Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Mon, 14 Sep 2026 18:08:47 +0200 Subject: [PATCH] Draw the FAB menu's trigger and items from the interaction classes Plan step 36 ("Interaction is the shared classes", the user, 2026-09-14). The trigger button (fab-menu.css) and each item (fab-menu-item.css) hand-rolled md-state-layer and md-focus-ring; both now render the classes and keep only their own colour, shape and motion. Neither needs md-touch-target: the trigger and every item draw at 56px. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/components/fab-menu-item.css | 38 ++----------------- resources/css/components/fab-menu.css | 36 ++---------------- .../views/components/fab-menu-item.blade.php | 2 +- resources/views/components/fab-menu.blade.php | 2 +- tests/Feature/Components/FabMenuTest.php | 14 +++++++ 5 files changed, 22 insertions(+), 70 deletions(-) diff --git a/resources/css/components/fab-menu-item.css b/resources/css/components/fab-menu-item.css index 317414f5..1383b830 100644 --- a/resources/css/components/fab-menu-item.css +++ b/resources/css/components/fab-menu-item.css @@ -4,7 +4,9 @@ * * FabMenuBaselineTokens (androidx Compose Material 3, Apache-2.0): 56px tall, 24px either side, * 8px between the icon and the label, 24px icons, full corner, elevation 3, `data-md-color`'s - * container. + * container. It renders the foundation's `md-state-layer` and `md-focus-ring` + * (foundation/interaction.css); its 56px already meets M3's target, so it needs no + * `md-touch-target`. * * The entry is `@starting-style` on the item itself; the exit needs the state to be readable * while the popover is on its way out, which `[data-md-fab-menu-popover]:not(:popover-open) > *` @@ -21,8 +23,6 @@ --md-fab-menu-item-container: var(--md-sys-color-primary-container); --md-fab-menu-item-on-container: var(--md-sys-color-on-primary-container); - position: relative; - isolation: isolate; display: inline-flex; flex-shrink: 0; align-items: center; @@ -38,7 +38,6 @@ letter-spacing: var(--md-sys-typescale-title-md-tracking); font-variation-settings: normal; cursor: pointer; - outline: none; -webkit-user-select: none; user-select: none; translate: 0 0; @@ -51,37 +50,6 @@ var(--md-sys-motion-spatial-fast), var(--md-sys-motion-effects-default), linear, linear; transition-behavior: allow-discrete; - &::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); - } - } - - &:focus-visible { - outline: 3px solid var(--md-sys-color-secondary); - outline-offset: 2px; - } - - &:focus-visible::before { - opacity: var(--md-sys-state-focus-state-layer-opacity); - } - - &:active::before { - opacity: var(--md-sys-state-pressed-state-layer-opacity); - } - @starting-style { translate: 0 var(--md-sys-measurement-space100); opacity: 0; diff --git a/resources/css/components/fab-menu.css b/resources/css/components/fab-menu.css index e827920d..4ef8819d 100644 --- a/resources/css/components/fab-menu.css +++ b/resources/css/components/fab-menu.css @@ -4,7 +4,9 @@ * FabMenuBaselineTokens (androidx Compose Material 3, Apache-2.0): the trigger is a 56px FAB, * `data-md-color`'s container, large corner, elevation 3, rising to elevation 4 under a hovering * pointer; while its list is open (`aria-expanded`) it turns fully round and paints the colour - * itself, on the default spatial and effects springs (ACT-19 splits the two). The list sits + * itself, on the default spatial and effects springs (ACT-19 splits the two). It renders the + * foundation's `md-state-layer` and `md-focus-ring` (foundation/interaction.css); its 56px already + * meets M3's target, so it needs no `md-touch-target`. The list sits * `data-md-position`'s corner of the trigger, 8px padding — the room the close button keeps below * the first item — and scrolls past `calc(100dvh - 128px)` on a short window, behind the close * button, which stays fixed (ACT-32). @@ -39,8 +41,6 @@ --md-fab-menu-color: var(--md-sys-color-primary); --md-fab-menu-on-color: var(--md-sys-color-on-primary); - position: relative; - isolation: isolate; display: inline-flex; flex-shrink: 0; align-items: center; @@ -52,7 +52,6 @@ color: var(--md-fab-menu-on-container); box-shadow: var(--md-sys-elevation-3); cursor: pointer; - outline: none; -webkit-user-select: none; user-select: none; transition-property: border-radius, background-color, color, box-shadow; @@ -63,39 +62,10 @@ var(--md-sys-motion-spatial-default), var(--md-sys-motion-effects-default), var(--md-sys-motion-effects-default), var(--md-sys-motion-effects-default); - &::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 { box-shadow: var(--md-sys-elevation-4); } - - &:hover::before { - opacity: var(--md-sys-state-hover-state-layer-opacity); - } - } - - &:focus-visible { - outline: 3px solid var(--md-sys-color-secondary); - outline-offset: 2px; - } - - &:focus-visible::before { - opacity: var(--md-sys-state-focus-state-layer-opacity); - } - - &:active::before { - opacity: var(--md-sys-state-pressed-state-layer-opacity); } &[aria-expanded='true'] { diff --git a/resources/views/components/fab-menu-item.blade.php b/resources/views/components/fab-menu-item.blade.php index 9bfbb06a..0d557ae6 100644 --- a/resources/views/components/fab-menu-item.blade.php +++ b/resources/views/components/fab-menu-item.blade.php @@ -32,7 +32,7 @@ 'target' => $isLink && $external ? '_blank' : null, 'rel' => $isLink && $external ? 'noopener' : null, 'wire:navigate' => $isLink && ! $external && ! $attributes->has('wire:navigate') ? true : null, - ], fn ($value): bool => $value !== null)); + ], fn ($value): bool => $value !== null))->class(['md-state-layer', 'md-focus-ring']); @endphp <{{ $tag }} {{ $attributes }}> diff --git a/resources/views/components/fab-menu.blade.php b/resources/views/components/fab-menu.blade.php index 7ab3e603..a720a9ff 100644 --- a/resources/views/components/fab-menu.blade.php +++ b/resources/views/components/fab-menu.blade.php @@ -53,7 +53,7 @@ x-on:keydown.down.prevent="open('first')" x-on:keydown.up.prevent="open('last')" > - diff --git a/tests/Feature/Components/FabMenuTest.php b/tests/Feature/Components/FabMenuTest.php index c17c8123..c6270f11 100644 --- a/tests/Feature/Components/FabMenuTest.php +++ b/tests/Feature/Components/FabMenuTest.php @@ -33,6 +33,20 @@ it('opens a FAB menu of end-aligned actions above it', function () { ]); }); +it('draws the trigger and its items from the foundation\'s shared classes, not their own', function () { + $html = (string) $this->blade(<<<'BLADE' + + + + BLADE); + + expect($html) + ->toContain('class="md-state-layer md-focus-ring" aria-label="New"') + ->toMatch('/