Files
livewire-material/resources/views/components/fab.blade.php
T
Andreas Reinhold / reiniandClaude Opus 5 ea12f9d75b Draw the FAB's state layer and focus ring from the classes
Plan step 36 ("Interaction is the shared classes", the user, 2026-09-14).
fab.css hand-rolled the same declarations as foundation/interaction.css's
md-state-layer and md-focus-ring; it now renders those classes and keeps
only its own hover elevation, colour and motion. Every FAB size draws at
56px or more, so it needs no md-touch-target.

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

94 lines
4.5 KiB
PHP

{{-- An M3 Expressive floating action button: the screen's primary action.
With only an `icon` it is a FAB — `size` `sm` 56px (the default; M3's deprecated small FAB is
gone, so this is the baseline FAB), `md` 80px, `lg` 96px. With a `label` it is an extended
FAB at the same three heights. `color` is `primary` (the default), `secondary` or `tertiary`,
drawn in its container, or in the colour itself with `variant="filled"`.
It does not place itself: put it where the layout wants it, e.g.
`<div style="position: fixed; inset-inline-end: 16px; bottom: 16px"><x-fab icon="add" tooltip="New share" /></div>`.
For a create action that is a FAB on a phone and a header button above, use `<x-button fab>`.
The glyph is filled, not outlined: M3 says so twice, in the FAB's anatomy and again in its
guidelines ("icon should be filled (not outlined) and unambiguous").
There is no `disabled`: M3 says never to disable a FAB — "if its action is unavailable,
remove the FAB entirely instead" — so hide it (`@if`), or leave it out of the render, rather
than greying it out. A form-submit FAB that must not be pressed twice takes the caller's own
`wire:loading.attr="disabled"`.
`data-md-fab` marks the root, so a place a FAB sits in can draw it its own way: a navigation
rail flattens a nested FAB to elevation 0.
`collapse-on-scroll` is M3's extended FAB that "can collapse to a FAB on scroll-down and
re-expand to extended on scroll-up": while the window scrolls down it shrinks to the FAB of
its size, and it extends again when the page scrolls back up or reaches the top. The label
closes and fades while the width follows it on the spatial spring; under reduced motion the
two swap outright. The label stays in the page, clipped rather than removed, so the collapsed
FAB keeps its accessible name. It needs an `icon` — a FAB with no glyph is no FAB — and does
nothing on a plain FAB.
Sizes, corners and elevation from FabBaseline/Medium/LargeTokens and ExtendedFab*Tokens
(androidx Compose Material 3, Apache-2.0), drawn by resources/css/components/fab.css. --}}
@props([
'icon' => null,
'label' => null,
'size' => 'sm',
'color' => 'primary',
'variant' => 'container',
'link' => null,
'external' => false,
'tooltip' => null,
'type' => 'button',
'collapseOnScroll' => false,
])
@php
$size = in_array($size, ['sm', 'md', 'lg'], true) ? $size : 'sm';
$color = in_array($color, ['primary', 'secondary', 'tertiary'], true) ? $color : 'primary';
$variant = $variant === 'filled' ? 'filled' : 'container';
$extended = filled($label) || $slot->isNotEmpty();
$collapsing = $collapseOnScroll && $extended && filled($icon);
$isLink = filled($link);
$iconSize = ['sm' => 24, 'md' => 28, 'lg' => 32][$size];
$anchor = $tooltip !== null ? '--material-fab-'.\Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10)) : null;
$tag = $isLink ? 'a' : 'button';
$attributes = $attributes->merge(array_filter([
// The hook a place uses to draw a nested FAB its own way: a rail flattens it to 0dp.
'data-md-fab' => true,
'data-md-size' => $size,
'data-md-color' => $color,
'data-md-variant' => $variant,
'data-md-extended' => $extended ? true : null,
'data-md-collapse-on-scroll' => $collapsing ? true : null,
'href' => $isLink ? $link : null,
'target' => $isLink && $external ? '_blank' : null,
'rel' => $isLink && $external ? 'noopener' : null,
'wire:navigate' => $isLink && ! $external && ! $attributes->has('wire:navigate') ? true : null,
'type' => $isLink ? null : $type,
'x-data' => $collapsing ? 'materialFab' : null,
'x-bind:data-md-collapsed' => $collapsing ? "collapsed ? '' : null" : null,
'aria-label' => ! $extended && ! $attributes->has('aria-label') ? $tooltip : null,
'style' => $anchor ? "anchor-name: {$anchor}" : null,
], fn ($value): bool => $value !== null))->class(['md-state-layer', 'md-focus-ring']);
@endphp
<{{ $tag }} {{ $attributes }}>
@if ($icon)
<x-livewire-material::icon :name="$icon" filled :size="$iconSize" />
@endif
@if ($collapsing)
<span data-md-fab-label><span>{{ $label ?? $slot }}</span></span>
@elseif ($extended)
<span>{{ $label ?? $slot }}</span>
@endif
@if ($tooltip !== null)
<x-livewire-material::tooltip :text="$tooltip" :anchor="$anchor" />
@endif
</{{ $tag }}>