Build the theme and contrast rows on the connected button group
`mode="picker"` hand-rolled an outlined segmented button, point for point the design M3 Expressive deprecates, and its own arrow keys neither wrapped nor handled Up/Down. Both rows are now <x-group>: native radios in a fieldset, so the browser supplies the radiogroup, both axes, the wrap and the roving tab stop, and the store is bound through an x-model accessor — a contrast row still marks the resolved level but writes through setContrast(). A new `label` prop legends the row. Plan: docs/plans/material-3-alignment.md, step 14 (navigation N-04, N-17). 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
98afef7b45
commit
a99fbd3794
@@ -120,14 +120,14 @@ it('moves focus along a toolbar with the arrow keys', function () {
|
||||
|
||||
it('switches the theme from a toggle, a cycle and a picker', function () {
|
||||
$page = barsProbe()
|
||||
->click('[data-theme-option="light"]')
|
||||
->click('label:has(input[name="material-theme"][value="light"])')
|
||||
->assertScript("document.documentElement.dataset.theme === 'light'")
|
||||
->assertAttribute('#toggle', 'aria-pressed', 'false');
|
||||
|
||||
$page->click('#toggle')
|
||||
->assertScript("document.documentElement.dataset.theme === 'dark'")
|
||||
->assertAttribute('#toggle', 'aria-pressed', 'true')
|
||||
->assertAttribute('[data-theme-option="dark"]', 'aria-checked', 'true');
|
||||
->assertScript('document.querySelector(\'input[name="material-theme"][value="dark"]\').checked');
|
||||
|
||||
$page->click('#cycle')
|
||||
->assertScript("document.documentElement.dataset.themeChoice === 'system'")
|
||||
|
||||
@@ -31,9 +31,9 @@ it('keeps the visitor\'s choice over the operating system', function () {
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'")
|
||||
->assertScript(theme('data-theme', 'dark'));
|
||||
|
||||
$page->click('[data-theme-option="light"]')
|
||||
$page->click('label:has(input[name="material-theme"][value="light"])')
|
||||
->assertScript(theme('data-theme', 'light'))
|
||||
->assertAttribute('[data-theme-option="light"]', 'aria-checked', 'true')
|
||||
->assertScript('document.querySelector(\'input[name="material-theme"][value="light"]\').checked')
|
||||
->assertScript("localStorage.getItem('material-theme') === 'light'");
|
||||
|
||||
$page->refresh()
|
||||
@@ -98,7 +98,7 @@ it('adds a theme-color meta in the painted surface, and follows the theme and th
|
||||
->assertScript(themeColorIs($profiles['baseline']['light']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['baseline']['light']['surface']));
|
||||
|
||||
$page->click('[data-theme-option="dark"]')
|
||||
$page->click('label:has(input[name="material-theme"][value="dark"])')
|
||||
->assertScript(theme('data-theme', 'dark'))
|
||||
->assertScript(themeColorIs($profiles['baseline']['dark']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['baseline']['dark']['surface']));
|
||||
@@ -108,7 +108,7 @@ it('adds a theme-color meta in the painted surface, and follows the theme and th
|
||||
->assertScript(themeColorIs($profiles['rose']['dark']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['rose']['dark']['surface']));
|
||||
|
||||
$page->click('[data-theme-option="light"]')
|
||||
$page->click('label:has(input[name="material-theme"][value="light"])')
|
||||
->assertScript(themeColorIs($profiles['rose']['light']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['rose']['light']['surface']))
|
||||
->assertNoJavaScriptErrors();
|
||||
|
||||
@@ -81,14 +81,25 @@ it('keeps a toolbar at the bottom clear of the navigation bar', function () {
|
||||
it('offers M3\'s three contrast levels, marking the one in force', function () {
|
||||
expect((string) $this->blade('<x-theme-toggle mode="contrast" />'))
|
||||
->toContain('data-theme-toggle="contrast"')
|
||||
->toContain('role="radiogroup"')
|
||||
->toContain('aria-label="Contrast"')
|
||||
->toContain('data-contrast-option="standard"')
|
||||
->toContain('data-contrast-option="medium"')
|
||||
->toContain('data-contrast-option="high"')
|
||||
->toContain("\$store.theme.setContrast('high')")
|
||||
// A connected button group over native radios, not the deprecated segmented button (N-04).
|
||||
->toContain('data-button-group="connected"')
|
||||
->toContain('name="material-contrast"')
|
||||
->toContain('value="standard"')
|
||||
->toContain('value="medium"')
|
||||
->toContain('value="high"')
|
||||
->toContain('$store.theme.setContrast(value)')
|
||||
// The row follows the resolved level, so the operating system's shows under `system`.
|
||||
->toContain("(\$store.theme.resolvedContrast === 'medium').toString()");
|
||||
->toContain('return this.$store.theme.resolvedContrast');
|
||||
});
|
||||
|
||||
it('names a theme row for a screen reader and legends it on request', function () {
|
||||
expect((string) $this->blade('<x-theme-toggle mode="picker" />'))
|
||||
->toContain('aria-label="Theme"')
|
||||
->not->toContain('<legend')
|
||||
->and((string) $this->blade('<x-theme-toggle mode="picker" label="Appearance" />'))
|
||||
->toContain('aria-label="Appearance"')
|
||||
->toContain('<legend class="mb-2 type-label-lg text-on-surface-variant">Appearance</legend>');
|
||||
});
|
||||
|
||||
it('switches the theme through the store in three shapes', function () {
|
||||
@@ -102,9 +113,11 @@ it('switches the theme through the store in three shapes', function () {
|
||||
->toContain('data-theme-toggle="cycle"')
|
||||
->toContain("{ light: 'dark', dark: 'system', system: 'light' }")
|
||||
->and((string) $this->blade('<x-theme-toggle mode="picker" />'))
|
||||
->toContain('role="radiogroup"')
|
||||
->toContain('data-theme-option="system"')
|
||||
->toContain("\$store.theme.set('dark')");
|
||||
->toContain('data-theme-toggle="picker"')
|
||||
->toContain('x-model="chosen"')
|
||||
->toContain('name="material-theme"')
|
||||
->toContain('value="system"')
|
||||
->toContain('$store.theme.set(value)');
|
||||
});
|
||||
|
||||
it('opens an account menu from the initials of a name', function () {
|
||||
|
||||
Reference in New Issue
Block a user