Skip to content

Commit 80aaf84

Browse files
committed
Replace hamburger auto-close with in-place loading feedback
Pressing a menu link no longer closes the drawer. Instead the pressed row holds full opacity while the rest of the menu dims and a spinner shows at its trailing edge — mirroring the search modal's result loading treatment — so the drawer stays open until the new page takes over. Modified/new-tab clicks are left untreated, and the state is cleared on bfcache restore.
1 parent 08eba25 commit 80aaf84

2 files changed

Lines changed: 85 additions & 3 deletions

File tree

openlibrary/templates/lib/nav_head.html

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,36 @@
174174
drawer.addEventListener('ol-drawer-after-hide', function() {
175175
trigger.setAttribute('aria-expanded', 'false');
176176
});
177+
178+
// Pressing a menu link navigates the whole window; the next page can take
179+
// a beat to start painting. Flag the pressed row so it shows a spinner
180+
// and the rest dim back (mirrors the search modal's result loading
181+
// treatment) — the drawer stays open until the new page takes over.
177182
drawer.addEventListener('click', function(e) {
178-
if (e.target.closest('a[href], button[type="submit"], button:not([type])')) {
179-
drawer.open = false;
180-
}
183+
var el = e.target.closest('a[href], button[type="submit"], button:not([type])');
184+
if (!el) return;
185+
// New-tab / modified / non-left clicks don't navigate this window.
186+
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
187+
if (el.tagName === 'A' && el.target === '_blank') return;
188+
var menu = drawer.querySelector('.drawer-menu');
189+
if (menu) menu.classList.add('is-navigating');
190+
el.classList.add('is-target');
191+
// Mark the row too, so it stays lit while its siblings dim.
192+
var row = el.closest('li');
193+
if (row) row.classList.add('is-target-row');
194+
});
195+
196+
// Restoring from bfcache (back/forward) reuses this DOM with the spinner
197+
// still on the pressed row — clear it so the menu looks idle again.
198+
window.addEventListener('pageshow', function(e) {
199+
if (!e.persisted) return;
200+
var menu = drawer.querySelector('.drawer-menu');
201+
if (!menu) return;
202+
menu.classList.remove('is-navigating');
203+
Array.prototype.forEach.call(menu.querySelectorAll('.is-target, .is-target-row'), function(t) {
204+
t.classList.remove('is-target');
205+
t.classList.remove('is-target-row');
206+
});
181207
});
182208
}
183209
})();

static/css/components/header-bar.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,3 +837,59 @@
837837
background-color: var(--white);
838838
}
839839
}
840+
841+
/**
842+
* Navigating (pressed menu link → page loading)
843+
* When a link is pressed the whole window navigates, and the next page can
844+
* take a beat to paint. During that gap the pressed row holds full opacity
845+
* while the rest dim back and a spinner shows at its trailing edge. Mirrors
846+
* the search modal's result loading treatment. The `is-navigating` /
847+
* `is-target` classes are toggled by the inline script in nav_head.html.
848+
*/
849+
.drawer-menu.is-navigating .drawer-menu__subsection,
850+
.drawer-menu.is-navigating .drawer-menu__item,
851+
.drawer-menu.is-navigating .drawer-menu__login-links {
852+
opacity: 0.4;
853+
}
854+
855+
/* The row holding the pressed link stays lit. `is-target-row` is set on the
856+
<li> (not the link) because opacity on the row compounds onto its children,
857+
so lifting the link alone can't undo a dimmed parent. */
858+
.drawer-menu.is-navigating .is-target-row {
859+
opacity: 1;
860+
}
861+
862+
.drawer-menu .is-target {
863+
position: relative;
864+
/* Reserve room at the trailing edge so the spinner never overlaps the label. */
865+
padding-right: var(--spacing-2xl);
866+
}
867+
868+
/* Spinner: a currentcolor ring with one transparent edge, spun by keyframes
869+
(the same construction as the <ol-button> loading spinner). */
870+
.drawer-menu .is-target::after {
871+
content: "";
872+
box-sizing: border-box;
873+
position: absolute;
874+
top: 50%;
875+
right: var(--spacing-inset-sm);
876+
width: 16px;
877+
height: 16px;
878+
margin-top: -8px;
879+
border: 2px solid currentColor;
880+
border-right-color: transparent;
881+
border-radius: var(--border-radius-circle);
882+
animation: ol-drawer-menu-spin 0.7s linear infinite;
883+
}
884+
885+
@keyframes ol-drawer-menu-spin {
886+
to {
887+
transform: rotate(360deg);
888+
}
889+
}
890+
891+
@media (prefers-reduced-motion: reduce) {
892+
.drawer-menu .is-target::after {
893+
animation-duration: 2s;
894+
}
895+
}

0 commit comments

Comments
 (0)