Draw the theme toggle without Tailwind
Plan step 36 (navigation group, third batch): <x-theme-toggle>'s icon button (mode="toggle"|"cycle") moves into resources/css/components/ theme-toggle.css, keyed on data-md-theme-toggle="toggle"|"cycle" and still carrying data-md-icon-button, the hook toolbar.css matches generically so the toggle takes a toolbar's own icon colour when placed in one (N-13). The button renders the shared md-state-layer/ md-touch-target/md-focus-ring classes (N-01's 48px target); its corner morphs from full to sm while pressed on the fast spatial spring, the same motion <x-button>'s own icon buttons take. mode="picker"|"contrast" already drew a connected group over native radios (N-04, N-17's fix), so only its wrapper's hook needed renaming. Hook renamed: data-theme-toggle to data-md-theme-toggle, updated in AppBarTest.php, which carries theme-toggle's render tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f5c90c3874
commit
74cbcd2f44
@@ -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';
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* <x-theme-toggle>: `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,
|
||||
* `<x-group>`, 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 `<x-button icon>` 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 `<x-button>`'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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
<div
|
||||
role="group"
|
||||
aria-label="{{ $label ?? $row['name'] }}"
|
||||
data-theme-toggle="{{ $mode }}"
|
||||
data-md-theme-toggle="{{ $mode }}"
|
||||
x-data="{
|
||||
get chosen() {
|
||||
return this.{{ $row['read'] }}
|
||||
@@ -85,7 +85,7 @@
|
||||
type="button"
|
||||
x-data
|
||||
data-md-icon-button
|
||||
data-theme-toggle="{{ $mode }}"
|
||||
data-md-theme-toggle="{{ $mode }}"
|
||||
@if ($mode === 'cycle')
|
||||
aria-label="{{ __('Theme') }}"
|
||||
x-bind:aria-label="{ light: @js(__('Theme: light')), dark: @js(__('Theme: dark')), system: @js(__('Theme: system')) }[$store.theme.choice]"
|
||||
@@ -96,7 +96,7 @@
|
||||
x-bind:aria-pressed="($store.theme.resolved === 'dark').toString()"
|
||||
x-on:click="$store.theme.toggle()"
|
||||
@endif
|
||||
{{ $attributes->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')
|
||||
<x-livewire-material::icon name="light_mode" x-cloak x-show="$store.theme.choice === 'light'" />
|
||||
|
||||
@@ -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('<x-theme-toggle mode="contrast" />'))
|
||||
->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('<x-theme-toggle />'))
|
||||
->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('<x-theme-toggle mode="cycle" />'))
|
||||
->toContain('data-theme-toggle="cycle"')
|
||||
->toContain('data-md-theme-toggle="cycle"')
|
||||
->toContain("{ light: 'dark', dark: 'system', system: 'light' }")
|
||||
->and((string) $this->blade('<x-theme-toggle mode="picker" />'))
|
||||
->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 <x-button>'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 () {
|
||||
|
||||
@@ -34,6 +34,7 @@ dataset('navigation components', [
|
||||
'navigation-rail-section',
|
||||
'section-nav',
|
||||
'account-menu',
|
||||
'theme-toggle',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user