Draw the FAB without Tailwind

Plan step 36. <x-fab> renders data-md-fab with data-md-size,
data-md-color, data-md-variant, data-md-extended and, collapsing on
scroll, data-md-collapse-on-scroll with a bound data-md-collapsed; it
passes its glyph's size to <x-icon>. fab.css draws FabBaseline/Medium/
LargeTokens and ExtendedFab*Tokens per size, the container and filled
colours, elevation 3 and 4 on hover, the state layer and focus ring,
and the collapse morph that leaves actions.css.

data-fab becomes data-md-fab, also in navigation.css (the rail flattens
a nested FAB) and toolbar.css (a docked toolbar's FAB), with their tests.
The actions browser test gains the owed collapse-on-scroll test.

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 15:06:45 +02:00
co-authored by Claude Opus 5
parent 2a4dcead3d
commit 1cf4f16c95
11 changed files with 357 additions and 146 deletions
+21
View File
@@ -465,6 +465,27 @@ it('closes a FAB menu when an item\'s action runs, and opens and closes it clean
->assertScript(focused("getAttribute('aria-label') === 'New'"));
});
it('collapses an extended FAB to a FAB while the page scrolls down, and extends it on the way back', function () {
$fab = "document.querySelector('#buttons [data-md-collapse-on-scroll]')";
// Reduced motion takes the spring's duration to zero, so the width is final at once.
$page = visit('/material/buttons', ['reducedMotion' => 'reduce'])
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'")
->assertScript("! {$fab}.hasAttribute('data-md-collapsed') && {$fab}.getBoundingClientRect().width > 56");
$page->script('window.scrollTo(0, 600)');
$page->assertScript("{$fab}.hasAttribute('data-md-collapsed')")
->assertScript("Math.round({$fab}.getBoundingClientRect().width) === 56")
// Clipped, not removed: the collapsed FAB keeps the extended one's name.
->assertScript("{$fab}.textContent.trim() === 'Compose'");
$page->script('window.scrollTo(0, 200)');
$page->assertScript("! {$fab}.hasAttribute('data-md-collapsed')")
->assertScript("{$fab}.getBoundingClientRect().width > 56");
});
it('takes a press on a small connected segment at its 48px edge, past what it draws', function () {
// M3: "XS and S connected button groups have a 48dp target area and a 48dp minimum width".
showcase()->assertScript(<<<'JS'