Let the navigation bar hide on a scroll down
Plan step 25 (navigation Missing): M3's "hides on scroll-down, reappears on scroll-up" had no implementation. `<x-navigation-bar hide-on-scroll>` slides the bar out on the default spatial spring — an instant swap under reduced motion, which zeroes the duration token — and never while a snackbar, a bottom sheet or a drawer is anchored to its edge; focus reaching the bar brings it back. `<x-app-shell hide-bar-on-scroll>` picks it, and --material-bottom-bar follows the bar down and up so a `fab` button and the snackbar keep their offsets. 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
4d00d87cd2
commit
ce1cd6eec0
@@ -15,6 +15,8 @@
|
||||
*
|
||||
* `materialNavigationRail` is one rail's view of the store for its `mode` — see
|
||||
* resources/views/components/navigation-rail.blade.php.
|
||||
*
|
||||
* `materialNavigationBar` is `<x-navigation-bar hide-on-scroll>` — see the same file's sibling.
|
||||
*/
|
||||
import { from } from './breakpoints.js'
|
||||
|
||||
@@ -172,4 +174,66 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
window.Alpine.data('materialNavigationBar', () => ({
|
||||
away: false,
|
||||
last: 0,
|
||||
frame: null,
|
||||
// Set in init(), so a second bar in the same page scope cannot take the first one's.
|
||||
schedule: null,
|
||||
|
||||
init() {
|
||||
this.last = Math.max(window.scrollY, 0)
|
||||
this.measure = this.measure.bind(this)
|
||||
this.schedule = () => {
|
||||
this.frame ??= requestAnimationFrame(this.measure)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', this.schedule, { passive: true })
|
||||
},
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('scroll', this.schedule)
|
||||
cancelAnimationFrame(this.frame)
|
||||
},
|
||||
|
||||
/** Anything that reaches the bar — the keyboard, a screen reader's focus — brings it back. */
|
||||
show() {
|
||||
this.away = false
|
||||
},
|
||||
|
||||
measure() {
|
||||
this.frame = null
|
||||
|
||||
const at = Math.max(window.scrollY, 0)
|
||||
const by = at - this.last
|
||||
|
||||
// Smaller than a finger's jitter, or the rubber band at either end of the page: not a
|
||||
// direction yet, and the bar should not flicker while one is being decided.
|
||||
if (Math.abs(by) < 8) {
|
||||
return
|
||||
}
|
||||
|
||||
this.last = at
|
||||
|
||||
// A bar that is not on screen at this width (the shell hides it from `medium`) has no
|
||||
// scroll behaviour to have; one with something anchored to its edge keeps still, so
|
||||
// the snackbar or sheet resting on it does not slide with it.
|
||||
if (this.$root.getClientRects().length === 0 || anchored()) {
|
||||
this.away = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Never before the first screenful: the bar has to be passed before it can be left.
|
||||
this.away = by > 0 && at > this.$root.offsetHeight
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* Whether something on screen is anchored to the bar's edge and would be dragged along with it: a
|
||||
* snackbar (`<x-toast>`'s, which reads --material-bottom-bar), or a bottom sheet or drawer over the
|
||||
* page. M3 lets those cover the bar; it is the bar leaving from under them that looks broken.
|
||||
*/
|
||||
const anchored = () => [...document.querySelectorAll('[data-toast], [role="dialog"]')].some((over) => over.getClientRects().length > 0)
|
||||
|
||||
Reference in New Issue
Block a user