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
+8 -6
View File
@@ -24,9 +24,11 @@
Behaviour kept from maryUI: `link` renders an anchor with `wire:navigate` (unless `external` Behaviour kept from maryUI: `link` renders an anchor with `wire:navigate` (unless `external`
or `no-wire-navigate`); `disabled` works on a link too, as `aria-disabled`; `spinner` shows or `no-wire-navigate`); `disabled` works on a link too, as `aria-disabled`; `spinner` shows
the loading indicator while the button's own `wire:click` runs (or the action named by a the loading indicator while the button's own `wire:click` runs (or the action named by a
string); `responsive` hides the label below `lg`; `tooltip`, `tooltip-left`, `tooltip-right` string); `responsive` hides the label below `expanded` (840px, where M3 lets buttons reposition);
`tooltip`, `tooltip-left`, `tooltip-right`
and `tooltip-bottom` attach a plain tooltip. `fab` is a page's create action: an extended FAB and `tooltip-bottom` attach a plain tooltip. `fab` is a page's create action: an extended FAB
pinned in the thumb zone below `sm`, a filled button above it one element either way. pinned in the thumb zone on a compact window (below `medium`, 600px), a filled button from
there one element either way.
Never pass `hidden`, a display or a position class: the button is `inline-flex` and Never pass `hidden`, a display or a position class: the button is `inline-flex` and
`relative`, and whichever Tailwind emits last wins. Wrap it instead. --}} `relative`, and whichever Tailwind emits last wins. Wrap it instead. --}}
@@ -173,8 +175,8 @@
'disabled:cursor-not-allowed disabled:shadow-none aria-disabled:pointer-events-none aria-disabled:shadow-none', 'disabled:cursor-not-allowed disabled:shadow-none aria-disabled:pointer-events-none aria-disabled:shadow-none',
'disabled:bg-on-surface/10 disabled:text-on-surface/38 aria-disabled:bg-on-surface/10 aria-disabled:text-on-surface/38' => $contained, 'disabled:bg-on-surface/10 disabled:text-on-surface/38 aria-disabled:bg-on-surface/10 aria-disabled:text-on-surface/38' => $contained,
'disabled:text-on-surface/38 aria-disabled:text-on-surface/38' => ! $contained, 'disabled:text-on-surface/38 aria-disabled:text-on-surface/38' => ! $contained,
// The FAB, below sm only: an extended FAB in the thumb zone, clear of a bottom bar the layout declares. // The FAB, on a compact window only: an extended FAB in the thumb zone, clear of a bottom bar the layout declares.
'max-sm:fixed max-sm:end-4 max-sm:bottom-[calc(var(--material-bottom-bar,0px)+1rem)] max-sm:z-30 max-sm:h-14 max-sm:gap-2 max-sm:px-4 max-sm:rounded-corner-lg max-sm:type-title-md max-sm:bg-primary-container max-sm:text-on-primary-container max-sm:shadow-elevation-3' => $fab, 'max-medium:fixed max-medium:end-4 max-medium:bottom-[calc(var(--material-bottom-bar,0px)+1rem)] max-medium:z-30 max-medium:h-14 max-medium:gap-2 max-medium:px-4 max-medium:rounded-corner-lg max-medium:type-title-md max-medium:bg-primary-container max-medium:text-on-primary-container max-medium:shadow-elevation-3' => $fab,
]; ];
$tag = $isLink ? 'a' : 'button'; $tag = $isLink ? 'a' : 'button';
@@ -210,12 +212,12 @@
@if ($icon) @if ($icon)
<span class="contents" @if ($spinnerTarget) wire:loading.remove wire:target="{{ $spinnerTarget }}" @endif> <span class="contents" @if ($spinnerTarget) wire:loading.remove wire:target="{{ $spinnerTarget }}" @endif>
<x-livewire-material::icon :name="$icon" :filled="$selected === true" :class="\Illuminate\Support\Arr::toCssClasses([$iconSize, 'max-sm:size-6' => $fab])" /> <x-livewire-material::icon :name="$icon" :filled="$selected === true" :class="\Illuminate\Support\Arr::toCssClasses([$iconSize, 'max-medium:size-6' => $fab])" />
</span> </span>
@endif @endif
@unless ($iconOnly) @unless ($iconOnly)
<span @class(['max-lg:hidden' => $responsive])>{{ $label ?? $slot }}</span> <span @class(['max-expanded:hidden' => $responsive])>{{ $label ?? $slot }}</span>
@endunless @endunless
@if ($iconRight) @if ($iconRight)
+22 -18
View File
@@ -1,5 +1,8 @@
{{-- An M3 side sheet: detail or controls that slide in from the edge over a scrim; on a phone it is {{-- An M3 side sheet: detail or controls that slide in from the edge over a scrim; on a phone it is
the whole screen. With `pane`, from `xl` it is a list-detail pane beside the list instead. the whole screen. With `pane`, from `expanded` (840px) it is a second pane beside the list
instead M3 shows two panes for list-detail from expanded upwards, and puts a supporting pane
on the side of the focus pane at expanded, 360dp wide
(docs/reference/m3/foundations-supplement.md § Canonical layout examples).
The open state is the Livewire property in `wire:model` (entangled live, so a detail held in the The open state is the Livewire property in `wire:model` (entangled live, so a detail held in the
URL follows a close) a flag or an id and closing writes back `false` or `null`. Without URL follows a close) a flag or an id and closing writes back `false` or `null`. Without
@@ -9,13 +12,14 @@
As a sheet it is modal: the page inert and still (`x-trap.inert.noscroll`), and it enters on As a sheet it is modal: the page inert and still (`x-trap.inert.noscroll`), and it enters on
emphasized decelerate rather than a spring a sheet anchored to the edge that overshot would emphasized decelerate rather than a spring a sheet anchored to the edge that overshot would
open a gap. M3's modal side sheet: surface-container-low, a large corner on its inner edge, open a gap. M3's modal side sheet: surface-container-low, a large corner on its inner edge,
elevation 1; `side` `end` (the default) or `start`; `width` from `sm` (a caller's `w-*` would elevation 1; `side` `end` (the default) or `start`; `width` from `medium` (a caller's `w-*`
race the sheet's own). would race the sheet's own — a compact window gets the full-bleed sheet).
As a pane (`pane`, from `xl`) nothing is covered: the page renders the drawer after its list in As a pane (`pane`, from `expanded`) nothing is covered: the page renders the drawer after its
an `xl:flex xl:items-start xl:gap-6` row, the drawer sticks under the top of the viewport, the list in
an `expanded:flex expanded:items-start expanded:gap-6` row, the drawer sticks under the top of the viewport, the
list stays usable and another row swaps what it shows — no scrim, no trap, no inert page. While list stays usable and another row swaps what it shows — no scrim, no trap, no inert page. While
closed it takes no room. `pane-width` sizes the pane (the sheet's width by default). Escape closes closed it takes no room. `pane-width` sizes the pane (`22.5rem`, M3's 360dp fixed pane). Escape closes
the sheet but leaves a pane open, since the page beside it is still in use; `pane-close-on-escape` the sheet but leaves a pane open, since the page beside it is still in use; `pane-close-on-escape`
closes the pane on Escape too, for a pane that is a transient detail. The body is closes the pane on Escape too, for a pane that is a transient detail. The body is
a size container, so its contents lay out by the room the sheet or pane actually has (`@md:`), a size container, so its contents lay out by the room the sheet or pane actually has (`@md:`),
@@ -35,7 +39,7 @@
'withoutBackdropClose' => false, 'withoutBackdropClose' => false,
'width' => '25rem', 'width' => '25rem',
'pane' => false, 'pane' => false,
'paneWidth' => null, 'paneWidth' => '22.5rem',
'paneCloseOnEscape' => false, 'paneCloseOnEscape' => false,
]) ])
@@ -52,7 +56,7 @@
close() { this.open = typeof this.open === 'boolean' ? false : null; }, close() { this.open = typeof this.open === 'boolean' ? false : null; },
@if ($pane) @if ($pane)
init() { init() {
const query = window.matchMedia('(min-width: 80rem)'); const query = window.matchMedia('(width >= 52.5rem)');
this.wide = query.matches; this.wide = query.matches;
query.addEventListener('change', (event) => this.wide = event.matches); query.addEventListener('change', (event) => this.wide = event.matches);
}, },
@@ -61,8 +65,8 @@
@if ($closeOnEscape) x-on:keydown.window.escape="{{ $paneCloseOnEscape ? 'if (open) close()' : 'if (open && ! wide) close()' }}" @endif @if ($closeOnEscape) x-on:keydown.window.escape="{{ $paneCloseOnEscape ? 'if (open) close()' : 'if (open && ! wide) close()' }}" @endif
data-sheet="{{ $id }}" data-sheet="{{ $id }}"
@if ($pane) @if ($pane)
x-bind:class="! open && 'xl:hidden'" x-bind:class="! open && 'expanded:hidden'"
class="xl:sticky xl:top-[calc(var(--material-safe-top,env(safe-area-inset-top))+1.25rem)] xl:shrink-0 xl:self-start" class="expanded:sticky expanded:top-[calc(var(--material-safe-top,env(safe-area-inset-top))+1.25rem)] expanded:shrink-0 expanded:self-start"
data-pane data-pane
@endif @endif
> >
@@ -71,7 +75,7 @@
x-show="open" x-show="open"
x-transition.opacity.duration.200ms x-transition.opacity.duration.200ms
@if (! $withoutBackdropClose) x-on:click="close()" @endif @if (! $withoutBackdropClose) x-on:click="close()" @endif
@class(['fixed inset-0 z-40 bg-scrim/32', 'xl:hidden' => $pane]) @class(['fixed inset-0 z-40 bg-scrim/32', 'expanded:hidden' => $pane])
aria-hidden="true" aria-hidden="true"
></div> ></div>
@@ -80,24 +84,24 @@
x-show="open" x-show="open"
x-trap.inert.noscroll="open && ! wide" x-trap.inert.noscroll="open && ! wide"
x-transition:enter="transition-[translate,opacity] duration-(--md-sys-motion-spatial-default-duration) ease-emphasized-decelerate" x-transition:enter="transition-[translate,opacity] duration-(--md-sys-motion-spatial-default-duration) ease-emphasized-decelerate"
x-transition:enter-start="{{ $pane ? ($start ? 'max-xl:-translate-x-full xl:opacity-0' : 'max-xl:translate-x-full xl:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}" x-transition:enter-start="{{ $pane ? ($start ? 'max-expanded:-translate-x-full expanded:opacity-0' : 'max-expanded:translate-x-full expanded:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}"
x-transition:enter-end="translate-x-0 opacity-100" x-transition:enter-end="translate-x-0 opacity-100"
x-transition:leave="transition-[translate,opacity] duration-(--md-sys-motion-effects-default-duration) ease-emphasized-accelerate" x-transition:leave="transition-[translate,opacity] duration-(--md-sys-motion-effects-default-duration) ease-emphasized-accelerate"
x-transition:leave-start="translate-x-0 opacity-100" x-transition:leave-start="translate-x-0 opacity-100"
x-transition:leave-end="{{ $pane ? ($start ? 'max-xl:-translate-x-full xl:opacity-0' : 'max-xl:translate-x-full xl:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}" x-transition:leave-end="{{ $pane ? ($start ? 'max-expanded:-translate-x-full expanded:opacity-0' : 'max-expanded:translate-x-full expanded:opacity-0') : ($start ? '-translate-x-full' : 'translate-x-full') }}"
id="{{ $id }}" id="{{ $id }}"
x-bind:role="wide ? 'region' : 'dialog'" x-bind:role="wide ? 'region' : 'dialog'"
x-bind:aria-modal="wide ? null : 'true'" x-bind:aria-modal="wide ? null : 'true'"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
@if (filled($title)) aria-labelledby="{{ $id }}-title" @endif @if (filled($title)) aria-labelledby="{{ $id }}-title" @endif
style="--sheet-width: {{ $width }}; --pane-width: {{ $paneWidth ?? $width }}" style="--sheet-width: {{ $width }}; --pane-width: {{ $paneWidth }}"
{{ $attributes->whereDoesntStartWith('wire:model')->except(['id', 'class'])->class([ {{ $attributes->whereDoesntStartWith('wire:model')->except(['id', 'class'])->class([
'fixed top-[var(--material-safe-top,env(safe-area-inset-top))] bottom-0 z-50 flex w-full flex-col overflow-y-auto bg-surface-container-low p-6 text-on-surface shadow-elevation-1', 'fixed top-[var(--material-safe-top,env(safe-area-inset-top))] bottom-0 z-50 flex w-full flex-col overflow-y-auto bg-surface-container-low p-6 text-on-surface shadow-elevation-1',
'end-0 sm:rounded-s-corner-lg' => ! $start, 'end-0 medium:rounded-s-corner-lg' => ! $start,
'start-0 sm:rounded-e-corner-lg' => $start, 'start-0 medium:rounded-e-corner-lg' => $start,
'sm:w-(--sheet-width) sm:max-w-[calc(100vw-4rem)]', 'medium:w-(--sheet-width) medium:max-w-[calc(100vw-4rem)]',
'xl:relative xl:top-0 xl:z-auto xl:max-h-[calc(100dvh-2.5rem-var(--material-safe-top,env(safe-area-inset-top)))] xl:w-(--pane-width) xl:max-w-none xl:rounded-corner-lg xl:bg-surface-container xl:shadow-none' => $pane, 'expanded:relative expanded:top-0 expanded:z-auto expanded:max-h-[calc(100dvh-2.5rem-var(--material-safe-top,env(safe-area-inset-top)))] expanded:w-(--pane-width) expanded:max-w-none expanded:rounded-corner-lg expanded:bg-surface-container expanded:shadow-none' => $pane,
$attributes->get('class'), $attributes->get('class'),
]) }} ]) }}
> >
+10 -9
View File
@@ -19,7 +19,8 @@
high, extra-large corner, elevation 3, a headline-small `title`, body-medium `subtitle` in high, extra-large corner, elevation 3, a headline-small `title`, body-medium `subtitle` in
on-surface-variant, and the `actions` slot at the end. `icon` puts a secondary-coloured icon on-surface-variant, and the `actions` slot at the end. `icon` puts a secondary-coloured icon
above a centred title, as M3 draws a dialog with a hero icon. `fullscreen` makes a dialog that above a centred title, as M3 draws a dialog with a hero icon. `fullscreen` makes a dialog that
holds a form take the whole screen below `sm`, with a close button and the title in a top bar holds a form take the whole screen on a compact window (below `medium`, 600px M3 uses
full-screen dialogs "only in compact breakpoints"), with a close button and the title in a top bar
clear of the notch. `persistent` ignores Escape and the scrim, for a dialog that must be clear of the notch. `persistent` ignores Escape and the scrim, for a dialog that must be
answered. It opens on the fast spatial spring and closes at once, as M3's do. --}} answered. It opens on the fast spatial spring and closes at once, as M3's do. --}}
@@ -58,17 +59,17 @@
'm-auto max-h-[calc(100dvh-3rem)] w-[calc(100vw-3rem)] max-w-[35rem] min-w-70 overflow-visible bg-transparent p-0 text-on-surface', 'm-auto max-h-[calc(100dvh-3rem)] w-[calc(100vw-3rem)] max-w-[35rem] min-w-70 overflow-visible bg-transparent p-0 text-on-surface',
'backdrop:bg-scrim/32', 'backdrop:bg-scrim/32',
'opacity-100 scale-100 starting:opacity-0 starting:scale-95 transition-[opacity,scale] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast', 'opacity-100 scale-100 starting:opacity-0 starting:scale-95 transition-[opacity,scale] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast',
'max-sm:m-0 max-sm:h-dvh max-sm:max-h-none max-sm:w-full max-sm:max-w-none max-sm:min-w-0' => $fullscreen, 'max-medium:m-0 max-medium:h-dvh max-medium:max-h-none max-medium:w-full max-medium:max-w-none max-medium:min-w-0' => $fullscreen,
$attributes->get('class'), $attributes->get('class'),
]) ])
> >
<div @class([ <div @class([
'flex max-h-[inherit] flex-col overflow-y-auto rounded-corner-xl bg-surface-container-high p-6 shadow-elevation-3', 'flex max-h-[inherit] flex-col overflow-y-auto rounded-corner-xl bg-surface-container-high p-6 shadow-elevation-3',
'max-sm:h-full max-sm:rounded-none max-sm:p-0 max-sm:pt-[var(--material-safe-top,env(safe-area-inset-top))]' => $fullscreen, 'max-medium:h-full max-medium:rounded-none max-medium:p-0 max-medium:pt-[var(--material-safe-top,env(safe-area-inset-top))]' => $fullscreen,
$boxClass, $boxClass,
])> ])>
@if ($fullscreen) @if ($fullscreen)
<div class="flex h-16 shrink-0 items-center gap-1 px-1 sm:hidden"> <div class="flex h-16 shrink-0 items-center gap-1 px-1 medium:hidden">
<x-livewire-material::button icon="close" :tooltip="__('Close')" x-on:click="close()" /> <x-livewire-material::button icon="close" :tooltip="__('Close')" x-on:click="close()" />
@if (filled($title)) @if (filled($title))
@@ -77,20 +78,20 @@
</div> </div>
@endif @endif
<div @class(['min-h-0 flex-1', 'max-sm:overflow-y-auto max-sm:px-6 max-sm:pb-6' => $fullscreen])> <div @class(['min-h-0 flex-1', 'max-medium:overflow-y-auto max-medium:px-6 max-medium:pb-6' => $fullscreen])>
@if (filled($title) || filled($subtitle) || $icon) @if (filled($title) || filled($subtitle) || $icon)
{{-- A full-screen dialog's bar names it on a phone, so only the subtitle stays there. --}} {{-- A full-screen dialog's bar names it on a phone, so only the subtitle stays there. --}}
<div @class(['mb-4', 'max-sm:hidden' => $fullscreen && ! $icon && blank($subtitle), 'text-center' => $icon])> <div @class(['mb-4', 'max-medium:hidden' => $fullscreen && ! $icon && blank($subtitle), 'text-center' => $icon])>
@if ($icon) @if ($icon)
<x-livewire-material::icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" /> <x-livewire-material::icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" />
@endif @endif
@if (filled($title)) @if (filled($title))
<h2 id="{{ $id }}-title" @class(['type-headline-sm', 'max-sm:hidden' => $fullscreen && ! $icon])>{{ $title }}</h2> <h2 id="{{ $id }}-title" @class(['type-headline-sm', 'max-medium:hidden' => $fullscreen && ! $icon])>{{ $title }}</h2>
@endif @endif
@if (filled($subtitle)) @if (filled($subtitle))
<p @class(['type-body-md text-on-surface-variant', 'mt-4' => filled($title) || $icon, 'max-sm:mt-0' => $fullscreen && ! $icon])>{{ $subtitle }}</p> <p @class(['type-body-md text-on-surface-variant', 'mt-4' => filled($title) || $icon, 'max-medium:mt-0' => $fullscreen && ! $icon])>{{ $subtitle }}</p>
@endif @endif
@if ($separator) @if ($separator)
@@ -105,7 +106,7 @@
@isset($actions) @isset($actions)
<div @class([ <div @class([
'flex shrink-0 flex-wrap items-center justify-end gap-2 pt-6', 'flex shrink-0 flex-wrap items-center justify-end gap-2 pt-6',
'max-sm:border-t max-sm:border-outline-variant max-sm:px-6 max-sm:py-4' => $fullscreen, 'max-medium:border-t max-medium:border-outline-variant max-medium:px-6 max-medium:py-4' => $fullscreen,
])> ])>
{{ $actions }} {{ $actions }}
</div> </div>
@@ -1,7 +1,7 @@
{{-- M3 Expressive's flexible navigation bar: three to five destinations at the bottom of a {{-- M3 Expressive's flexible navigation bar: three to five destinations at the bottom of a
compact window. compact window.
<div class="fixed inset-x-0 bottom-0 z-30 sm:hidden"> <div class="fixed inset-x-0 bottom-0 z-30 medium:hidden">
<x-navigation-bar> <x-navigation-bar>
<x-navigation-bar-item label="Inbox" icon="inbox" link="/inbox" active badge="12" /> <x-navigation-bar-item label="Inbox" icon="inbox" link="/inbox" active badge="12" />
<x-navigation-bar-item label="Starred" icon="star" link="/starred" /> <x-navigation-bar-item label="Starred" icon="star" link="/starred" />
+3 -2
View File
@@ -3,7 +3,8 @@
A `<fieldset>` whose legend is the question, so a screen reader reads the group's name with each A `<fieldset>` whose legend is the question, so a screen reader reads the group's name with each
option, and native radios sharing a name, so the arrow keys move between them. `options` as option, and native radios sharing a name, so the arrow keys move between them. `options` as
`['id' => …, 'name' => …, 'hint' => …]` (`'disabled' => true` greys one out), `option-value`, `['id' => …, 'name' => …, 'hint' => …]` (`'disabled' => true` greys one out), `option-value`,
`option-label` and `option-hint` to read other keys, `inline` to lay them in a row from `sm`, and `option-label` and `option-hint` to read other keys, `inline` to lay them in a row from `medium`
(600px, M3's compact/medium boundary — compact always stacks), and
`value` for the option chosen when the page loads (with `wire:model`, Livewire sets it). `value` for the option chosen when the page loads (with `wire:model`, Livewire sets it).
Errors are read from the bag under the `wire:model` name Errors are read from the bag under the `wire:model` name
(resources/css/components/selection.css). --}} (resources/css/components/selection.css). --}}
@@ -36,7 +37,7 @@
<legend class="mb-3 type-label-lg text-on-surface-variant">{{ $label }}</legend> <legend class="mb-3 type-label-lg text-on-surface-variant">{{ $label }}</legend>
@endif @endif
<div @class(['grid gap-4', 'sm:flex sm:flex-wrap sm:gap-x-6' => $inline])> <div @class(['grid gap-4', 'medium:flex medium:flex-wrap medium:gap-x-6' => $inline])>
@foreach ($options as $option) @foreach ($options as $option)
@php($optionHintText = data_get($option, $optionHint)) @php($optionHintText = data_get($option, $optionHint))
@@ -34,7 +34,7 @@
<legend class="mb-2 type-label-lg text-on-surface-variant">{{ $label }}</legend> <legend class="mb-2 type-label-lg text-on-surface-variant">{{ $label }}</legend>
@endif @endif
<div class="grid grid-cols-2 gap-2 sm:grid-cols-4"> <div class="grid grid-cols-2 gap-2 medium:grid-cols-4">
@foreach ($profiles as $profile => $scheme) @foreach ($profiles as $profile => $scheme)
<label <label
data-scheme-option="{{ $profile }}" data-scheme-option="{{ $profile }}"
@@ -2,8 +2,10 @@
single destination in the app's own navigation. single destination in the app's own navigation.
`items`, a list of `['title' => …, 'url' => …]` with an optional `icon`, `active` and `badge` `items`, a list of `['title' => …, 'url' => …]` with an optional `icon`, `active` and `badge`
(an item is current when `active` is true, or when its `url` is the page's). From `sm` they (an item is current when `active` is true, or when its `url` is the page's). From `medium`
are M3's secondary tabs as links, the current one underlined; below `sm`, where a row of them (600px) they
are M3's secondary tabs as links, the current one underlined; on a compact window, where a row
of them
never fits, a button naming the current section opens a menu of all of them, the current one never fits, a button naming the current section opens a menu of all of them, the current one
marked `aria-current="page"` and each with its badge. The same list is marked `aria-current="page"` and each with its badge. The same list is
rendered for both, and CSS shows one. rendered for both, and CSS shows one.
@@ -11,7 +13,8 @@
The page's URL is `Livewire::originalUrl()`: while a Livewire component on the page updates, the The page's URL is `Livewire::originalUrl()`: while a Livewire component on the page updates, the
request is Livewire's update endpoint, and comparing with it left no section lit. request is Livewire's update endpoint, and comparing with it left no section lit.
A row too long for its column wraps onto a grid rather than scrolling: below `xl` five or six A row too long for its column wraps onto a grid rather than scrolling: below `large` (1200px)
five or six
sections go 3 + 3 and seven or more go four to a row — tabs that scroll hid the last sections on sections go 3 + 3 and seven or more go four to a row — tabs that scroll hid the last sections on
a tablet. `label` names the navigation ("Sections"). Links use `wire:navigate` unless a tablet. `label` names the navigation ("Sections"). Links use `wire:navigate` unless
`no-wire-navigate`. --}} `no-wire-navigate`. --}}
@@ -29,15 +32,15 @@
$current = collect($items)->first($isCurrent) ?? ($items[0] ?? null); $current = collect($items)->first($isCurrent) ?? ($items[0] ?? null);
$layout = match (true) { $layout = match (true) {
count($items) < 5 => 'sm:flex', count($items) < 5 => 'medium:flex',
count($items) < 7 => 'sm:grid sm:grid-cols-3 xl:flex', count($items) < 7 => 'medium:grid medium:grid-cols-3 large:flex',
default => 'sm:grid sm:grid-cols-4 xl:flex', default => 'medium:grid medium:grid-cols-4 large:flex',
}; };
@endphp @endphp
<div {{ $attributes->class(['min-w-0']) }} data-section-nav> <div {{ $attributes->class(['min-w-0']) }} data-section-nav>
@if ($current) @if ($current)
<div class="sm:hidden" data-section-picker> <div class="medium:hidden" data-section-picker>
<x-livewire-material::menu :$label position="bottom-start"> <x-livewire-material::menu :$label position="bottom-start">
<x-slot:trigger> <x-slot:trigger>
<button type="button" class="focus-ring flex h-12 w-[min(20rem,calc(100vw-2rem))] cursor-pointer items-center gap-3 rounded-corner-xs border border-outline px-4 text-start type-body-lg text-on-surface"> <button type="button" class="focus-ring flex h-12 w-[min(20rem,calc(100vw-2rem))] cursor-pointer items-center gap-3 rounded-corner-xs border border-outline px-4 text-start type-body-lg text-on-surface">
@@ -56,7 +59,7 @@
</div> </div>
@endif @endif
<nav aria-label="{{ $label }}" class="max-sm:hidden"> <nav aria-label="{{ $label }}" class="max-medium:hidden">
<ul data-tabs-bar data-variant="secondary" class="{{ $layout }}"> <ul data-tabs-bar data-variant="secondary" class="{{ $layout }}">
@foreach ($items as $item) @foreach ($items as $item)
@php($on = $isCurrent($item)) @php($on = $isCurrent($item))
+4 -3
View File
@@ -25,7 +25,8 @@
body-medium text, a label-large action in inverse-primary, extra-small corners, elevation 3, body-medium text, a label-large action in inverse-primary, extra-small corners, elevation 3,
48px for one line. A type draws its state icon in the inverse state colour. `position`: 48px for one line. A type draws its state icon in the inverse state colour. `position`:
`bottom` (centred, the default) or `bottom-start`. It lifts above a bottom bar through `bottom` (centred, the default) or `bottom-start`. It lifts above a bottom bar through
`--material-bottom-bar`. --}} `--material-bottom-bar`. A compact window (below `medium`, 600px) gets the full-width snackbar;
from `medium` it hugs its line length instead, as M3 asks. --}}
@props(['position' => 'bottom']) @props(['position' => 'bottom'])
@@ -35,7 +36,7 @@
{{ $attributes->class([ {{ $attributes->class([
'pointer-events-none fixed inset-x-4 z-50 flex bottom-[calc(var(--material-bottom-bar,0px)+1rem)]', 'pointer-events-none fixed inset-x-4 z-50 flex bottom-[calc(var(--material-bottom-bar,0px)+1rem)]',
'justify-center' => $position !== 'bottom-start', 'justify-center' => $position !== 'bottom-start',
'justify-start sm:start-6' => $position === 'bottom-start', 'justify-start medium:start-6' => $position === 'bottom-start',
]) }} ]) }}
> >
<template x-if="current"> <template x-if="current">
@@ -48,7 +49,7 @@
x-on:mouseleave="resume()" x-on:mouseleave="resume()"
x-on:focusin="pause()" x-on:focusin="pause()"
x-on:focusout="resume()" x-on:focusout="resume()"
class="pointer-events-auto flex min-h-12 w-full max-w-[min(100%,36rem)] items-center gap-3 rounded-corner-xs bg-inverse-surface py-1.5 ps-4 pe-2 text-inverse-on-surface shadow-elevation-3 transition-[translate,opacity] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast starting:translate-y-4 starting:opacity-0 sm:w-auto sm:min-w-86" class="pointer-events-auto flex min-h-12 w-full max-w-[min(100%,36rem)] items-center gap-3 rounded-corner-xs bg-inverse-surface py-1.5 ps-4 pe-2 text-inverse-on-surface shadow-elevation-3 transition-[translate,opacity] duration-(--md-sys-motion-spatial-fast-duration) ease-spatial-fast starting:translate-y-4 starting:opacity-0 medium:w-auto medium:min-w-86"
> >
<template x-if="icon(current.type)"> <template x-if="icon(current.type)">
<span class="flex shrink-0" x-bind:class="{ <span class="flex shrink-0" x-bind:class="{
@@ -47,7 +47,7 @@
</head> </head>
<body class="min-h-dvh bg-surface font-sans text-on-surface antialiased"> <body class="min-h-dvh bg-surface font-sans text-on-surface antialiased">
<main data-error-page class="mx-auto flex min-h-dvh max-w-xl flex-col items-center justify-center px-6 py-12 text-center"> <main data-error-page class="mx-auto flex min-h-dvh max-w-xl flex-col items-center justify-center px-6 py-12 text-center">
<div data-error-art class="relative grid size-48 shrink-0 place-items-center sm:size-60"> <div data-error-art class="relative grid size-48 shrink-0 place-items-center medium:size-60">
<div data-error-shape class="absolute inset-0"> <div data-error-shape class="absolute inset-0">
<x-livewire-material::shape :name="trim($__env->yieldContent('shape', 'cookie-7'))" class="size-full text-primary-container" /> <x-livewire-material::shape :name="trim($__env->yieldContent('shape', 'cookie-7'))" class="size-full text-primary-container" />
</div> </div>
@@ -55,7 +55,7 @@
<p data-error-code class="relative type-emphasized-display-lg text-on-primary-container tabular-nums">@yield('code')</p> <p data-error-code class="relative type-emphasized-display-lg text-on-primary-container tabular-nums">@yield('code')</p>
</div> </div>
<h1 data-error-headline class="mt-10 type-headline-md text-balance sm:type-headline-lg"> <h1 data-error-headline class="mt-10 type-headline-md text-balance medium:type-headline-lg">
@hasSection('headline') @hasSection('headline')
@yield('headline') @yield('headline')
@else @else
@@ -12,8 +12,8 @@
@if ($paginator->hasPages()) @if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4"> <nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4">
<p class="type-body-sm text-on-surface-variant"> <p class="type-body-sm text-on-surface-variant">
<span class="sm:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span> <span class="medium:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span>
<span class="max-sm:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span> <span class="max-medium:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span>
</p> </p>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@@ -29,15 +29,15 @@
@foreach ($elements as $element) @foreach ($elements as $element)
@if (is_string($element)) @if (is_string($element))
<span class="{{ $dead }} max-sm:hidden" aria-disabled="true">{{ $element }}</span> <span class="{{ $dead }} max-medium:hidden" aria-disabled="true">{{ $element }}</span>
@endif @endif
@if (is_array($element)) @if (is_array($element))
@foreach ($element as $page => $url) @foreach ($element as $page => $url)
@if ($page == $paginator->currentPage()) @if ($page == $paginator->currentPage())
<span aria-current="page" class="{{ $step }} bg-secondary-container text-on-secondary-container max-sm:hidden">{{ $page }}</span> <span aria-current="page" class="{{ $step }} bg-secondary-container text-on-secondary-container max-medium:hidden">{{ $page }}</span>
@else @else
<a href="{{ $url }}" class="{{ $live }} max-sm:hidden" aria-label="{{ __('Go to page :page', ['page' => $page]) }}">{{ $page }}</a> <a href="{{ $url }}" class="{{ $live }} max-medium:hidden" aria-label="{{ __('Go to page :page', ['page' => $page]) }}">{{ $page }}</a>
@endif @endif
@endforeach @endforeach
@endif @endif
@@ -28,8 +28,8 @@
@if ($paginator->hasPages()) @if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4"> <nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" data-pagination class="flex items-center justify-between gap-4">
<p class="type-body-sm text-on-surface-variant"> <p class="type-body-sm text-on-surface-variant">
<span class="sm:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span> <span class="medium:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span>
<span class="max-sm:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span> <span class="max-medium:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span>
</p> </p>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@@ -45,12 +45,12 @@
@foreach ($elements as $element) @foreach ($elements as $element)
@if (is_string($element)) @if (is_string($element))
<span class="{{ $dead }} max-sm:hidden" aria-disabled="true">{{ $element }}</span> <span class="{{ $dead }} max-medium:hidden" aria-disabled="true">{{ $element }}</span>
@endif @endif
@if (is_array($element)) @if (is_array($element))
@foreach ($element as $page => $url) @foreach ($element as $page => $url)
<span wire:key="paginator-{{ $paginator->getPageName() }}-page{{ $page }}" class="max-sm:hidden"> <span wire:key="paginator-{{ $paginator->getPageName() }}-page{{ $page }}" class="max-medium:hidden">
@if ($page == $paginator->currentPage()) @if ($page == $paginator->currentPage())
<span aria-current="page" class="{{ $step }} bg-secondary-container text-on-secondary-container">{{ $page }}</span> <span aria-current="page" class="{{ $step }} bg-secondary-container text-on-secondary-container">{{ $page }}</span>
@else @else
+1 -1
View File
@@ -15,7 +15,7 @@
<section class="space-y-4" aria-labelledby="group-{{ \Illuminate\Support\Str::slug($group) }}"> <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> <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) @foreach ($entries as $key => $entry)
<x-livewire-material::card variant="outlined" data-list-row wire:key="section-{{ $key }}"> <x-livewire-material::card variant="outlined" data-list-row wire:key="section-{{ $key }}">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
+3 -3
View File
@@ -50,7 +50,7 @@
<x-slot:top> <x-slot:top>
<x-livewire-material::app-bar variant="search"> <x-livewire-material::app-bar variant="search">
<x-slot:navigation> <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> </x-slot:navigation>
{{-- The search looks through every section, example and component: the index is {{-- The search looks through every section, example and component: the index is
@@ -145,8 +145,8 @@
</x-livewire-material::menu> </x-livewire-material::menu>
@endif @endif
<span class="ms-2 me-3 max-md:hidden"><x-livewire-material::theme-toggle mode="picker" /></span> <span class="ms-2 me-3 max-expanded:hidden"><x-livewire-material::theme-toggle mode="picker" /></span>
<span class="md:hidden"><x-livewire-material::theme-toggle mode="cycle" /></span> <span class="expanded:hidden"><x-livewire-material::theme-toggle mode="cycle" /></span>
</x-slot:actions> </x-slot:actions>
</x-livewire-material::app-bar> </x-livewire-material::app-bar>
</x-slot:top> </x-slot:top>
@@ -77,7 +77,7 @@
</x-button-group> </x-button-group>
BLADE, BLADE,
'A choice as a connected 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="[ <x-group label="Theme" name="showcase-theme" x-model="theme" :options="[
['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'], ['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'],
['id' => 'dark', 'name' => 'Dark', 'icon' => 'dark_mode'], ['id' => 'dark', 'name' => 'Dark', 'icon' => 'dark_mode'],
@@ -38,7 +38,7 @@
<x-showcase::example :$title :$code /> <x-showcase::example :$title :$code />
@endforeach @endforeach
<div class="grid gap-4 lg:grid-cols-2"> <div class="grid gap-4 expanded:grid-cols-2">
@foreach (['light', 'dark'] as $theme) @foreach (['light', 'dark'] as $theme)
<div data-theme="{{ $theme }}" class="space-y-6 rounded-corner-lg bg-surface p-4 text-on-surface"> <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> <h3 class="type-title-md capitalize">{{ $theme }}</h3>
@@ -47,7 +47,7 @@
<div class="space-y-2"> <div class="space-y-2">
<h4 class="type-label-lg text-on-surface-variant">{{ $group }}</h4> <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) @foreach ($roles as $role)
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span @class(['size-8 shrink-0 rounded-corner-sm border border-outline-variant', $role])></span> <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" /> <x-alert description="Settings saved." color="success" class="w-full" />
BLADE, BLADE,
'Stats' => <<<'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="Shares" value="1,204" icon="link" description="12 this week" />
<x-stat title="Files" value="8,317" icon="description" /> <x-stat title="Files" value="8,317" icon="description" />
<x-stat title="Stored" value="3.2 GB" icon="hard_drive" description="of 4 GB" /> <x-stat title="Stored" value="3.2 GB" icon="hard_drive" description="of 4 GB" />
@@ -1,7 +1,7 @@
@php @php
$examples = [ $examples = [
'Cards' => <<<'BLADE' '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"> <x-card title="Filled" subtitle="surface-container-highest">
The default card. The default card.
<x-slot:actions><x-button label="Open" /></x-slot:actions> <x-slot:actions><x-button label="Open" /></x-slot:actions>
@@ -27,7 +27,7 @@
</x-card> </x-card>
BLADE, BLADE,
'Lists' => <<<'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 dividers label="Files">
<x-list-item title="holiday-photos.zip" description="248 MB" icon="folder_zip" trailing="3 days" /> <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" /> <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. Five shadow levels as <code>shadow-elevation-*</code>, for what floats over content.
</p> </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) @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])> <div @class(['flex h-24 items-center justify-center rounded-corner-md bg-surface-container-high', $shadow])>
<code class="type-label-md">{{ $shadow }}</code> <code class="type-label-md">{{ $shadow }}</code>
@@ -1,7 +1,7 @@
@php @php
$examples = [ $examples = [
'Outlined and filled text fields' => <<<'BLADE' '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"> <div class="grid content-start gap-4">
<x-input label="Share name" hint="Recipients see this name" /> <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" /> <x-input label="Recipient" icon="mail" type="email" placeholder="name@example.com" clearable value="anna@example.com" />
@@ -22,7 +22,7 @@
</div> </div>
BLADE, BLADE,
'Errors, required and sizes' => <<<'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"> <div class="grid content-start gap-4">
<x-input label="Email" required /> <x-input label="Email" required />
<x-field id="showcase-error" label="Expiry" :messages="['Choose a date in the future.']"> <x-field id="showcase-error" label="Expiry" :messages="['Choose a date in the future.']">
@@ -42,7 +42,7 @@
</div> </div>
BLADE, BLADE,
'Textarea and select' => <<<'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"> <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-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]]" /> <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> </div>
BLADE, BLADE,
'Checkboxes' => <<<'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"> <div class="grid content-start gap-4">
<x-checkbox label="Notify me on download" checked /> <x-checkbox label="Notify me on download" checked />
<x-checkbox label="Select all files" indeterminate /> <x-checkbox label="Select all files" indeterminate />
@@ -75,13 +75,13 @@
</div> </div>
BLADE, BLADE,
'Radio buttons' => <<<'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="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" /> <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> </div>
BLADE, BLADE,
'Switches' => <<<'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"> <div class="grid content-start gap-4">
<x-toggle label="Email notifications" checked /> <x-toggle label="Email notifications" checked />
<x-toggle label="Weekly summary" /> <x-toggle label="Weekly summary" />
@@ -99,7 +99,7 @@
</div> </div>
BLADE, BLADE,
'Choices' => <<<'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"> <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="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']]" /> <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> </div>
BLADE, BLADE,
'Search' => <<<'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: '' }"> <div x-data="{ query: '' }">
<x-search x-model="query" placeholder="Search shares"> <x-search x-model="query" placeholder="Search shares">
<x-list> <x-list>
@@ -1,7 +1,7 @@
@php @php
$examples = [ $examples = [
'Docked' => <<<'BLADE' '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"> <div class="grid content-start gap-4">
<x-datepicker label="Expires on" clearable x-model="expires" hint="Type a date or pick one" /> <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> <p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(expires)"></code></p>
@@ -14,7 +14,7 @@
</div> </div>
BLADE, BLADE,
'Modal and modal input' => <<<'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"> <div class="grid content-start gap-4">
<x-datepicker label="Birthday" mode="modal" x-model="birthday" :max="now()" /> <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> <p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(birthday)"></code></p>
@@ -27,7 +27,7 @@
</div> </div>
BLADE, BLADE,
'Range' => <<<'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"> <div class="grid content-start gap-4">
<x-datepicker label="Trip" range x-model="trip" /> <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> <p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(trip)"></code></p>
@@ -40,7 +40,7 @@
</div> </div>
BLADE, BLADE,
'First day of the week and format' => <<<'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"> <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" /> <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> <p class="type-body-sm text-on-surface-variant">Bound value: <code x-text="JSON.stringify(race)"></code></p>
@@ -53,7 +53,7 @@
</div> </div>
BLADE, BLADE,
'Limits, errors and states' => <<<'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"> <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="Within the next 30 days" :min="now()" :max="now()->addDays(30)" hint="Days outside are disabled" />
<x-datepicker label="Required" required /> <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>. The corner scale as <code>rounded-corner-*</code>, and M3 Expressive's shapes as <code>&lt;x-shape name="…" /&gt;</code>.
</p> </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) @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="space-y-2">
<div @class(['h-20 bg-primary-container', $corner])></div> <div @class(['h-20 bg-primary-container', $corner])></div>
@@ -14,7 +14,7 @@
@endforeach @endforeach
</div> </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) @foreach (\NoNameWeb\LivewireMaterial\Support\SvgFile::shapeNames() as $shape)
<div class="space-y-2 text-center"> <div class="space-y-2 text-center">
<x-livewire-material::shape :name="$shape" class="mx-auto size-20 text-secondary-container" /> <x-livewire-material::shape :name="$shape" class="mx-auto size-20 text-secondary-container" />
@@ -1,7 +1,7 @@
@php @php
$examples = [ $examples = [
'Standard, discrete and disabled' => <<<'BLADE' '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="Volume" value="40" hint="Drag the handle or use the arrow keys" />
<x-slider label="Quality" value="6" :max="10" ticks /> <x-slider label="Quality" value="6" :max="10" ticks />
<x-slider label="Locked" value="70" disabled /> <x-slider label="Locked" value="70" disabled />
@@ -17,20 +17,20 @@
</div> </div>
BLADE, BLADE,
'Range and centred' => <<<'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="Price" :value="[20, 60]" range ticks :step="10" hint="Handles never cross" />
<x-slider label="Balance" value="15" :min="-50" :max="50" centered /> <x-slider label="Balance" value="15" :min="-50" :max="50" centered />
</div> </div>
BLADE, BLADE,
'Colours and value labels' => <<<'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="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="Tertiary, no label" value="35" color="tertiary" value-label="never" size="sm" />
<x-slider label="Error" :value="[30, 70]" color="error" range size="sm" /> <x-slider label="Error" :value="[30, 70]" color="error" range size="sm" />
</div> </div>
BLADE, BLADE,
'Bound with x-model' => <<<'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"> <div class="space-y-2">
<x-slider label="Bound volume" x-model="volume" /> <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> <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 @php
$examples = [ $examples = [
'12 and 24 hours, outlined and filled' => <<<'BLADE' '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"> <div class="grid content-start gap-4">
<x-timepicker label="Meeting starts" value="09:30" hint="The locale's clock" /> <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 /> <x-timepicker label="Pick-up" value="17:15" format="12" icon="local_shipping" clearable />
@@ -16,7 +16,7 @@
</div> </div>
BLADE, BLADE,
'Steps and limits' => <<<'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"> <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="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" /> <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"> <div class="divide-y divide-divider rounded-corner-lg bg-surface-container">
@foreach ($styles as [$regular, $emphasized]) @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> <code class="type-label-md text-on-surface-variant">{{ $regular }}</code>
<p @class([$regular, 'truncate'])>Share files, safely</p> <p @class([$regular, 'truncate'])>Share files, safely</p>
<p @class([$emphasized, '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:actions>
<x-slot:top> <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"> <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="sm:hidden"><x-livewire-material::button icon="menu" tooltip="Open navigation" x-data x-on:click="$store.rail.show()" data-test="shell-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="shell-menu" /></span>
<h1 class="min-w-0 flex-1 truncate px-3 type-title-lg sm:px-0">{{ $current['title'] }}</h1> <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" /> <x-livewire-material::button icon="search" tooltip="Search" />
</header> </header>
</x-slot:top> </x-slot:top>