Collapse an extended FAB to a FAB while the page scrolls

Plan step 22, actions.md § Missing (FAB to extended FAB scroll collapse):
M3 has an extended FAB collapse to a FAB on scroll-down and re-extend on
scroll-up, and `<x-button fab>` only swapped on window width.

`<x-fab collapse-on-scroll>` (extended, with an icon) takes a
`materialFab` flag from the new resources/js/fab.js, which reads the
window's scroll once a frame, ignores moves under 8px and extends again
near the top. The morph is CSS in actions.css: the label sits in a grid
track that closes to zero, the gap and minimum width follow on the
default spatial spring, and the label fades on the effects one; reduced
motion swaps outright through the motion tokens. The label is clipped, not
removed, so the collapsed FAB keeps its accessible name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 07:15:56 +02:00
co-authored by Claude Opus 5
parent 44ae9ecea4
commit 7e4e7b44c6
7 changed files with 179 additions and 2 deletions
+17
View File
@@ -41,6 +41,23 @@ it('gives the extended FAB M3\'s gaps and its 80px minimum width', function (str
'lg' => ['lg', 'h-24 min-w-24 gap-5'],
]);
it('collapses an extended FAB to a FAB on scroll when asked', function () {
$html = (string) $this->blade('<x-fab icon="add" label="New share" size="lg" collapse-on-scroll />');
expect($html)
->toContain('x-data="materialFab"')
->toContain('x-bind:data-collapsed="collapsed ? &#039;&#039; : null"')
->toContain('data-fab-collapsible')
->toContain('data-fab-size="lg"')
// The label is clipped, never removed, so the collapsed FAB keeps its name.
->toContain('<span data-fab-label><span>New share</span></span>');
// A plain FAB has nothing to collapse, and an extended FAB without a glyph would collapse to nothing.
expect((string) $this->blade('<x-fab icon="add" aria-label="New" collapse-on-scroll />'))->not->toContain('materialFab')
->and((string) $this->blade('<x-fab label="New share" collapse-on-scroll />'))->not->toContain('materialFab')
->and((string) $this->blade('<x-fab icon="add" label="New share" />'))->not->toContain('materialFab')->toContain('<span>New share</span>');
});
it('marks its root for the places that draw a nested FAB their own way', function () {
expect((string) $this->blade('<x-fab icon="add" aria-label="New" />'))->toContain('data-fab');
});