Ask breakpoints.js, not Tailwind's screens, for a window class

Plan step 16. `navigation.js` takes its standard-rail threshold from
`from('expanded')` (840px) instead of the 64rem it had, and `navigation.css`'s
three 64rem queries follow, so the whole expanded class gets the in-layout
collapsible rail M3 asks for rather than one that opens over a scrim (N-06);
its two 40rem queries become 37.5rem, the compact/medium boundary the bar's own
container query already used (N-07). `search.js` and `datepicker.js` swap their
39.99rem media strings for `upTo('medium')`, so the full-screen search view and
the modal date picker end at 600px, not 640px.

The time picker's landscape layout stops keying on width at all: M3 swaps it on
orientation and viewport height, so it is now landscape plus a window too short
for the upright dial (35rem, the dialog's own height), and the two dial-shrink
rules key on height alone.

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:06:35 +02:00
co-authored by Claude Fable 5.1
parent b1642aed1c
commit 17723d2a76
5 changed files with 45 additions and 28 deletions
+4 -3
View File
@@ -4,7 +4,8 @@
*
* The dialog is shown two ways. Docked, it is a `popover="manual"` placed under the field by CSS
* anchor positioning; modal (the `modal` and `input` modes, and a docked picker on a compact
* window) it is opened with `showModal()`. Everything a person sees in it is drawn by Alpine from
* window — below `medium`, 600px, where M3 puts the picker in a dialog rather than under the
* field) it is opened with `showModal()`. Everything a person sees in it is drawn by Alpine from
* the state below, and the dialog is `wire:ignore`, so a Livewire render never touches it while
* it is open.
*
@@ -20,7 +21,7 @@
* Enter selects and confirms, Escape closes and hands focus back to the field. Focus never leaves
* the `min``max` span, so a disabled day cannot be chosen from the keyboard either.
*/
const COMPACT = '(max-width: 39.99rem)'
import { upTo } from './breakpoints.js'
// CLDR's first day of the week by region, for an engine without `Intl.Locale#getWeekInfo`.
const WEEK_STARTS_SUNDAY = 'AG AS BD BR BS BT BW BZ CA CN CO DM DO ET GT GU HK HN ID IL IN JM JP KE KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PT PY SA SG SV TH TT TW UM US VE VI WS YE ZA ZW'.split(' ')
@@ -222,7 +223,7 @@ document.addEventListener('alpine:init', () => {
}
})
const query = window.matchMedia(COMPACT)
const query = upTo('medium')
this.compact = query.matches
query.addEventListener('change', (event) => (this.compact = event.matches))
+6 -5
View File
@@ -13,7 +13,7 @@
* `materialNavigationRail` is one rail's view of the store for its `mode` — see
* resources/views/components/navigation-rail.blade.php.
*/
const WIDE = '(min-width: 64rem)'
import { from } from './breakpoints.js'
/*
* The active indicator grows out of its centre when a page arrives through wire:navigate. The
@@ -86,7 +86,7 @@ document.addEventListener('alpine:init', () => {
document.addEventListener('livewire:navigating', () => window.Alpine.store('rail').hide())
window.Alpine.data('materialNavigationRail', (mode) => ({
wide: mode === 'adaptive' ? window.matchMedia(WIDE).matches : false,
wide: mode === 'adaptive' ? from('expanded').matches : false,
query: null,
onWidth: null,
@@ -95,9 +95,10 @@ document.addEventListener('alpine:init', () => {
return
}
// From lg the adaptive rail is a standard, collapsible rail: a modal left open while
// the window widens is shut, or its focus trap would hold a page that has no scrim.
this.query = window.matchMedia(WIDE)
// From `expanded` (840px) the adaptive rail is a standard, collapsible rail — what M3
// asks for at expanded and above. A modal left open while the window widens is shut,
// or its focus trap would hold a page that has no scrim.
this.query = from('expanded')
this.onWidth = (event) => {
this.wide = event.matches
+5 -5
View File
@@ -4,11 +4,11 @@
* Focus or a press on the bar opens the view; Escape, the back arrow, a press outside, focus
* leaving the search, or choosing a result closes it. ArrowDown from the input moves into the
* results and the arrow keys walk them; ArrowUp past the first result returns to the input. On a
* compact window (below `sm`) the view is full screen and modal: focus stays in it and the page
* behind does not scroll. The results themselves are the caller's, rendered by Livewire into the
* view as the query changes.
* compact window (below `medium`, 600px) the view is full screen and modal: focus stays in it and
* the page behind does not scroll — M3 docks the view from medium upwards. The results themselves
* are the caller's, rendered by Livewire into the view as the query changes.
*/
const COMPACT = '(max-width: 39.99rem)'
import { upTo } from './breakpoints.js'
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
@@ -26,7 +26,7 @@ document.addEventListener('alpine:init', () => {
closedAt: -Infinity,
init() {
const query = window.matchMedia(COMPACT)
const query = upTo('medium')
this.compact = query.matches
query.addEventListener('change', (event) => (this.compact = event.matches))