Files
livewire-material/resources/css/components/fab.css
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

177 lines
7.5 KiB
CSS

/*
* <x-fab>: M3 Expressive's floating action button and extended FAB.
*
* FabBaseline/Medium/LargeTokens and ExtendedFab*Tokens (androidx Compose Material 3, Apache-2.0),
* per `data-md-size`:
*
* size FAB corner glyph extended: min width gap padding type
* sm 56px lg (16px) 24px 80px 8px 16px title-md
* md 80px lg-increased 28px 80px 16px 26px title-lg
* lg 96px xl (28px) 32px 96px 20px 28px headline-sm
*
* A FAB and its extended form share the height and the corner. `data-md-color` is `primary`,
* `secondary` or `tertiary`, drawn in its container (`data-md-variant="container"`) or in the
* colour itself (`filled`), at elevation 3, 4 under a hovering pointer, the shadow and colours on
* the fast effects spring. It renders the foundation's `md-state-layer` and `md-focus-ring`
* (foundation/interaction.css); every size draws at 56px or more, so it needs no `md-touch-target`.
* `data-md-fab` on the root is also the hook a place uses to draw a nested FAB its own way: a
* navigation rail or a docked toolbar flattens it to elevation 0.
*
* `data-md-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 — when switching between FAB↔extended FAB, shape changes, the
* icon moves left, and the text label fades in/out" (resources/js/fab.js sets
* `data-md-collapsed`). `width: auto` cannot transition, so the label sits in a one-column grid
* whose track closes to zero; that track is what animates, and the button lays itself out around
* it every frame. The gap and the minimum width go with it, all on the default spatial spring,
* while the label fades on the effects spring. The collapsed width is the FAB's own size. The
* extended padding already centres the glyph at `sm` (16 + 24 + 16) and `md` (26 + 28 + 26); at
* `lg` its 28px is 4px short of the 32px that centres a 32px glyph in 96px, so that one moves. The
* label stays in the accessibility tree — clipped and transparent, never `display: none` — so the
* collapsed FAB keeps the name the extended one had. Under reduced motion tokens/motion.css takes
* every duration to zero, so it swaps.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@import './icon.css';
@import './tooltip.css';
@layer material.components {
[data-md-fab] {
--md-fab-color: var(--md-sys-color-primary);
--md-fab-on-color: var(--md-sys-color-on-primary);
--md-fab-container: var(--md-sys-color-primary-container);
--md-fab-on-container: var(--md-sys-color-on-primary-container);
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
block-size: var(--md-fab-size);
border-radius: var(--md-fab-corner);
background-color: var(--md-fab-container);
color: var(--md-fab-on-container);
box-shadow: var(--md-sys-elevation-3);
white-space: nowrap;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
transition-property: box-shadow, background-color, color;
transition-duration: var(--md-sys-motion-effects-fast-duration);
transition-timing-function: var(--md-sys-motion-effects-fast);
@media (hover: hover) {
&:hover {
box-shadow: var(--md-sys-elevation-4);
}
}
}
[data-md-fab][data-md-size='sm'] {
--md-fab-size: 56px;
--md-fab-corner: var(--md-sys-shape-corner-lg);
--md-fab-extended-min: 80px;
}
[data-md-fab][data-md-size='md'] {
--md-fab-size: 80px;
--md-fab-corner: var(--md-sys-shape-corner-lg-increased);
--md-fab-extended-min: 80px;
}
[data-md-fab][data-md-size='lg'] {
--md-fab-size: 96px;
--md-fab-corner: var(--md-sys-shape-corner-xl);
--md-fab-extended-min: 96px;
}
[data-md-fab][data-md-color='secondary'] {
--md-fab-color: var(--md-sys-color-secondary);
--md-fab-on-color: var(--md-sys-color-on-secondary);
--md-fab-container: var(--md-sys-color-secondary-container);
--md-fab-on-container: var(--md-sys-color-on-secondary-container);
}
[data-md-fab][data-md-color='tertiary'] {
--md-fab-color: var(--md-sys-color-tertiary);
--md-fab-on-color: var(--md-sys-color-on-tertiary);
--md-fab-container: var(--md-sys-color-tertiary-container);
--md-fab-on-container: var(--md-sys-color-on-tertiary-container);
}
[data-md-fab][data-md-variant='filled'] {
background-color: var(--md-fab-color);
color: var(--md-fab-on-color);
}
[data-md-fab]:not([data-md-extended]) {
inline-size: var(--md-fab-size);
}
[data-md-fab][data-md-extended] {
min-inline-size: var(--md-fab-extended-min);
}
[data-md-fab][data-md-extended][data-md-size='sm'] {
gap: var(--md-sys-measurement-space100);
padding-inline: var(--md-sys-measurement-space200);
font: var(--md-sys-typescale-title-md);
letter-spacing: var(--md-sys-typescale-title-md-tracking);
font-variation-settings: normal;
}
[data-md-fab][data-md-extended][data-md-size='md'] {
gap: var(--md-sys-measurement-space200);
padding-inline: 26px;
font: var(--md-sys-typescale-title-lg);
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
font-variation-settings: normal;
}
[data-md-fab][data-md-extended][data-md-size='lg'] {
gap: 20px;
padding-inline: 28px;
font: var(--md-sys-typescale-headline-sm);
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
font-variation-settings: normal;
}
/* The extended FAB that collapses to a FAB while the page scrolls down. */
[data-md-fab][data-md-collapse-on-scroll] {
transition-property: min-inline-size, padding-inline, gap, box-shadow, background-color, color;
transition-duration:
var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-spatial-default-duration),
var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-effects-fast-duration);
transition-timing-function:
var(--md-sys-motion-spatial-default), var(--md-sys-motion-spatial-default), var(--md-sys-motion-spatial-default),
var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast);
}
[data-md-fab-label] {
display: grid;
grid-template-columns: 1fr;
transition-property: grid-template-columns, opacity;
transition-duration: var(--md-sys-motion-spatial-default-duration), var(--md-sys-motion-effects-default-duration);
transition-timing-function: var(--md-sys-motion-spatial-default), var(--md-sys-motion-effects-default);
& > span {
overflow: hidden;
white-space: nowrap;
}
}
[data-md-fab][data-md-collapse-on-scroll][data-md-collapsed] {
min-inline-size: var(--md-fab-size);
gap: 0;
& [data-md-fab-label] {
grid-template-columns: 0fr;
opacity: 0;
}
}
[data-md-fab][data-md-collapse-on-scroll][data-md-size='lg'][data-md-collapsed] {
padding-inline: var(--md-sys-measurement-space400);
}
}