Key the views on M3's window size classes, not Tailwind's

Plan step 15. Every `sm:`/`md:`/`lg:`/`xl:` in the components, the showcase
(sections, layout, shell) and the pagination and error views becomes the M3
window size class the audits' breakpoint maps prescribe: the full-screen
dialog, the FAB, the snackbar's width, `radio inline`, the section picker and
the page numbers move from 640px to `medium` (600px, M3's compact boundary,
N-07 / C-07 / IN-28); `button responsive` moves from 1024px to `expanded`
(840px); `section-nav`'s grid-to-row switch from 1280px to `large` (1200px,
N-07); and `drawer pane` from 1280px to `expanded`, where M3 shows two panes
for list-detail (C-07). The pane's default width is now 360dp, M3's fixed
supporting pane at expanded.

Showcase example grids take two columns from `medium` and three or more from
`expanded`; the showcase's own theme picker swaps at `expanded`, the nearest
class boundary to the 768px it used (no M3 rule applies to it).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 04:03:54 +02:00
co-authored by Claude Fable 5.1
parent 25ff5a8d71
commit b1642aed1c
25 changed files with 107 additions and 95 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
<section class="space-y-4" aria-labelledby="group-{{ \Illuminate\Support\Str::slug($group) }}">
<h2 id="group-{{ \Illuminate\Support\Str::slug($group) }}" class="type-title-lg">{{ $group }}</h2>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
<div class="grid grid-cols-1 gap-3 medium:grid-cols-2 expanded:grid-cols-3">
@foreach ($entries as $key => $entry)
<x-livewire-material::card variant="outlined" data-list-row wire:key="section-{{ $key }}">
<div class="flex items-start gap-4">
+3 -3
View File
@@ -50,7 +50,7 @@
<x-slot:top>
<x-livewire-material::app-bar variant="search">
<x-slot:navigation>
<span class="sm:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="showcase-menu" /></span>
<span class="medium:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="showcase-menu" /></span>
</x-slot:navigation>
{{-- The search looks through every section, example and component: the index is
@@ -145,8 +145,8 @@
</x-livewire-material::menu>
@endif
<span class="ms-2 me-3 max-md:hidden"><x-livewire-material::theme-toggle mode="picker" /></span>
<span class="md:hidden"><x-livewire-material::theme-toggle mode="cycle" /></span>
<span class="ms-2 me-3 max-expanded:hidden"><x-livewire-material::theme-toggle mode="picker" /></span>
<span class="expanded:hidden"><x-livewire-material::theme-toggle mode="cycle" /></span>
</x-slot:actions>
</x-livewire-material::app-bar>
</x-slot:top>
@@ -77,7 +77,7 @@
</x-button-group>
BLADE,
'A choice as a connected group' => <<<'BLADE'
<div x-data="{ theme: 'system', days: ['mon'] }" class="grid w-full gap-6 md:grid-cols-2">
<div x-data="{ theme: 'system', days: ['mon'] }" class="grid w-full gap-6 medium:grid-cols-2">
<x-group label="Theme" name="showcase-theme" x-model="theme" :options="[
['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'],
['id' => 'dark', 'name' => 'Dark', 'icon' => 'dark_mode'],
@@ -38,7 +38,7 @@
<x-showcase::example :$title :$code />
@endforeach
<div class="grid gap-4 lg:grid-cols-2">
<div class="grid gap-4 expanded:grid-cols-2">
@foreach (['light', 'dark'] as $theme)
<div data-theme="{{ $theme }}" class="space-y-6 rounded-corner-lg bg-surface p-4 text-on-surface">
<h3 class="type-title-md capitalize">{{ $theme }}</h3>
@@ -47,7 +47,7 @@
<div class="space-y-2">
<h4 class="type-label-lg text-on-surface-variant">{{ $group }}</h4>
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
<div class="grid grid-cols-2 gap-2 medium:grid-cols-3">
@foreach ($roles as $role)
<div class="flex items-center gap-2">
<span @class(['size-8 shrink-0 rounded-corner-sm border border-outline-variant', $role])></span>
@@ -53,7 +53,7 @@
<x-alert description="Settings saved." color="success" class="w-full" />
BLADE,
'Stats' => <<<'BLADE'
<div class="grid w-full gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="grid w-full gap-4 medium:grid-cols-2 expanded:grid-cols-4">
<x-stat title="Shares" value="1,204" icon="link" description="12 this week" />
<x-stat title="Files" value="8,317" icon="description" />
<x-stat title="Stored" value="3.2 GB" icon="hard_drive" description="of 4 GB" />
@@ -1,7 +1,7 @@
@php
$examples = [
'Cards' => <<<'BLADE'
<div class="grid w-full gap-4 md:grid-cols-3">
<div class="grid w-full gap-4 expanded:grid-cols-3">
<x-card title="Filled" subtitle="surface-container-highest">
The default card.
<x-slot:actions><x-button label="Open" /></x-slot:actions>
@@ -27,7 +27,7 @@
</x-card>
BLADE,
'Lists' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<x-list dividers label="Files">
<x-list-item title="holiday-photos.zip" description="248 MB" icon="folder_zip" trailing="3 days" />
<x-list-item title="contract.pdf" overline="Password protected" description="1.2 MB · downloaded twice" icon="picture_as_pdf" trailing="1 hour" />
@@ -5,7 +5,7 @@
Five shadow levels as <code>shadow-elevation-*</code>, for what floats over content.
</p>
<div class="grid grid-cols-2 gap-6 rounded-corner-lg bg-surface-container-low p-6 sm:grid-cols-5">
<div class="grid grid-cols-2 gap-6 rounded-corner-lg bg-surface-container-low p-6 medium:grid-cols-5">
@foreach (['shadow-elevation-1', 'shadow-elevation-2', 'shadow-elevation-3', 'shadow-elevation-4', 'shadow-elevation-5'] as $shadow)
<div @class(['flex h-24 items-center justify-center rounded-corner-md bg-surface-container-high', $shadow])>
<code class="type-label-md">{{ $shadow }}</code>
@@ -1,7 +1,7 @@
@php
$examples = [
'Outlined and filled text fields' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-input label="Share name" hint="Recipients see this name" />
<x-input label="Recipient" icon="mail" type="email" placeholder="name@example.com" clearable value="anna@example.com" />
@@ -22,7 +22,7 @@
</div>
BLADE,
'Errors, required and sizes' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-input label="Email" required />
<x-field id="showcase-error" label="Expiry" :messages="['Choose a date in the future.']">
@@ -42,7 +42,7 @@
</div>
BLADE,
'Textarea and select' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-textarea label="Message" hint="Grows as you type, up to six lines" rows="2" max-rows="6" />
<x-select label="Expires after" :options="[['id' => 1, 'name' => '1 hour'], ['id' => 24, 'name' => '1 day'], ['id' => 168, 'name' => '7 days'], ['id' => 720, 'name' => '30 days', 'disabled' => true]]" />
@@ -57,7 +57,7 @@
</div>
BLADE,
'Checkboxes' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-3">
<div class="grid w-full gap-6 expanded:grid-cols-3">
<div class="grid content-start gap-4">
<x-checkbox label="Notify me on download" checked />
<x-checkbox label="Select all files" indeterminate />
@@ -75,13 +75,13 @@
</div>
BLADE,
'Radio buttons' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<x-radio label="Who can open the link" name="showcase-audience" value="password" :options="[['id' => 'anyone', 'name' => 'Anyone with the link'], ['id' => 'password', 'name' => 'Anyone with the password', 'hint' => 'Share the password separately'], ['id' => 'team', 'name' => 'My team only', 'disabled' => true]]" />
<x-radio label="Theme" name="showcase-radio-theme" value="system" inline :options="[['id' => 'light', 'name' => 'Light'], ['id' => 'dark', 'name' => 'Dark'], ['id' => 'system', 'name' => 'System']]" hint="Inline from sm" />
</div>
BLADE,
'Switches' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-3">
<div class="grid w-full gap-6 expanded:grid-cols-3">
<div class="grid content-start gap-4">
<x-toggle label="Email notifications" checked />
<x-toggle label="Weekly summary" />
@@ -99,7 +99,7 @@
</div>
BLADE,
'Choices' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-6">
<x-choices label="Days you are free" hint="Filter chips: any number" :value="[1, 3]" :options="[['id' => 1, 'name' => 'Mon'], ['id' => 2, 'name' => 'Tue'], ['id' => 3, 'name' => 'Wed'], ['id' => 4, 'name' => 'Thu'], ['id' => 5, 'name' => 'Fri'], ['id' => 6, 'name' => 'Sat', 'disabled' => true]]" />
<x-choices label="Expires after" single :value="24" :options="[['id' => 1, 'name' => '1 hour'], ['id' => 24, 'name' => '1 day'], ['id' => 168, 'name' => '7 days']]" />
@@ -112,7 +112,7 @@
</div>
BLADE,
'Search' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div x-data="{ query: '' }">
<x-search x-model="query" placeholder="Search shares">
<x-list>
@@ -1,7 +1,7 @@
@php
$examples = [
'Docked' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2" x-data="{ expires: '{{ now()->addWeek()->format('Y-m-d') }}', starts: null }">
<div class="grid w-full gap-6 medium:grid-cols-2" x-data="{ expires: '{{ now()->addWeek()->format('Y-m-d') }}', starts: null }">
<div class="grid content-start gap-4">
<x-datepicker label="Expires on" clearable x-model="expires" hint="Type a date or pick one" />
<p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(expires)"></code></p>
@@ -14,7 +14,7 @@
</div>
BLADE,
'Modal and modal input' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2" x-data="{ birthday: '1990-05-17', delivery: null }">
<div class="grid w-full gap-6 medium:grid-cols-2" x-data="{ birthday: '1990-05-17', delivery: null }">
<div class="grid content-start gap-4">
<x-datepicker label="Birthday" mode="modal" x-model="birthday" :max="now()" />
<p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(birthday)"></code></p>
@@ -27,7 +27,7 @@
</div>
BLADE,
'Range' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2" x-data="{ trip: { start: '{{ now()->addDays(3)->format('Y-m-d') }}', end: '{{ now()->addDays(9)->format('Y-m-d') }}' }, leave: { start: null, end: null } }">
<div class="grid w-full gap-6 medium:grid-cols-2" x-data="{ trip: { start: '{{ now()->addDays(3)->format('Y-m-d') }}', end: '{{ now()->addDays(9)->format('Y-m-d') }}' }, leave: { start: null, end: null } }">
<div class="grid content-start gap-4">
<x-datepicker label="Trip" range x-model="trip" />
<p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(trip)"></code></p>
@@ -40,7 +40,7 @@
</div>
BLADE,
'First day of the week and format' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2" x-data="{ race: '{{ now()->addMonth()->format('Y-m-d') }}', block: { start: null, end: null } }">
<div class="grid w-full gap-6 medium:grid-cols-2" x-data="{ race: '{{ now()->addMonth()->format('Y-m-d') }}', block: { start: null, end: null } }">
<div class="grid content-start gap-4">
<x-datepicker label="Race day" week-start="0" format="yyyy-MM-dd" x-model="race" hint="Weeks start on Sunday; typed year first" />
<p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(race)"></code></p>
@@ -53,7 +53,7 @@
</div>
BLADE,
'Limits, errors and states' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-datepicker label="Within the next 30 days" :min="now()" :max="now()->addDays(30)" hint="Days outside are disabled" />
<x-datepicker label="Required" required />
@@ -5,7 +5,7 @@
The corner scale as <code>rounded-corner-*</code>, and M3 Expressive's shapes as <code>&lt;x-shape name="…" /&gt;</code>.
</p>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-5">
<div class="grid grid-cols-2 gap-4 medium:grid-cols-5">
@foreach (['rounded-corner-none', 'rounded-corner-xs', 'rounded-corner-sm', 'rounded-corner-md', 'rounded-corner-lg', 'rounded-corner-lg-increased', 'rounded-corner-xl', 'rounded-corner-xl-increased', 'rounded-corner-xxl', 'rounded-corner-full'] as $corner)
<div class="space-y-2">
<div @class(['h-20 bg-primary-container', $corner])></div>
@@ -14,7 +14,7 @@
@endforeach
</div>
<div class="grid grid-cols-3 gap-4 sm:grid-cols-5 lg:grid-cols-7">
<div class="grid grid-cols-3 gap-4 medium:grid-cols-5 expanded:grid-cols-7">
@foreach (\NoNameWeb\LivewireMaterial\Support\SvgFile::shapeNames() as $shape)
<div class="space-y-2 text-center">
<x-livewire-material::shape :name="$shape" class="mx-auto size-20 text-secondary-container" />
@@ -1,7 +1,7 @@
@php
$examples = [
'Standard, discrete and disabled' => <<<'BLADE'
<div class="grid w-full gap-8 md:grid-cols-3">
<div class="grid w-full gap-8 expanded:grid-cols-3">
<x-slider label="Volume" value="40" hint="Drag the handle or use the arrow keys" />
<x-slider label="Quality" value="6" :max="10" ticks />
<x-slider label="Locked" value="70" disabled />
@@ -17,20 +17,20 @@
</div>
BLADE,
'Range and centred' => <<<'BLADE'
<div class="grid w-full gap-8 md:grid-cols-2">
<div class="grid w-full gap-8 medium:grid-cols-2">
<x-slider label="Price" :value="[20, 60]" range ticks :step="10" hint="Handles never cross" />
<x-slider label="Balance" value="15" :min="-50" :max="50" centered />
</div>
BLADE,
'Colours and value labels' => <<<'BLADE'
<div class="grid w-full gap-8 md:grid-cols-3">
<div class="grid w-full gap-8 expanded:grid-cols-3">
<x-slider label="Always labelled" value="55" color="secondary" value-label="always" size="sm" />
<x-slider label="Tertiary, no label" value="35" color="tertiary" value-label="never" size="sm" />
<x-slider label="Error" :value="[30, 70]" color="error" range size="sm" />
</div>
BLADE,
'Bound with x-model' => <<<'BLADE'
<div x-data="{ volume: 25, price: [100, 300] }" class="grid w-full gap-8 md:grid-cols-2">
<div x-data="{ volume: 25, price: [100, 300] }" class="grid w-full gap-8 medium:grid-cols-2">
<div class="space-y-2">
<x-slider label="Bound volume" x-model="volume" />
<p class="type-body-md text-on-surface-variant">volume: <span data-bound="volume" x-text="volume"></span> <x-button label="Set 80" x-on:click="volume = 80" /></p>
@@ -1,7 +1,7 @@
@php
$examples = [
'12 and 24 hours, outlined and filled' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-timepicker label="Meeting starts" value="09:30" hint="The locale's clock" />
<x-timepicker label="Pick-up" value="17:15" format="12" icon="local_shipping" clearable />
@@ -16,7 +16,7 @@
</div>
BLADE,
'Steps and limits' => <<<'BLADE'
<div class="grid w-full gap-6 md:grid-cols-2">
<div class="grid w-full gap-6 medium:grid-cols-2">
<div class="grid content-start gap-4">
<x-timepicker label="Appointment" value="10:00" format="24" step="15" min="08:00" max="17:30" hint="Quarter hours from 08:00 to 17:30" />
<x-timepicker label="Night shift starts" value="23:00" format="24" min="22:00" max="06:00" hint="From 22:00, across midnight, to 06:00" />
@@ -27,7 +27,7 @@
<div class="divide-y divide-divider rounded-corner-lg bg-surface-container">
@foreach ($styles as [$regular, $emphasized])
<div class="grid gap-2 p-4 md:grid-cols-[12rem_1fr_1fr] md:items-baseline">
<div class="grid gap-2 p-4 expanded:grid-cols-[12rem_1fr_1fr] expanded:items-baseline">
<code class="type-label-md text-on-surface-variant">{{ $regular }}</code>
<p @class([$regular, 'truncate'])>Share files, safely</p>
<p @class([$emphasized, 'truncate'])>Share files, safely</p>
+3 -3
View File
@@ -59,9 +59,9 @@
</x-slot:actions>
<x-slot:top>
<header class="sticky top-0 z-20 flex h-16 items-center gap-1 bg-surface px-1 pt-[var(--material-safe-top,env(safe-area-inset-top))] sm:px-4">
<span class="sm:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" /></span>
<h1 class="min-w-0 flex-1 truncate px-3 type-title-lg sm:px-0">{{ $current['title'] }}</h1>
<header class="sticky top-0 z-20 flex h-16 items-center gap-1 bg-surface px-1 pt-[var(--material-safe-top,env(safe-area-inset-top))] medium:px-4">
<span class="medium:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-menu" /></span>
<h1 class="min-w-0 flex-1 truncate px-3 type-title-lg medium:px-0">{{ $current['title'] }}</h1>
<x-livewire-material::button icon="search" tooltip="Search" />
</header>
</x-slot:top>