It borrowed the connected group's corner variables, so its inner corners were wrong at four of five sizes and shrank under the finger where SplitButton*Tokens grows them; its chevron took the icon-button table's size at every step, sat dead centre, and turned over on the expressive spring. groups.css now carries --split-inner, --split-icon and --split-nudge per size, and the rotation uses M3's standard scheme. Plan step 18, actions.md ACT-06, ACT-07, ACT-29, ACT-30. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
136 lines
6.6 KiB
CSS
136 lines
6.6 KiB
CSS
/*
|
|
* Button groups and split buttons: shapes that depend on a button's place among its siblings.
|
|
*
|
|
* Unlayered on purpose. Each <x-button> draws its own corners and padding as utilities, and a
|
|
* rule in any @layer loses to a utility whatever its specificity; the group has to win.
|
|
*
|
|
* Standard group (`<x-button-group>`): pressing a label button widens it and narrows its
|
|
* neighbours by the same amount, so the row keeps its length — Expressive's press expansion
|
|
* (ButtonGroupDefaults.ExpandedRatio is 15%; this uses a fixed step per size). Icon buttons keep
|
|
* their width.
|
|
*
|
|
* Connected group (`<x-button-group connected>`, `<x-group>`): 2px apart, the outer corners
|
|
* full, the inner corners small, smaller still while pressed, and a selected segment fully round
|
|
* (ConnectedButtonGroupSmallTokens and the M3 Expressive connected button group spec).
|
|
*
|
|
* Split button (`<x-split-button>`): the same idea for two halves, on its own corner scale —
|
|
* SplitButton*Tokens gives the inner corner 4/4/4/8/12px at rest and *grows* it to 8/12/12/20/20px
|
|
* under the finger, the opposite of a connected group, so `--split-inner*` is separate from
|
|
* `--group-inner*`. The trailing half turns round and its chevron turns over while its menu is
|
|
* open, on M3's standard scheme rather than the expressive springs (the site: "the trailing
|
|
* button uses the standard motion scheme, not expressive, when rotating"), and the chevron sits
|
|
* a little towards the leading half so the pair reads as one control (`--split-nudge`:
|
|
* 1/1/2/3/6px). `--split-icon` is the trailing icon's own size, 22/22/26/38/50px, which the
|
|
* icon-button table does not know about.
|
|
*
|
|
* "Full" here is half the size's height (`--group-full`), never `--md-sys-shape-corner-full`'s
|
|
* 9999px. One element mixes full outer corners with small inner ones, and when a box's radii
|
|
* add up to more than its side, CSS scales every radius by the same factor: a 9999px corner
|
|
* beside an 8px one shrank the 8px one to a hundredth of a pixel, so the inner corners drew
|
|
* square.
|
|
*/
|
|
|
|
[data-button-group] {
|
|
--group-inner: var(--md-sys-shape-corner-sm);
|
|
--group-inner-pressed: var(--md-sys-shape-corner-xs);
|
|
--group-full: 1.25rem;
|
|
}
|
|
|
|
[data-button-group][data-size='xs'] { --group-pad: 0.75rem; --group-grow: 4px; --group-inner: var(--md-sys-shape-corner-xs); --group-inner-pressed: 2px; --group-full: 1rem; }
|
|
[data-button-group][data-size='sm'] { --group-pad: 1rem; --group-grow: 6px; --group-full: 1.25rem; }
|
|
[data-button-group][data-size='md'] { --group-pad: 1.5rem; --group-grow: 8px; --group-full: 1.75rem; }
|
|
[data-button-group][data-size='lg'] { --group-pad: 3rem; --group-grow: 16px; --group-inner: var(--md-sys-shape-corner-lg); --group-inner-pressed: var(--md-sys-shape-corner-md); --group-full: 3rem; }
|
|
[data-button-group][data-size='xl'] { --group-pad: 4rem; --group-grow: 20px; --group-inner: var(--md-sys-shape-corner-lg-increased); --group-inner-pressed: var(--md-sys-shape-corner-lg); --group-full: 4.25rem; }
|
|
|
|
[data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) {
|
|
padding-inline: calc(var(--group-pad) + var(--group-grow));
|
|
}
|
|
|
|
[data-button-group='standard'] > :not([data-icon-button]):has(+ :not([data-icon-button]):active:not(:disabled, [aria-disabled='true'])) {
|
|
padding-inline-end: calc(var(--group-pad) - var(--group-grow));
|
|
}
|
|
|
|
[data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) + :not([data-icon-button]) {
|
|
padding-inline-start: calc(var(--group-pad) - var(--group-grow));
|
|
}
|
|
|
|
[data-button-group='split'] { --split-inner: var(--md-sys-shape-corner-xs); --split-inner-pressed: var(--md-sys-shape-corner-md); --split-icon: 22px; --split-nudge: 1px; }
|
|
[data-button-group='split'][data-size='xs'] { --split-inner-pressed: var(--md-sys-shape-corner-sm); }
|
|
[data-button-group='split'][data-size='md'] { --split-icon: 26px; --split-nudge: 2px; }
|
|
[data-button-group='split'][data-size='lg'] { --split-inner: var(--md-sys-shape-corner-sm); --split-inner-pressed: var(--md-sys-shape-corner-lg-increased); --split-icon: 38px; --split-nudge: 3px; }
|
|
[data-button-group='split'][data-size='xl'] { --split-inner: var(--md-sys-shape-corner-md); --split-inner-pressed: var(--md-sys-shape-corner-lg-increased); --split-icon: 50px; --split-nudge: 6px; }
|
|
|
|
/*
|
|
* A connected segment at the two smallest sizes is 32px or 40px tall, and an icon-only one is as
|
|
* narrow as it is tall. M3 names both numbers and tells you not to reduce them: "XS and S
|
|
* connected button groups have a 48dp target area and a 48dp minimum width". The target is the
|
|
* `touch-target` utility on the segment itself; the width is here, where a group can reach the
|
|
* `<x-button>`s a caller put inside it.
|
|
*/
|
|
[data-button-group='connected'][data-size='xs'] > *,
|
|
[data-button-group='connected'][data-size='sm'] > * {
|
|
min-inline-size: 3rem;
|
|
}
|
|
|
|
/*
|
|
* Connected segments and split halves take their inner corner from `--group-corner`, so pressing
|
|
* or selecting changes one variable and the rounded outer corners stay put.
|
|
*/
|
|
[data-button-group='connected'] > *,
|
|
[data-split] {
|
|
border-start-start-radius: var(--group-corner);
|
|
border-end-start-radius: var(--group-corner);
|
|
border-start-end-radius: var(--group-corner);
|
|
border-end-end-radius: var(--group-corner);
|
|
}
|
|
|
|
[data-button-group='connected'] > * {
|
|
--group-corner: var(--group-inner);
|
|
}
|
|
|
|
[data-button-group='connected'] > :active {
|
|
--group-corner: var(--group-inner-pressed);
|
|
}
|
|
|
|
[data-split] {
|
|
--group-corner: var(--split-inner);
|
|
}
|
|
|
|
[data-split]:active {
|
|
--group-corner: var(--split-inner-pressed);
|
|
}
|
|
|
|
[data-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)),
|
|
[data-split='trailing'][aria-expanded='true'] {
|
|
--group-corner: var(--group-full);
|
|
}
|
|
|
|
[data-button-group='connected'] > :first-child,
|
|
[data-split='leading'] {
|
|
border-start-start-radius: var(--group-full);
|
|
border-end-start-radius: var(--group-full);
|
|
}
|
|
|
|
[data-button-group='connected'] > :last-child,
|
|
[data-split='trailing'] {
|
|
border-start-end-radius: var(--group-full);
|
|
border-end-end-radius: var(--group-full);
|
|
}
|
|
|
|
/*
|
|
* The trailing chevron: its own size, and `--split-nudge` off centre towards the leading half —
|
|
* equal and opposite margins move it by exactly that much inside a centred flex row, in whichever
|
|
* direction the writing mode runs.
|
|
*/
|
|
[data-split='trailing'] svg {
|
|
width: var(--split-icon);
|
|
height: var(--split-icon);
|
|
margin-inline-start: calc(-1 * var(--split-nudge));
|
|
margin-inline-end: var(--split-nudge);
|
|
transition: rotate var(--md-sys-motion-duration-short) var(--md-sys-motion-easing-standard);
|
|
}
|
|
|
|
[data-split='trailing'][aria-expanded='true'] svg {
|
|
rotate: 180deg;
|
|
}
|