Files
Andreas Reinhold / reiniandClaude Opus 5 8dd0a7ef9d Draw the button group without Tailwind
Plan step 36. <x-button-group> renders data-md-button-group (standard or
connected), data-md-size, data-md-shape and, with a selection,
data-md-selection and data-md-selection-required. button-group.css now
also draws the standard group: its gaps per size, the square corner scale
it hands its buttons, and the press expansion that widens a pressed label
button while its neighbours give way. groups.css keeps only the split
button until that is rewritten too.

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

170 lines
8.0 KiB
CSS

/*
* <x-button-group>: related buttons in a row, and the shapes a button takes from its place among
* its siblings. The connected form is also what `<x-group>` draws its segments with, which is why
* its stylesheet imports this one. A group never wraps: M3's rule, and the press expansion only
* reaches a neighbour on the same line anyway.
*
* Standard (`data-md-button-group="standard"`): the buttons stand apart — 18/12/8/8/8px by size
* (ButtonGroupXSmall…XLargeTokens.BetweenSpace) — and 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, 4/6/8/16/
* 20px, on the button's own padding of 16/16/24/48/64px). Icon buttons keep their width. A square
* standard group has no inner edges, so it hands its buttons the square corner scale
* `<x-button shape="square">` draws (12/12/16/28/28px, 8/8/12/16/16px under the finger), and a
* selected button in it turns round.
*
* Connected (`data-md-button-group="connected"`): 2px apart, the outer corners full, the inner
* corners small, smaller still while pressed, and a selected segment (`aria-pressed`, or a checked
* input inside it) fully round — ConnectedButtonGroupXSmall…XLargeTokens and the M3 Expressive
* connected button group spec: inner corners 4/8/8/16/20px, pressed 2/4/4/12/16px. At the two
* smallest sizes a segment is 32px or 40px tall, and M3 names both numbers it must not go below:
* "XS and S connected button groups have a 48dp target area and a 48dp minimum width".
*
* Shape (`data-md-shape="square"`, M3's "Default shape | Round, square" configuration):
* `--md-button-group-outer` is what the two ends of a group round to — half the height for a round
* group, and for a square one the corner its inner edges take, which is the square table M3
* publishes for connected groups (4/8/8/16/20dp by size). Selection morphs the other way: M3 has a
* toggle inside a group "swap shape square/round on selection", so a selected segment in a square
* group turns round, which is why the selected rule keeps `--md-button-group-full`.
*
* "Full" is half the size's height (16/20/28/48/68px), 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.
*
* <x-button>'s own corner rules carry no specificity, so these reshape the buttons in a group
* whatever selector they use; a pressed or selected segment changes one custom property and the
* outer corners stay put.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@layer material.components {
[data-md-button-group] {
--md-button-group-inner: var(--md-sys-shape-corner-sm);
--md-button-group-inner-pressed: var(--md-sys-shape-corner-xs);
--md-button-group-full: 20px;
--md-button-group-square: var(--md-sys-shape-corner-md);
--md-button-group-square-pressed: var(--md-sys-shape-corner-sm);
--md-button-group-outer: var(--md-button-group-full);
--md-button-group-pad: var(--md-sys-measurement-space200);
--md-button-group-grow: 6px;
display: inline-flex;
align-items: center;
}
[data-md-button-group][data-md-size='xs'] {
--md-button-group-inner: var(--md-sys-shape-corner-xs);
--md-button-group-inner-pressed: 2px;
--md-button-group-full: 16px;
--md-button-group-grow: 4px;
}
[data-md-button-group][data-md-size='md'] {
--md-button-group-full: 28px;
--md-button-group-square: var(--md-sys-shape-corner-lg);
--md-button-group-square-pressed: var(--md-sys-shape-corner-md);
--md-button-group-pad: var(--md-sys-measurement-space300);
--md-button-group-grow: 8px;
}
[data-md-button-group][data-md-size='lg'] {
--md-button-group-inner: var(--md-sys-shape-corner-lg);
--md-button-group-inner-pressed: var(--md-sys-shape-corner-md);
--md-button-group-full: 48px;
--md-button-group-square: var(--md-sys-shape-corner-xl);
--md-button-group-square-pressed: var(--md-sys-shape-corner-lg);
--md-button-group-pad: var(--md-sys-measurement-space600);
--md-button-group-grow: 16px;
}
[data-md-button-group][data-md-size='xl'] {
--md-button-group-inner: var(--md-sys-shape-corner-lg-increased);
--md-button-group-inner-pressed: var(--md-sys-shape-corner-lg);
--md-button-group-full: 68px;
--md-button-group-square: var(--md-sys-shape-corner-xl);
--md-button-group-square-pressed: var(--md-sys-shape-corner-lg);
--md-button-group-pad: var(--md-sys-measurement-space800);
--md-button-group-grow: 20px;
}
[data-md-button-group='standard'] {
gap: var(--md-sys-measurement-space100);
}
[data-md-button-group='standard'][data-md-size='xs'] {
gap: 18px;
}
[data-md-button-group='standard'][data-md-size='sm'] {
gap: 12px;
}
/* A square standard group hands its buttons the square corner scale; a selected one rounds. */
[data-md-button-group='standard'][data-md-shape='square'] > * {
border-radius: var(--md-button-group-square);
}
[data-md-button-group='standard'][data-md-shape='square'] > :active:not(:disabled, [aria-disabled='true']) {
border-radius: var(--md-button-group-square-pressed);
}
[data-md-button-group='standard'][data-md-shape='square'] > [aria-pressed='true'] {
border-radius: var(--md-button-group-full);
}
/* Press expansion: the pressed label button grows, the label buttons beside it give way. */
[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true']) {
padding-inline: calc(var(--md-button-group-pad) + var(--md-button-group-grow));
}
[data-md-button-group='standard'] > :not([data-md-icon-button]):has(+ :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true'])) {
padding-inline-end: calc(var(--md-button-group-pad) - var(--md-button-group-grow));
}
[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true']) + :not([data-md-icon-button]) {
padding-inline-start: calc(var(--md-button-group-pad) - var(--md-button-group-grow));
}
/* A square group's ends take the corner its inner edges take. */
[data-md-button-group][data-md-shape='square'] {
--md-button-group-outer: var(--md-button-group-inner);
}
[data-md-button-group='connected'] {
gap: var(--md-sys-measurement-space25);
}
[data-md-button-group='connected']:is([data-md-size='xs'], [data-md-size='sm']) > * {
min-inline-size: var(--md-sys-measurement-space600);
}
[data-md-button-group='connected'] > * {
--md-button-group-corner: var(--md-button-group-inner);
border-start-start-radius: var(--md-button-group-corner);
border-end-start-radius: var(--md-button-group-corner);
border-start-end-radius: var(--md-button-group-corner);
border-end-end-radius: var(--md-button-group-corner);
}
[data-md-button-group='connected'] > :active {
--md-button-group-corner: var(--md-button-group-inner-pressed);
}
[data-md-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)) {
--md-button-group-corner: var(--md-button-group-full);
}
[data-md-button-group='connected'] > :first-child {
border-start-start-radius: var(--md-button-group-outer);
border-end-start-radius: var(--md-button-group-outer);
}
[data-md-button-group='connected'] > :last-child {
border-start-end-radius: var(--md-button-group-outer);
border-end-end-radius: var(--md-button-group-outer);
}
}