Plan step 36. <x-group> renders data-md-group with its size, variant, shape, multiple and inline, a data-md-button-group row and data-md-group-segment labels over its radios or checkboxes. group.css draws the segments as M3's buttons with the toggle button's colours from :has(:checked), the disabled treatment from :has(:disabled), the focus ring from :has(:focus-visible) and the 48px target at xs and sm. The connected shapes move into button-group.css (the corner scale, square ends, the pressed and selected inner corners), which group.css imports; <x-button-group> adopts them in its own rewrite. The group's render tests move to GroupTest; AppBarTest (navigation group) follows the new legend and row hooks of theme-toggle's rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
242 lines
9.0 KiB
CSS
242 lines
9.0 KiB
CSS
/*
|
|
* <x-group>: a choice of a few options drawn as M3 Expressive's connected button group, the
|
|
* successor of the segmented button — native radios (checkboxes with `data-md-multiple`) under
|
|
* segments that share the row, or hug their labels with `data-md-inline`.
|
|
*
|
|
* The segments are M3's buttons at the group's `data-md-size` (Button*Tokens: 32/40/56/96/136px
|
|
* tall, 16/16/24/48/64px either side, 8/8/8/12/16px between icon and label, label-large, label-
|
|
* large, title-medium, headline-small, headline-large), with the connected shape from
|
|
* button-group.css: 2px apart, small inner corners, the chosen segment fully round. At `xs` and
|
|
* `sm` a segment carries M3's 48px target and a 48px minimum width, which M3 asks for by name.
|
|
*
|
|
* `data-md-variant` is the toggle button's colours: `tonal` (secondary-container, chosen in
|
|
* secondary), `filled` (surface-container behind on-surface-variant, chosen in primary) or
|
|
* `outlined` (an outline-variant edge, chosen in inverse-surface). A checked input picks the
|
|
* chosen colours (`:has(:checked)`), a disabled one the disabled treatment — on-surface at 10%
|
|
* behind 38% text — and keyboard focus on the input draws M3's focus ring around its segment.
|
|
* Corners move on the fast spatial spring and colours on the fast effects spring beside it.
|
|
*
|
|
* The legend is label-large and the hint body-small, both in on-surface-variant; a validation
|
|
* message takes the hint's place in error. `hint-class` lands on the hint, where a caller's class
|
|
* outranks the colour here.
|
|
*/
|
|
|
|
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
|
|
|
@import './icon.css';
|
|
@import './button-group.css';
|
|
|
|
@layer material.components {
|
|
[data-md-group] {
|
|
min-inline-size: 0;
|
|
}
|
|
|
|
[data-md-group-legend] {
|
|
margin-block-end: var(--md-sys-measurement-space100);
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
font: var(--md-sys-typescale-label-lg);
|
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group] > [data-md-button-group] {
|
|
display: flex;
|
|
align-items: normal;
|
|
inline-size: 100%;
|
|
}
|
|
|
|
[data-md-group][data-md-inline] > [data-md-button-group] {
|
|
inline-size: fit-content;
|
|
}
|
|
|
|
[data-md-group-segment] {
|
|
--md-group-container: var(--md-sys-color-secondary-container);
|
|
--md-group-label: var(--md-sys-color-on-secondary-container);
|
|
|
|
position: relative;
|
|
isolation: isolate;
|
|
display: flex;
|
|
flex: 1 1 0%;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-inline-size: 0;
|
|
background-color: var(--md-group-container);
|
|
color: var(--md-group-label);
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
transition-property: border-radius, padding, margin, bottom, background-color, color, box-shadow;
|
|
transition-duration:
|
|
var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-duration),
|
|
var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-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-fast), var(--md-sys-motion-spatial-fast),
|
|
var(--md-sys-motion-spatial-fast), var(--md-sys-motion-spatial-fast),
|
|
var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast);
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: -1;
|
|
border-radius: inherit;
|
|
background-color: currentColor;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
&:hover::before {
|
|
opacity: var(--md-sys-state-hover-state-layer-opacity);
|
|
}
|
|
}
|
|
|
|
&:has(:focus-visible) {
|
|
outline: 3px solid var(--md-sys-color-secondary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
&:active::before {
|
|
opacity: var(--md-sys-state-pressed-state-layer-opacity);
|
|
}
|
|
|
|
/* The radio or checkbox stays in the page, for the form, the keyboard and a screen reader. */
|
|
& > input {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
[data-md-group][data-md-inline] [data-md-group-segment] {
|
|
flex: initial;
|
|
}
|
|
|
|
[data-md-group]:is([data-md-size='xs'], [data-md-size='sm']) [data-md-group-segment] {
|
|
min-inline-size: var(--md-sys-measurement-space600);
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 100%;
|
|
height: 100%;
|
|
min-width: var(--md-sys-measurement-space600);
|
|
min-height: var(--md-sys-measurement-space600);
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
}
|
|
|
|
[data-md-group][data-md-size='xs'] [data-md-group-segment] {
|
|
gap: var(--md-sys-measurement-space100);
|
|
block-size: 32px;
|
|
padding-inline: var(--md-sys-measurement-space200);
|
|
font: var(--md-sys-typescale-label-lg);
|
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group][data-md-size='sm'] [data-md-group-segment] {
|
|
gap: var(--md-sys-measurement-space100);
|
|
block-size: 40px;
|
|
padding-inline: var(--md-sys-measurement-space200);
|
|
font: var(--md-sys-typescale-label-lg);
|
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group][data-md-size='md'] [data-md-group-segment] {
|
|
gap: var(--md-sys-measurement-space100);
|
|
block-size: 56px;
|
|
padding-inline: var(--md-sys-measurement-space300);
|
|
font: var(--md-sys-typescale-title-md);
|
|
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group][data-md-size='lg'] [data-md-group-segment] {
|
|
gap: 12px;
|
|
block-size: 96px;
|
|
padding-inline: var(--md-sys-measurement-space600);
|
|
font: var(--md-sys-typescale-headline-sm);
|
|
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group][data-md-size='xl'] [data-md-group-segment] {
|
|
gap: var(--md-sys-measurement-space200);
|
|
block-size: 136px;
|
|
padding-inline: var(--md-sys-measurement-space800);
|
|
font: var(--md-sys-typescale-headline-lg);
|
|
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group-segment]:has(:checked) {
|
|
--md-group-container: var(--md-sys-color-secondary);
|
|
--md-group-label: var(--md-sys-color-on-secondary);
|
|
}
|
|
|
|
[data-md-group][data-md-variant='filled'] [data-md-group-segment] {
|
|
--md-group-container: var(--md-sys-color-surface-container);
|
|
--md-group-label: var(--md-sys-color-on-surface-variant);
|
|
|
|
&:has(:checked) {
|
|
--md-group-container: var(--md-sys-color-primary);
|
|
--md-group-label: var(--md-sys-color-on-primary);
|
|
}
|
|
}
|
|
|
|
[data-md-group][data-md-variant='outlined'] [data-md-group-segment] {
|
|
--md-group-container: transparent;
|
|
--md-group-label: var(--md-sys-color-on-surface-variant);
|
|
|
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
|
|
&:has(:checked) {
|
|
--md-group-container: var(--md-sys-color-inverse-surface);
|
|
--md-group-label: var(--md-sys-color-inverse-on-surface);
|
|
|
|
border-color: transparent;
|
|
}
|
|
}
|
|
|
|
[data-md-group-segment]:has(:disabled) {
|
|
background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
|
|
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
[data-md-group-label] {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
[data-md-group-hint],
|
|
[data-md-group-error] {
|
|
margin-block-start: var(--md-sys-measurement-space50);
|
|
font: var(--md-sys-typescale-body-sm);
|
|
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
|
|
font-variation-settings: normal;
|
|
}
|
|
|
|
[data-md-group-hint] {
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
}
|
|
|
|
[data-md-group-error] {
|
|
color: var(--md-sys-color-error);
|
|
}
|
|
}
|