Give the app shell one shape per M3 window size class
Plan step 17, on N-06, N-07 and C-07. The shell now changes at 600, 840 and 1200 and nowhere else: a compact window keeps the navigation bar and the modal rail; `medium` (600-839) gets the collapsed rail in the layout and no bar; `expanded` (840-1199) gets a standard rail, collapsed, whose menu button expands it in place rather than over a scrim; `large` and above start it expanded, which is what M3 prefers once there is room. `data-rail` alone could not say "collapsed at expanded, expanded at large", since it carries `rail.default` for a visitor who never chose. <x-theme-script> now also writes `data-rail-auto` while nothing is stored, the `rail-collapsed:` variant reads it in the 840-1199 band, and `$store.rail.auto` mirrors it for Alpine; the first press of the menu button drops it, so a remembered choice still wins in both bands. `rail.default` and the rest of `$store.rail` are unchanged, and the attribute rides through `wire:navigate` with the others. `--material-margin` carries M3's window margin on the shell -- 16px compact, 24px from `medium` -- and the content region is padded with it, so the showcase pages drop their own gutters. 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
17723d2a76
commit
fd1e063d4c
+44
-11
@@ -5,6 +5,9 @@
|
||||
* localStorage. <x-theme-script> has already applied it before the first paint as
|
||||
* <html data-rail="expanded|collapsed">, which is what the stylesheet keys on (the
|
||||
* `rail-collapsed:` variant); the store starts from that attribute and writes it back.
|
||||
* `$store.rail.auto` is true while nothing is stored — the value is `rail.default`, not a choice —
|
||||
* and the adaptive rail then takes its window size class's default instead: collapsed in the
|
||||
* expanded class (840–1199), expanded from `large` (1200), as M3 asks. The first `set()` drops it.
|
||||
*
|
||||
* `$store.rail.open` is the modal rail: on a window too narrow for an expanded rail, a menu
|
||||
* button opens it over a scrim (`show()`), and Escape, the scrim or leaving the page closes it
|
||||
@@ -48,6 +51,10 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
window.Alpine.store('rail', {
|
||||
collapsed: root.dataset.rail === 'collapsed',
|
||||
|
||||
// Nothing stored yet: `collapsed` is only `rail.default`, so an adaptive rail may still
|
||||
// take its window size class's own default. The first choice made here clears it.
|
||||
auto: root.hasAttribute('data-rail-auto'),
|
||||
open: false,
|
||||
|
||||
toggle() {
|
||||
@@ -64,7 +71,9 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
set(collapsed) {
|
||||
this.collapsed = collapsed
|
||||
this.auto = false
|
||||
root.dataset.rail = collapsed ? 'collapsed' : 'expanded'
|
||||
root.removeAttribute('data-rail-auto')
|
||||
|
||||
try {
|
||||
localStorage.setItem(root.dataset.railKey || 'material-rail', root.dataset.rail)
|
||||
@@ -87,8 +96,9 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
window.Alpine.data('materialNavigationRail', (mode) => ({
|
||||
wide: mode === 'adaptive' ? from('expanded').matches : false,
|
||||
query: null,
|
||||
onWidth: null,
|
||||
roomy: mode === 'adaptive' ? from('large').matches : false,
|
||||
queries: [],
|
||||
listeners: [],
|
||||
|
||||
init() {
|
||||
if (mode !== 'adaptive') {
|
||||
@@ -98,19 +108,30 @@ document.addEventListener('alpine:init', () => {
|
||||
// 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
|
||||
this.watch(from('expanded'), (matches) => {
|
||||
this.wide = matches
|
||||
|
||||
if (event.matches) {
|
||||
if (matches) {
|
||||
this.$store.rail.hide()
|
||||
}
|
||||
}
|
||||
this.query.addEventListener('change', this.onWidth)
|
||||
})
|
||||
|
||||
// From `large` (1200px) it starts expanded rather than collapsed, until someone
|
||||
// chooses otherwise; below that the expanded class starts it collapsed. Both mirror
|
||||
// the `rail-collapsed:` variant in resources/css/components/navigation.css.
|
||||
this.watch(from('large'), (matches) => (this.roomy = matches))
|
||||
},
|
||||
|
||||
watch(query, onChange) {
|
||||
const listener = (event) => onChange(event.matches)
|
||||
|
||||
query.addEventListener('change', listener)
|
||||
this.queries.push(query)
|
||||
this.listeners.push(listener)
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this.query?.removeEventListener('change', this.onWidth)
|
||||
this.queries.forEach((query, index) => query.removeEventListener('change', this.listeners[index]))
|
||||
},
|
||||
|
||||
/** Whether this rail expands over a scrim rather than in the layout. */
|
||||
@@ -127,7 +148,17 @@ document.addEventListener('alpine:init', () => {
|
||||
return true
|
||||
}
|
||||
|
||||
return (mode === 'collapsible' || (mode === 'adaptive' && this.wide)) && !this.$store.rail.collapsed
|
||||
if (this.$store.rail.collapsed) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (mode === 'adaptive') {
|
||||
// A standard rail from `expanded`; with no choice stored it is the window size
|
||||
// class that decides, and only `large` and above start it expanded.
|
||||
return this.wide && (this.roomy || !this.$store.rail.auto)
|
||||
}
|
||||
|
||||
return mode === 'collapsible'
|
||||
},
|
||||
|
||||
/** The rail's own menu button: open or close the modal, or collapse and expand in place. */
|
||||
@@ -135,7 +166,9 @@ document.addEventListener('alpine:init', () => {
|
||||
if (this.modal) {
|
||||
this.$store.rail.open ? this.$store.rail.hide() : this.$store.rail.show()
|
||||
} else {
|
||||
this.$store.rail.toggle()
|
||||
// `set`, not `toggle`: with nothing stored the store's `collapsed` is only
|
||||
// `rail.default`, so the button has to flip what is actually drawn.
|
||||
this.$store.rail.set(this.expanded)
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user