Draw the group segment's state layer from the foundation class

Plan step 36 ("Interaction is the shared classes", the user, 2026-09-14).
A segment's hover and press opacities were group.css's own copy of
md-state-layer's declarations; the label now renders md-state-layer
(and, below medium's two smallest sizes, md-touch-target) and the
stylesheet keeps only what the class cannot draw: focus and disabled read
off the real radio or checkbox inside with :has(), not the label itself,
so the focus ring's layer tint and the disabled hide are kept as a
refinement (the latter a real gap the old rule left open). button-group.css,
which only shapes corners and spacing, needed no change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 18:06:01 +02:00
co-authored by Claude Opus 5
parent ea12f9d75b
commit 7803298fe7
3 changed files with 34 additions and 45 deletions
+13 -8
View File
@@ -16,14 +16,13 @@ it('draws a choice as connected radios', function () {
->toMatch('/<fieldset data-md-group="data-md-group" data-md-size="sm" data-md-variant="tonal" data-md-shape="round">/')
->toContain('<legend data-md-group-legend>Theme</legend>')
->toContain('<div data-md-button-group="connected" data-md-size="sm" data-md-shape="round">')
->toContain('<label data-md-group-segment>')
->toMatch('/<label data-md-group-segment class="md-state-layer\s+md-touch-target\s*">/')
->toContain('type="radio"')
->toContain('name="theme"')
->toContain('x-model="theme"')
->toContain('value="light"')
->toContain('disabled')
->toContain('<span data-md-group-label>Light</span>')
->not->toContain('class=')
->and(substr_count($html, '<svg'))->toBe(1);
});
@@ -37,16 +36,19 @@ it('squares a choice\'s ends, and still rounds the chosen segment', function ()
->and(ComponentStylesheet::read('group')->imports())->toBe(['./icon.css', './button-group.css']);
});
it('keeps a 48px target and a 48px minimum width on the smallest connected segments', function () {
it('keeps a 48px target and a 48px minimum width on the smallest connected segments, from the foundation\'s class', function () {
$css = ComponentStylesheet::read('group');
$options = [['id' => 'a', 'name' => 'A']];
expect($css->declarations("[data-md-group]:is([data-md-size='xs'], [data-md-size='sm']) [data-md-group-segment]"))->toBe(['min-inline-size' => 'var(--md-sys-measurement-space600)'])
->and($css->declarations("[data-md-group]:is([data-md-size='xs'], [data-md-size='sm']) [data-md-group-segment]::after"))->toMatchArray([
'min-width' => 'var(--md-sys-measurement-space600)',
'min-height' => 'var(--md-sys-measurement-space600)',
])
->and($css->has("[data-md-group]:is([data-md-size='xs'], [data-md-size='sm']) [data-md-group-segment]::after"))->toBeFalse()
->and($css->declarations('[data-md-group-segment]'))->toMatchArray(['min-inline-size' => '0'])
->and(ComponentStylesheet::read('button-group')->declarations("[data-md-button-group='connected']:is([data-md-size='xs'], [data-md-size='sm']) > *"))->toBe(['min-inline-size' => 'var(--md-sys-measurement-space600)']);
->and(ComponentStylesheet::read('button-group')->declarations("[data-md-button-group='connected']:is([data-md-size='xs'], [data-md-size='sm']) > *"))->toBe(['min-inline-size' => 'var(--md-sys-measurement-space600)'])
->and((string) $this->blade('<x-group size="xs" name="x" :$options />', ['options' => $options]))
->toMatch('/class="md-state-layer\s+md-touch-target\s*"/')
->and((string) $this->blade('<x-group size="md" name="x" :$options />', ['options' => $options]))
->toMatch('/class="md-state-layer\s*"/')
->not->toContain('md-touch-target');
});
it('sizes its segments as M3\'s buttons', function (string $size, string $height, string $padding, string $type, int $icon) {
@@ -78,7 +80,10 @@ it('draws several choices as checkboxes, in the toggle button\'s colours', funct
->and($css->declarations("[data-md-group][data-md-variant='filled'] [data-md-group-segment]:has(:checked)"))->toBe(['--md-group-container' => 'var(--md-sys-color-primary)', '--md-group-label' => 'var(--md-sys-color-on-primary)'])
->and($css->declarations("[data-md-group][data-md-variant='outlined'] [data-md-group-segment]:has(:checked)"))->toBe(['--md-group-container' => 'var(--md-sys-color-inverse-surface)', '--md-group-label' => 'var(--md-sys-color-inverse-on-surface)', 'border-color' => 'transparent'])
->and($css->declarations('[data-md-group-segment]:has(:disabled)'))->toMatchArray(['cursor' => 'not-allowed'])
->and($css->declarations('[data-md-group-segment]:has(:disabled)::before'))->toBe(['display' => 'none'])
->and($css->declarations('[data-md-group-segment]:has(:focus-visible)'))->toBe(['outline' => '3px solid var(--md-sys-color-secondary)', 'outline-offset' => '2px'])
->and($css->declarations('[data-md-group-segment]:has(:focus-visible)::before'))->toBe(['opacity' => 'var(--md-sys-state-focus-state-layer-opacity)'])
->and($css->has('[data-md-group-segment]::before'))->toBeFalse()
->and((string) $this->blade('<x-group name="x" variant="glass" :options="[]" />'))->toContain('data-md-variant="tonal"');
});