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:
co-authored by
Claude Fable 5.1
parent
b1642aed1c
commit
17723d2a76
@@ -30,9 +30,16 @@
|
|||||||
*
|
*
|
||||||
* `rail-collapsed:` matches a rail, and everything in it, while it is drawn collapsed — whatever
|
* `rail-collapsed:` matches a rail, and everything in it, while it is drawn collapsed — whatever
|
||||||
* made it so: its mode, the visitor's choice on <html data-rail> (set before the first paint by
|
* made it so: its mode, the visitor's choice on <html data-rail> (set before the first paint by
|
||||||
* <x-theme-script>), or a window under `lg` for the adaptive rail. A rail item is written once
|
* <x-theme-script>), or a window below `expanded` (840px) for the adaptive rail. A rail item is written once
|
||||||
* and takes both shapes from it; so can anything an application puts in a rail
|
* and takes both shapes from it; so can anything an application puts in a rail
|
||||||
* (`<span class="rail-collapsed:hidden">`).
|
* (`<span class="rail-collapsed:hidden">`).
|
||||||
|
*
|
||||||
|
* The two widths the adaptive rail turns on are M3's window size classes, not Tailwind's screens
|
||||||
|
* (docs/reference/m3/foundations.md § Layout, foundations-supplement.md § Breakpoints): 37.5rem
|
||||||
|
* (600px) is the compact/medium boundary — below it the navigation bar carries the destinations and
|
||||||
|
* the rail takes no room in the layout; 52.5rem (840px) is `expanded`, where M3 asks for a standard
|
||||||
|
* rail in the layout rather than one that opens over a scrim. The bar's own item layout is a
|
||||||
|
* *container* query at the same 37.5rem, so a bar in a narrow column lays out by its own width.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@custom-variant rail-collapsed {
|
@custom-variant rail-collapsed {
|
||||||
@@ -48,13 +55,13 @@
|
|||||||
@slot;
|
@slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 64rem) {
|
@media (width < 52.5rem) {
|
||||||
&:where([data-navigation-rail='adaptive']:not([data-open]), [data-navigation-rail='adaptive']:not([data-open]) *) {
|
&:where([data-navigation-rail='adaptive']:not([data-open]), [data-navigation-rail='adaptive']:not([data-open]) *) {
|
||||||
@slot;
|
@slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >= 64rem) {
|
@media (width >= 52.5rem) {
|
||||||
&:where([data-rail='collapsed'] [data-navigation-rail='adaptive'], [data-rail='collapsed'] [data-navigation-rail='adaptive'] *) {
|
&:where([data-rail='collapsed'] [data-navigation-rail='adaptive'], [data-rail='collapsed'] [data-navigation-rail='adaptive'] *) {
|
||||||
@slot;
|
@slot;
|
||||||
}
|
}
|
||||||
@@ -176,18 +183,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* A modal rail keeps its collapsed width in the layout while it is open over it, as Compose's
|
/* A modal rail keeps its collapsed width in the layout while it is open over it, as Compose's
|
||||||
ModalWideNavigationRail does; the adaptive rail does below lg, and takes no room below sm. */
|
ModalWideNavigationRail does; the adaptive rail does below `expanded`, and takes no room on a compact window. */
|
||||||
[data-navigation-rail='modal'] {
|
[data-navigation-rail='modal'] {
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 64rem) {
|
@media (width < 52.5rem) {
|
||||||
[data-navigation-rail='adaptive'] {
|
[data-navigation-rail='adaptive'] {
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 40rem) {
|
@media (width < 37.5rem) {
|
||||||
[data-navigation-rail='adaptive'] {
|
[data-navigation-rail='adaptive'] {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
@@ -230,9 +237,9 @@
|
|||||||
box-shadow: var(--md-sys-elevation-2);
|
box-shadow: var(--md-sys-elevation-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Below sm there is no collapsed rail to grow out of: the open rail slides in from the edge,
|
/* On a compact window there is no collapsed rail to grow out of: the open rail slides in from the edge,
|
||||||
on emphasized decelerate rather than a spring, which would overshoot and open a gap. */
|
on emphasized decelerate rather than a spring, which would overshoot and open a gap. */
|
||||||
@media (width < 40rem) {
|
@media (width < 37.5rem) {
|
||||||
[data-navigation-rail='adaptive'] > [data-navigation-rail-panel] {
|
[data-navigation-rail='adaptive'] > [data-navigation-rail-panel] {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset-block: 0;
|
inset-block: 0;
|
||||||
|
|||||||
@@ -24,10 +24,18 @@
|
|||||||
* on by default): two toggle buttons 4px apart, round and surface-container-lowest when off, a 12px
|
* on by default): two toggle buttons 4px apart, round and surface-container-lowest when off, a 12px
|
||||||
* corner and primary-container with a bold label when on, not the outlined pair of the tokens.
|
* corner and primary-container with a bold label when on, not the outlined pair of the tokens.
|
||||||
*
|
*
|
||||||
* In a landscape window the dial variant lies on its side, as Compose's HorizontalTimePicker does
|
* In a landscape window with no room for the upright dial the dial variant lies on its side, as
|
||||||
* whenever the screen is wider than it is tall: the display with the period selector (216×38, 16px
|
* Compose's HorizontalTimePicker does: the display with the period selector (216×38, 16px under it)
|
||||||
* under it) on the start side, the dial 36px after it, the actions under both; and the dial shrinks
|
* on the start side, the dial 36px after it, the actions under both; and the dial shrinks to 238 or
|
||||||
* to 238 or 200px when the window is short (ClockFaceSizeModifier). The input variant always stands.
|
* 200px when the window is shorter still (ClockFaceSizeModifier). The input variant always stands.
|
||||||
|
*
|
||||||
|
* The switch is orientation and viewport *height*, never a width breakpoint: M3 says the picker
|
||||||
|
* "swaps orientation/variant based on device orientation and viewport height"
|
||||||
|
* (docs/reference/m3/components-navigation-selection-inputs.md § Time pickers/Behaviour), and no
|
||||||
|
* window size class applies. 35rem is what the upright dial needs — 33rem of surface (3 padding,
|
||||||
|
* 2.25 title, 7.25 display, 17.5 dial, 3 actions) inside the dialog's `100dvh - 2rem` — so a
|
||||||
|
* landscape phone lies the dial down and a landscape tablet or desktop, which has the height,
|
||||||
|
* leaves it standing.
|
||||||
*
|
*
|
||||||
* The selector's angle is a registered property, so a single transition turns the line, carries the
|
* The selector's angle is a registered property, so a single transition turns the line, carries the
|
||||||
* handle round and moves the clip that inks the number under it, on the default spatial spring. The
|
* handle round and moves the clip that inks the number under it, on the default spatial spring. The
|
||||||
@@ -400,7 +408,7 @@
|
|||||||
|
|
||||||
/* ---- Landscape ---------------------------------------------------------------------------- */
|
/* ---- Landscape ---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
@media (orientation: landscape) and (min-width: 37rem) {
|
@media (orientation: landscape) and (height < 35rem) {
|
||||||
[data-timepicker-surface][data-mode="dial"] {
|
[data-timepicker-surface][data-mode="dial"] {
|
||||||
grid-template-columns: 13.5rem auto;
|
grid-template-columns: 13.5rem auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
@@ -446,13 +454,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: landscape) and (min-width: 37rem) and (max-height: 22.75rem) {
|
@media (orientation: landscape) and (max-height: 22.75rem) {
|
||||||
[data-timepicker-surface][data-mode="dial"] [data-timepicker-dial] {
|
[data-timepicker-surface][data-mode="dial"] [data-timepicker-dial] {
|
||||||
--dial: 14.875rem;
|
--dial: 14.875rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: landscape) and (min-width: 37rem) and (max-height: 21.625rem) {
|
@media (orientation: landscape) and (max-height: 21.625rem) {
|
||||||
[data-timepicker-surface][data-mode="dial"] [data-timepicker-dial] {
|
[data-timepicker-surface][data-mode="dial"] [data-timepicker-dial] {
|
||||||
--dial: 12.5rem;
|
--dial: 12.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
*
|
*
|
||||||
* The dialog is shown two ways. Docked, it is a `popover="manual"` placed under the field by CSS
|
* 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
|
* 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
|
* the state below, and the dialog is `wire:ignore`, so a Livewire render never touches it while
|
||||||
* it is open.
|
* it is open.
|
||||||
*
|
*
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
* Enter selects and confirms, Escape closes and hands focus back to the field. Focus never leaves
|
* 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.
|
* 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`.
|
// 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(' ')
|
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
|
this.compact = query.matches
|
||||||
query.addEventListener('change', (event) => (this.compact = event.matches))
|
query.addEventListener('change', (event) => (this.compact = event.matches))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* `materialNavigationRail` is one rail's view of the store for its `mode` — see
|
* `materialNavigationRail` is one rail's view of the store for its `mode` — see
|
||||||
* resources/views/components/navigation-rail.blade.php.
|
* 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
|
* 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())
|
document.addEventListener('livewire:navigating', () => window.Alpine.store('rail').hide())
|
||||||
|
|
||||||
window.Alpine.data('materialNavigationRail', (mode) => ({
|
window.Alpine.data('materialNavigationRail', (mode) => ({
|
||||||
wide: mode === 'adaptive' ? window.matchMedia(WIDE).matches : false,
|
wide: mode === 'adaptive' ? from('expanded').matches : false,
|
||||||
query: null,
|
query: null,
|
||||||
onWidth: null,
|
onWidth: null,
|
||||||
|
|
||||||
@@ -95,9 +95,10 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// From lg the adaptive rail is a standard, collapsible rail: a modal left open while
|
// From `expanded` (840px) the adaptive rail is a standard, collapsible rail — what M3
|
||||||
// the window widens is shut, or its focus trap would hold a page that has no scrim.
|
// asks for at expanded and above. A modal left open while the window widens is shut,
|
||||||
this.query = window.matchMedia(WIDE)
|
// or its focus trap would hold a page that has no scrim.
|
||||||
|
this.query = from('expanded')
|
||||||
this.onWidth = (event) => {
|
this.onWidth = (event) => {
|
||||||
this.wide = event.matches
|
this.wide = event.matches
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
* Focus or a press on the bar opens the view; Escape, the back arrow, a press outside, focus
|
* 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
|
* 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
|
* 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
|
* compact window (below `medium`, 600px) the view is full screen and modal: focus stays in it and
|
||||||
* behind does not scroll. The results themselves are the caller's, rendered by Livewire into the
|
* the page behind does not scroll — M3 docks the view from medium upwards. The results themselves
|
||||||
* view as the query changes.
|
* 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"])'
|
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,
|
closedAt: -Infinity,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const query = window.matchMedia(COMPACT)
|
const query = upTo('medium')
|
||||||
|
|
||||||
this.compact = query.matches
|
this.compact = query.matches
|
||||||
query.addEventListener('change', (event) => (this.compact = event.matches))
|
query.addEventListener('change', (event) => (this.compact = event.matches))
|
||||||
|
|||||||
Reference in New Issue
Block a user