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`
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
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
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
`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: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,
// The FAB, below sm 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,
// The FAB, on a compact window only: an extended FAB in the thumb zone, clear of a bottom bar the layout declares.
'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';
@@ -210,12 +212,12 @@
@if ($icon)
<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>
@endif
@unless ($iconOnly)
<span @class(['max-lg:hidden' => $responsive])>{{ $label ?? $slot }}</span>
<span @class(['max-expanded:hidden' => $responsive])>{{ $label ?? $slot }}</span>
@endunless
@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
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
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
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,
elevation 1; `side` `end` (the default) or `start`; `width` from `sm` (a caller's `w-*` would
race the sheet's own).
elevation 1; `side` `end` (the default) or `start`; `width` from `medium` (a caller's `w-*`
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
an `xl:flex xl:items-start xl:gap-6` row, the drawer sticks under the top of the viewport, the
As a pane (`pane`, from `expanded`) nothing is covered: the page renders the drawer after its
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
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`
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:`),
@@ -35,7 +39,7 @@
'withoutBackdropClose' => false,
'width' => '25rem',
'pane' => false,
'paneWidth' => null,
'paneWidth' => '22.5rem',
'paneCloseOnEscape' => false,
])
@@ -52,7 +56,7 @@
close() { this.open = typeof this.open === 'boolean' ? false : null; },
@if ($pane)
init() {
const query = window.matchMedia('(min-width: 80rem)');
const query = window.matchMedia('(width >= 52.5rem)');
this.wide = query.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
data-sheet="{{ $id }}"
@if ($pane)
x-bind:class="! open && 'xl:hidden'"
class="xl:sticky xl:top-[calc(var(--material-safe-top,env(safe-area-inset-top))+1.25rem)] xl:shrink-0 xl:self-start"
x-bind:class="! open && 'expanded:hidden'"
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
@endif
>
@@ -71,7 +75,7 @@
x-show="open"
x-transition.opacity.duration.200ms
@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"
></div>
@@ -80,24 +84,24 @@
x-show="open"
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-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: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-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 }}"
x-bind:role="wide ? 'region' : 'dialog'"
x-bind:aria-modal="wide ? null : 'true'"
role="dialog"
aria-modal="true"
@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([
'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,
'start-0 sm:rounded-e-corner-lg' => $start,
'sm:w-(--sheet-width) sm: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,
'end-0 medium:rounded-s-corner-lg' => ! $start,
'start-0 medium:rounded-e-corner-lg' => $start,
'medium:w-(--sheet-width) medium:max-w-[calc(100vw-4rem)]',
'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'),
]) }}
>
+10 -9
View File
@@ -19,7 +19,8 @@
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
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
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',
'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',
'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'),
])
>
<div @class([
'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,
])>
@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()" />
@if (filled($title))
@@ -77,20 +78,20 @@
</div>
@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)
{{-- 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)
<x-livewire-material::icon :name="$icon" class="mx-auto mb-4 size-6 text-secondary" />
@endif
@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
@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
@if ($separator)
@@ -105,7 +106,7 @@
@isset($actions)
<div @class([
'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 }}
</div>
@@ -1,7 +1,7 @@
{{-- M3 Expressive's flexible navigation bar: three to five destinations at the bottom of a
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-item label="Inbox" icon="inbox" link="/inbox" active badge="12" />
<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
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`,
`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).
Errors are read from the bag under the `wire:model` name
(resources/css/components/selection.css). --}}
@@ -36,7 +37,7 @@
<legend class="mb-3 type-label-lg text-on-surface-variant">{{ $label }}</legend>
@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)
@php($optionHintText = data_get($option, $optionHint))
@@ -34,7 +34,7 @@
<legend class="mb-2 type-label-lg text-on-surface-variant">{{ $label }}</legend>
@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)
<label
data-scheme-option="{{ $profile }}"
@@ -2,8 +2,10 @@
single destination in the app's own navigation.
`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
are M3's secondary tabs as links, the current one underlined; below `sm`, where a row of them
(an item is current when `active` is true, or when its `url` is the page's). From `medium`
(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
marked `aria-current="page"` and each with its badge. The same list is
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
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
a tablet. `label` names the navigation ("Sections"). Links use `wire:navigate` unless
`no-wire-navigate`. --}}
@@ -29,15 +32,15 @@
$current = collect($items)->first($isCurrent) ?? ($items[0] ?? null);
$layout = match (true) {
count($items) < 5 => 'sm:flex',
count($items) < 7 => 'sm:grid sm:grid-cols-3 xl:flex',
default => 'sm:grid sm:grid-cols-4 xl:flex',
count($items) < 5 => 'medium:flex',
count($items) < 7 => 'medium:grid medium:grid-cols-3 large:flex',
default => 'medium:grid medium:grid-cols-4 large:flex',
};
@endphp
<div {{ $attributes->class(['min-w-0']) }} data-section-nav>
@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-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">
@@ -56,7 +59,7 @@
</div>
@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 }}">
@foreach ($items as $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,
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
`--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'])
@@ -35,7 +36,7 @@
{{ $attributes->class([
'pointer-events-none fixed inset-x-4 z-50 flex bottom-[calc(var(--material-bottom-bar,0px)+1rem)]',
'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">
@@ -48,7 +49,7 @@
x-on:mouseleave="resume()"
x-on:focusin="pause()"
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)">
<span class="flex shrink-0" x-bind:class="{
@@ -47,7 +47,7 @@
</head>
<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">
<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">
<x-livewire-material::shape :name="trim($__env->yieldContent('shape', 'cookie-7'))" class="size-full text-primary-container" />
</div>
@@ -55,7 +55,7 @@
<p data-error-code class="relative type-emphasized-display-lg text-on-primary-container tabular-nums">@yield('code')</p>
</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')
@yield('headline')
@else
@@ -12,8 +12,8 @@
@if ($paginator->hasPages())
<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">
<span class="sm: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="medium:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span>
<span class="max-medium:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span>
</p>
<div class="flex items-center gap-1">
@@ -29,15 +29,15 @@
@foreach ($elements as $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
@if (is_array($element))
@foreach ($element as $page => $url)
@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
<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
@endforeach
@endif
@@ -28,8 +28,8 @@
@if ($paginator->hasPages())
<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">
<span class="sm: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="medium:hidden">{{ __('Page :page of :pages', ['page' => $paginator->currentPage(), 'pages' => $paginator->lastPage()]) }}</span>
<span class="max-medium:hidden">{{ __(':first:last of :total', ['first' => $paginator->firstItem(), 'last' => $paginator->lastItem(), 'total' => $paginator->total()]) }}</span>
</p>
<div class="flex items-center gap-1">
@@ -45,12 +45,12 @@
@foreach ($elements as $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
@if (is_array($element))
@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())
<span aria-current="page" class="{{ $step }} bg-secondary-container text-on-secondary-container">{{ $page }}</span>
@else
+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>