You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
5.3 KiB
132 lines
5.3 KiB
|
4 months ago
|
/*
|
||
|
|
* Fab (Floating Action Button) recipe.
|
||
|
|
*
|
||
|
|
* The composed <Button> paints the chrome (variant fill, intent colour, focus
|
||
|
|
* ring, icon slot). This recipe owns only the FAB-specific treatment, layered
|
||
|
|
* on the same `<button>` (it carries `data-button` + `data-fab`):
|
||
|
|
*
|
||
|
|
* 1. prominent elevation, lifting on hover;
|
||
|
|
* 2. the FAB size scale (its own sm/md/lg — larger than a control);
|
||
|
|
* 3. the circular icon form vs the extended pill (`data-extended`);
|
||
|
|
* 4. floating placement, fixed to the viewport (`data-placement`).
|
||
|
|
*
|
||
|
|
* The dimensional rules out-specify Button's `[data-button][data-size]` (0,2,0)
|
||
|
|
* by also matching `[data-fab]` (≥ 0,3,0), so they win regardless of which
|
||
|
|
* code-split chunk loads first.
|
||
|
|
*
|
||
|
|
* Elevation drives Button's OWN `--button-shadow` token (its `[data-button]`
|
||
|
|
* rule already renders + transitions `box-shadow`), so the FAB shadow composes
|
||
|
|
* with Button's `outline` focus ring instead of fighting its box-shadow rule on
|
||
|
|
* a specificity tie. The `[data-button][data-fab]` prefix (0,2,0) wins over any
|
||
|
|
* `[data-button]` setter regardless of code-split load order.
|
||
|
|
*
|
||
|
|
* The hover lift uses the `translate` property, NOT `transform` — Button's
|
||
|
|
* active press-squeeze animates `transform: scale(...)`, so a transform here
|
||
|
|
* would clobber it; `translate` composes cleanly alongside it. Button transitions
|
||
|
|
* `transform` but not `translate`, so we re-declare its transition list + add
|
||
|
|
* `translate` for the lift to animate.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* The FAB's size / icon / offset / elevation / lift scale (`--fab-*`) is declared
|
||
|
|
* in the recipe (`base.ts > fab`) and generated to `:root` — this file only
|
||
|
|
* CONSUMES it (the values are NOT literals here). The scale is deliberately off
|
||
|
|
* the control-height ladder (Material 40 / 56 / 72); the shadow tops out at
|
||
|
|
* `--shadow-overlay`, so the hover affordance is the `--fab-lift` translate. */
|
||
|
|
|
||
|
|
[data-button][data-fab] {
|
||
|
|
--button-shadow: var(--fab-shadow);
|
||
|
|
transition:
|
||
|
|
background var(--button-transition-duration) var(--button-transition-ease),
|
||
|
|
border-color var(--button-transition-duration) var(--button-transition-ease),
|
||
|
|
color var(--button-transition-duration) var(--button-transition-ease),
|
||
|
|
box-shadow var(--button-transition-duration) var(--button-transition-ease),
|
||
|
|
transform var(--button-transition-duration) var(--button-transition-ease),
|
||
|
|
translate var(--button-transition-duration) var(--button-transition-ease);
|
||
|
|
}
|
||
|
|
|
||
|
|
[data-button][data-fab]:hover:not([data-disabled]):not(:active) {
|
||
|
|
translate: 0 calc(-1 * var(--fab-lift));
|
||
|
|
}
|
||
|
|
|
||
|
|
[data-button][data-fab][data-disabled] {
|
||
|
|
--button-shadow: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Size (diameter + glyph) ─────────────────────────────────────────── */
|
||
|
|
|
||
|
|
[data-button][data-fab][data-fab-size='sm'] {
|
||
|
|
block-size: var(--fab-size-sm);
|
||
|
|
font-size: var(--fab-icon-sm);
|
||
|
|
}
|
||
|
|
[data-button][data-fab][data-fab-size='md'] {
|
||
|
|
block-size: var(--fab-size-md);
|
||
|
|
font-size: var(--fab-icon-md);
|
||
|
|
}
|
||
|
|
[data-button][data-fab][data-fab-size='lg'] {
|
||
|
|
block-size: var(--fab-size-lg);
|
||
|
|
font-size: var(--fab-icon-lg);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Circular icon-only form: width = height. Overrides Button's iconOnly
|
||
|
|
* `inline-size: var(--_button-height)` (which would size to the control
|
||
|
|
* height, not the FAB diameter). */
|
||
|
|
[data-button][data-fab][data-fab-size='sm']:not([data-extended]) {
|
||
|
|
inline-size: var(--fab-size-sm);
|
||
|
|
}
|
||
|
|
[data-button][data-fab][data-fab-size='md']:not([data-extended]) {
|
||
|
|
inline-size: var(--fab-size-md);
|
||
|
|
}
|
||
|
|
[data-button][data-fab][data-fab-size='lg']:not([data-extended]) {
|
||
|
|
inline-size: var(--fab-size-lg);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Extended pill: height = diameter, width hugs the content with comfortable
|
||
|
|
* inline padding + a gap between the icon and the label. */
|
||
|
|
[data-button][data-fab][data-extended] {
|
||
|
|
inline-size: auto;
|
||
|
|
padding-inline: var(--space-5);
|
||
|
|
gap: var(--space-2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* The visible glyph follows the FAB's font-size (1em icon). */
|
||
|
|
[data-fab] [data-button-icon] {
|
||
|
|
font-size: 1em;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ── Floating placement (fixed to the viewport) ──────────────────────── */
|
||
|
|
/*
|
||
|
|
* `static` = no positioning (the consumer places the FAB). The four corners
|
||
|
|
* pin to their edges, padded by `--fab-offset` + the matching safe-area inset
|
||
|
|
* so the FAB clears notches / home indicators.
|
||
|
|
*/
|
||
|
|
[data-fab][data-placement='bottom-end'],
|
||
|
|
[data-fab][data-placement='bottom-start'],
|
||
|
|
[data-fab][data-placement='top-end'],
|
||
|
|
[data-fab][data-placement='top-start'] {
|
||
|
|
position: fixed;
|
||
|
|
z-index: var(--fab-z);
|
||
|
|
}
|
||
|
|
|
||
|
|
[data-fab][data-placement='bottom-end'] {
|
||
|
|
inset-block-end: calc(var(--fab-offset) + env(safe-area-inset-bottom, 0px));
|
||
|
|
inset-inline-end: calc(var(--fab-offset) + env(safe-area-inset-right, 0px));
|
||
|
|
}
|
||
|
|
[data-fab][data-placement='bottom-start'] {
|
||
|
|
inset-block-end: calc(var(--fab-offset) + env(safe-area-inset-bottom, 0px));
|
||
|
|
inset-inline-start: calc(var(--fab-offset) + env(safe-area-inset-left, 0px));
|
||
|
|
}
|
||
|
|
[data-fab][data-placement='top-end'] {
|
||
|
|
inset-block-start: calc(var(--fab-offset) + env(safe-area-inset-top, 0px));
|
||
|
|
inset-inline-end: calc(var(--fab-offset) + env(safe-area-inset-right, 0px));
|
||
|
|
}
|
||
|
|
[data-fab][data-placement='top-start'] {
|
||
|
|
inset-block-start: calc(var(--fab-offset) + env(safe-area-inset-top, 0px));
|
||
|
|
inset-inline-start: calc(var(--fab-offset) + env(safe-area-inset-left, 0px));
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (prefers-reduced-motion: reduce) {
|
||
|
|
[data-button][data-fab]:hover:not([data-disabled]):not(:active) {
|
||
|
|
translate: none;
|
||
|
|
}
|
||
|
|
}
|