From d560e7449cb5ffecd7634ac1f597f3b149ed07f5 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold Date: Mon, 14 Sep 2026 05:58:13 +0200 Subject: [PATCH] Put the split button on its own geometry 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) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/components/groups.css | 44 ++++++++++++++++--- .../views/components/split-button.blade.php | 6 ++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/resources/css/components/groups.css b/resources/css/components/groups.css index b556acd3..1251eb31 100644 --- a/resources/css/components/groups.css +++ b/resources/css/components/groups.css @@ -13,8 +13,15 @@ * 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 (``): the same idea for two halves; the trailing half turns - * round and its chevron turns over while its menu is open (SplitButton*Tokens). + * 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 @@ -47,6 +54,12 @@ 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 @@ -65,18 +78,28 @@ */ [data-button-group='connected'] > *, [data-split] { - --group-corner: var(--group-inner); 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'] > :active, -[data-split]:active { +[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); @@ -94,8 +117,17 @@ 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 { - transition: rotate var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast); + 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 { diff --git a/resources/views/components/split-button.blade.php b/resources/views/components/split-button.blade.php index 13f2ce37..ff430b88 100644 --- a/resources/views/components/split-button.blade.php +++ b/resources/views/components/split-button.blade.php @@ -13,7 +13,11 @@ Padding from SplitButton*Tokens (androidx Compose Material 3, Apache-2.0): the leading button keeps less room on its inner side at the two smallest sizes, and the trailing button is 48px - wide there. The corners are resources/css/components/groups.css. --}} + wide there. resources/css/components/groups.css carries the rest of the spec, which is all + geometry: the inner corners (4/4/4/8/12px, growing under the finger, not shrinking as a + connected group's do), the trailing icon's own size (22/22/26/38/50px, which the icon-button + table has no entry for), the 1/1/2/3/6px nudge that sits the chevron towards the leading half, + and the standard motion scheme M3 asks for while it turns over. --}} @props([ 'label' => null,