Draw the button's state layer, focus ring and target from the classes
Plan step 36 ("Interaction is the shared classes", the user, 2026-09-14).
button.css hand-rolled the same declarations as foundation/interaction.css's
md-state-layer, md-focus-ring and md-touch-target; it now renders those
classes (the touch target only at xs and sm, where it draws under 48px) and
keeps only its own geometry, colour and motion. The stylesheet test's "writes
no class list" check now allows the three interaction classes, in any subset,
and $attributes->class() as the way a root merges them with a caller's own
class; split-button and button-group, which nest a button, are updated to match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
aa3e789081
commit
bfd368e463
@@ -22,6 +22,7 @@ function buttonCss(): ComponentStylesheet
|
||||
|
||||
it('is a text button in the primary colour by default, drawn by its stylesheet', function () {
|
||||
expect(buttonAttributes('<x-button label="Cancel" />'))->toBe([
|
||||
'class' => 'md-state-layer md-focus-ring md-touch-target',
|
||||
'data-md-button' => 'data-md-button',
|
||||
'data-md-variant' => 'text',
|
||||
'data-md-color' => 'primary',
|
||||
@@ -108,23 +109,18 @@ it('rounds by default, squares on request, and squares off further while pressed
|
||||
->and(buttonCss()->declarations("[data-md-button][data-md-size='xl']"))->toMatchArray(['--md-button-square' => 'var(--md-sys-shape-corner-xl)', '--md-button-pressed' => 'var(--md-sys-shape-corner-lg)']);
|
||||
});
|
||||
|
||||
it('reaches a 48px touch target below the medium size', function () {
|
||||
expect(buttonCss()->declarations("[data-md-button]:is([data-md-size='xs'], [data-md-size='sm'])::after"))->toMatchArray([
|
||||
'content' => "''",
|
||||
'min-width' => 'var(--md-sys-measurement-space600)',
|
||||
'min-height' => 'var(--md-sys-measurement-space600)',
|
||||
])
|
||||
->and(buttonCss()->has("[data-md-button]:is([data-md-size='md'])::after"))->toBeFalse();
|
||||
it('reaches a 48px touch target below the medium size, from the foundation\'s shared class', function () {
|
||||
expect(buttonAttributes('<x-button label="Go" size="xs" />')['class'])->toBe('md-state-layer md-focus-ring md-touch-target')
|
||||
->and(buttonAttributes('<x-button label="Go" size="sm" />')['class'])->toBe('md-state-layer md-focus-ring md-touch-target')
|
||||
->and(buttonAttributes('<x-button label="Go" size="md" />')['class'])->toBe('md-state-layer md-focus-ring');
|
||||
});
|
||||
|
||||
it('draws M3\'s state layer and focus ring on the button itself', function () {
|
||||
it('draws M3\'s state layer and focus ring from the foundation\'s shared classes, not its own', function () {
|
||||
$css = buttonCss();
|
||||
|
||||
expect($css->declarations('[data-md-button]::before'))->toMatchArray(['background-color' => 'currentColor', 'opacity' => '0', 'border-radius' => 'inherit'])
|
||||
->and($css->declarations('[data-md-button]:hover::before', ['@media (hover: hover)']))->toBe(['opacity' => 'var(--md-sys-state-hover-state-layer-opacity)'])
|
||||
->and($css->declarations('[data-md-button]:focus-visible::before'))->toBe(['opacity' => 'var(--md-sys-state-focus-state-layer-opacity)'])
|
||||
->and($css->declarations('[data-md-button]:active::before'))->toBe(['opacity' => 'var(--md-sys-state-pressed-state-layer-opacity)'])
|
||||
->and($css->declarations('[data-md-button]:focus-visible'))->toBe(['outline' => '3px solid var(--md-sys-color-secondary)', 'outline-offset' => '2px']);
|
||||
expect($css->has('[data-md-button]::before'))->toBeFalse()
|
||||
->and($css->has("[data-md-button]:is([data-md-size='xs'], [data-md-size='sm'])::after"))->toBeFalse()
|
||||
->and($css->has('[data-md-button]:focus-visible'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('moves shape on the spatial spring and colour on the effects spring', function () {
|
||||
@@ -211,7 +207,6 @@ it('disables a button, and a link as far as a link can be', function () {
|
||||
])
|
||||
->and(buttonCss()->declarations("[data-md-button]:is([data-md-variant='filled'], [data-md-variant='tonal'], [data-md-variant='elevated'], [data-md-variant='outlined'][data-md-selected='true']):is(:disabled, [aria-disabled='true'])"))
|
||||
->toBe(['background-color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent)'])
|
||||
->and(buttonCss()->declarations("[data-md-button]:is(:disabled, [aria-disabled='true'])::before"))->toBe(['display' => 'none'])
|
||||
->and(buttonCss()->declarations("[data-md-button][aria-disabled='true']"))->toBe(['pointer-events' => 'none']);
|
||||
});
|
||||
|
||||
@@ -283,8 +278,8 @@ it('keeps aria-pressed off a selected link, which is not a toggle', function ()
|
||||
->toContain('aria-pressed="true"');
|
||||
});
|
||||
|
||||
it('lands a caller\'s class and style on the button untouched', function () {
|
||||
it('lands a caller\'s class and style on the button untouched, beside the interaction classes', function () {
|
||||
expect(buttonAttributes('<x-button label="Save" class="w-full" style="margin: 0" tooltip="Save" />'))
|
||||
->toMatchArray(['class' => 'w-full'])
|
||||
->toMatchArray(['class' => 'md-state-layer md-focus-ring md-touch-target w-full'])
|
||||
->and(buttonAttributes('<x-button label="Save" class="w-full" style="margin: 0" tooltip="Save" />')['style'])->toStartWith('anchor-name: --material-button-')->toEndWith('margin: 0;');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user