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
@@ -99,7 +99,7 @@ it('lifts the snackbar above the bar only when there is a bar', function () {
|
||||
|
||||
it('passes M3\'s tall bar through, and the bottom offset grows with it', function () {
|
||||
expect((string) $this->blade('<x-app-shell :destinations="$destinations" tall-bar />', ['destinations' => shellDestinations()]))
|
||||
->toContain('data-navigation-bar data-tall ')
|
||||
->toMatch('/data-navigation-bar\s+data-tall\s/')
|
||||
->toContain('max-medium:[--material-bottom-bar:calc(5rem+')
|
||||
->not->toContain('[--material-bottom-bar:calc(4rem+')
|
||||
->and((string) $this->blade('<x-app-shell :destinations="$destinations" />', ['destinations' => shellDestinations()]))
|
||||
@@ -107,6 +107,14 @@ it('passes M3\'s tall bar through, and the bottom offset grows with it', functio
|
||||
->toContain('max-medium:[--material-bottom-bar:calc(4rem+');
|
||||
});
|
||||
|
||||
it('lets the bar leave the window while the page scrolls down', function () {
|
||||
expect((string) $this->blade('<x-app-shell :destinations="$destinations" hide-bar-on-scroll />', ['destinations' => shellDestinations()]))
|
||||
->toContain('data-hide-on-scroll')
|
||||
->toContain('x-data="materialNavigationBar"')
|
||||
->and((string) $this->blade('<x-app-shell :destinations="$destinations" />', ['destinations' => shellDestinations()]))
|
||||
->not->toContain('data-hide-on-scroll');
|
||||
});
|
||||
|
||||
it('reads the safe area and anything docked on the bar through variables an application can set', function () {
|
||||
$html = (string) $this->blade('<x-app-shell :destinations="$destinations" />', ['destinations' => shellDestinations()]);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use Illuminate\Support\Str;
|
||||
|
||||
it('draws a navigation landmark around its items', function () {
|
||||
expect((string) $this->blade('<x-navigation-bar><x-navigation-bar-item label="Inbox" icon="inbox" link="/inbox" /></x-navigation-bar>'))
|
||||
->toContain('<nav aria-label="Main" data-navigation-bar')
|
||||
->toMatch('/<nav\s+aria-label="Main"\s+data-navigation-bar/')
|
||||
->toContain('data-navigation-bar-items')
|
||||
->and((string) $this->blade('<x-navigation-bar label="Sections" />'))->toContain('aria-label="Sections"');
|
||||
});
|
||||
@@ -63,7 +63,7 @@ it('keeps the bar item on the label and state-layer colours M3 tokens', function
|
||||
|
||||
it('takes M3\'s tall container, which keeps the vertical item layout at every width', function () {
|
||||
expect((string) $this->blade('<x-navigation-bar tall><x-navigation-bar-item label="Inbox" icon="inbox" link="/inbox" /></x-navigation-bar>'))
|
||||
->toContain('data-navigation-bar data-tall ')
|
||||
->toMatch('/data-navigation-bar\s+data-tall\s/')
|
||||
->and((string) $this->blade('<x-navigation-bar />'))->not->toContain('data-tall');
|
||||
|
||||
$css = file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css');
|
||||
@@ -77,3 +77,29 @@ it('takes M3\'s tall container, which keeps the vertical item layout at every wi
|
||||
->and(preg_match_all('/@container \(width [<>]=? 37\.5rem\) \{\n(.*?)\n \}/s', $css, $blocks) > 0)->toBeTrue()
|
||||
->and(implode("\n", $blocks[1]))->not->toMatch('/^ \[data-navigation-bar-item/m');
|
||||
});
|
||||
|
||||
it('hides on a scroll down and springs back on a scroll up', function () {
|
||||
expect((string) $this->blade('<x-navigation-bar hide-on-scroll />'))
|
||||
->toContain('data-hide-on-scroll')
|
||||
->toContain('x-data="materialNavigationBar"')
|
||||
->toContain('x-bind:data-hidden="away ? \'\' : null"')
|
||||
// Focus reaching the bar brings it back — the web's nearest answer to M3's "never hide it
|
||||
// while a screen reader is active".
|
||||
->toContain('x-on:focusin="show()"')
|
||||
->and((string) $this->blade('<x-navigation-bar />'))
|
||||
->not->toContain('data-hide-on-scroll')
|
||||
->not->toContain('materialNavigationBar');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
// The spatial spring, which reduced motion zeroes along with every other duration token.
|
||||
->toMatch('/\[data-navigation-bar\]\[data-hide-on-scroll\] \{\s+transition: translate var\(--md-sys-motion-spatial-default-duration\) var\(--md-sys-motion-spatial-default\);/')
|
||||
->toMatch('/\[data-navigation-bar\]\[data-hide-on-scroll\]\[data-hidden\] \{\s+translate: 0 100%;/')
|
||||
// Unlayered, or the utility <x-app-shell> publishes the offset with would win.
|
||||
->toContain('[data-app-shell]:has([data-navigation-bar][data-hide-on-scroll][data-hidden]) {')
|
||||
->and(Str::of(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))->after('[data-app-shell]:has(')->toString())
|
||||
->not->toContain('@layer');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/js/navigation.js'))
|
||||
// Never out from under a snackbar, a bottom sheet or a drawer resting on its edge.
|
||||
->toContain("document.querySelectorAll('[data-toast], [role=\"dialog\"]')");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user