diff --git a/resources/css/components.css b/resources/css/components.css index be9ff2be..c1fa3021 100644 --- a/resources/css/components.css +++ b/resources/css/components.css @@ -80,3 +80,4 @@ @import './components/navigation-rail-section.css'; @import './components/section-nav.css'; @import './components/account-menu.css'; +@import './components/theme-toggle.css'; diff --git a/resources/css/components/theme-toggle.css b/resources/css/components/theme-toggle.css new file mode 100644 index 00000000..a93d0b15 --- /dev/null +++ b/resources/css/components/theme-toggle.css @@ -0,0 +1,45 @@ +/* + * : `mode="toggle"|"cycle"` an icon button between light, dark (and system for + * cycle); `mode="picker"|"contrast"` a connected group over native radios (docs/audits/m3-alignment/ + * navigation.md N-04, N-17 — the Expressive-deprecated segmented button's successor, + * ``, already fixes both there). This file only draws the icon button; the group's own + * shape, colour and 48px minimum below `medium` are group.css's. + * + * [data-md-theme-toggle="toggle"|"cycle"] the icon button: 40px reaching 48px through + * md-touch-target (N-01), morphing corner + * [data-md-theme-toggle="picker"|"contrast"] the group's own wrapper (`role="group"`) + * + * The icon button also carries `data-md-icon-button`, the hook `resources/css/components/ + * toolbar.css` matches generically (not `[data-md-button][data-md-icon-button]`, as button.css + * requires): a theme toggle placed in a toolbar's `actions` takes the toolbar's own icon colour + * (N-13) the same way one of its `` children would. + * + * The corner morphs from full to `sm` while pressed on the fast spatial spring — M3 Expressive's + * pressed-state shape change, the same motion ``'s own icon buttons use + * (button.css, `:active`). + */ + +@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; + +@import './group.css'; +@import './icon.css'; + +@layer material.components { + [data-md-theme-toggle='toggle'], + [data-md-theme-toggle='cycle'] { + display: inline-flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + inline-size: var(--md-sys-measurement-space500); + block-size: var(--md-sys-measurement-space500); + border-radius: var(--md-sys-shape-corner-full); + cursor: pointer; + color: var(--md-sys-color-on-surface-variant); + transition: border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast); + + &:active { + border-radius: var(--md-sys-shape-corner-sm); + } + } +} diff --git a/resources/views/components/theme-toggle.blade.php b/resources/views/components/theme-toggle.blade.php index 5ae93846..6e310c27 100644 --- a/resources/views/components/theme-toggle.blade.php +++ b/resources/views/components/theme-toggle.blade.php @@ -61,7 +61,7 @@
class(['state-layer touch-target focus-ring inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-corner-full text-on-surface-variant transition-[border-radius] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast active:rounded-corner-sm']) }} + {{ $attributes->class(['md-state-layer', 'md-touch-target', 'md-focus-ring']) }} > @if ($mode === 'cycle') diff --git a/tests/Feature/Components/AppBarTest.php b/tests/Feature/Components/AppBarTest.php index b86edc76..f0c596ca 100644 --- a/tests/Feature/Components/AppBarTest.php +++ b/tests/Feature/Components/AppBarTest.php @@ -218,7 +218,7 @@ 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('')) - ->toContain('data-theme-toggle="contrast"') + ->toContain('data-md-theme-toggle="contrast"') ->toContain('aria-label="Contrast"') // A connected button group over native radios, not the deprecated segmented button (N-04). ->toContain('data-md-button-group="connected"') @@ -242,20 +242,30 @@ it('names a theme row for a screen reader and legends it on request', function ( it('switches the theme through the store in three shapes', function () { expect((string) $this->blade('')) - ->toContain('data-theme-toggle="toggle"') + ->toContain('data-md-theme-toggle="toggle"') + ->toContain('data-md-icon-button') ->toContain('aria-label="Dark theme"') // 40px drawn, 48px reached: M3's minimum target (N-01). - ->toContain('touch-target') + ->toContain('md-touch-target') ->toContain('$store.theme.toggle()') ->and((string) $this->blade('')) - ->toContain('data-theme-toggle="cycle"') + ->toContain('data-md-theme-toggle="cycle"') ->toContain("{ light: 'dark', dark: 'system', system: 'light' }") ->and((string) $this->blade('')) - ->toContain('data-theme-toggle="picker"') + ->toContain('data-md-theme-toggle="picker"') ->toContain('x-model="chosen"') ->toContain('name="material-theme"') ->toContain('value="system"') ->toContain('$store.theme.set(value)'); + + $css = ComponentStylesheet::read('theme-toggle'); + + // The corner morphs from full to sm while pressed, the same motion 's own icon + // buttons take (button.css), on the fast spatial spring. + expect($css->declarations("[data-md-theme-toggle='toggle'], [data-md-theme-toggle='cycle']")) + ->toHaveKey('border-radius', 'var(--md-sys-shape-corner-full)') + ->and($css->declarations("[data-md-theme-toggle='toggle']:active, [data-md-theme-toggle='cycle']:active")) + ->toBe(['border-radius' => 'var(--md-sys-shape-corner-sm)']); }); it('opens an account menu from the initials of a name', function () { diff --git a/tests/Feature/Components/NavigationStylesheetsTest.php b/tests/Feature/Components/NavigationStylesheetsTest.php index 84948cb2..0df9c26f 100644 --- a/tests/Feature/Components/NavigationStylesheetsTest.php +++ b/tests/Feature/Components/NavigationStylesheetsTest.php @@ -34,6 +34,7 @@ dataset('navigation components', [ 'navigation-rail-section', 'section-nav', 'account-menu', + 'theme-toggle', ]); /**