Skip to content

Add OlDrawer component for hamburger menu#12326

Open
lokesh wants to merge 20 commits into
internetarchive:masterfrom
lokesh:drawer-component
Open

Add OlDrawer component for hamburger menu#12326
lokesh wants to merge 20 commits into
internetarchive:masterfrom
lokesh:drawer-component

Conversation

@lokesh

@lokesh lokesh commented Apr 9, 2026

Copy link
Copy Markdown
Collaborator
drawer.mp4

Replaces the hamburger menu's <details>-based dropdown with a new ol-drawer Lit web component that slides in from the viewport edge.

Motivation: As we flatten stacking contexts across the site, overlays like drawers and popovers need to render reliably above all page content, including the header. The old dropdown was tied to the header's stacking context, which made that hard. The drawer renders at the document root, sidestepping the problem.

Improvements:

  • Slide-in and slide-out animation (the current menu doesn't animate closed)
  • Swipe-to-dismiss on touch devices
  • Focus trapping and background scroll lock, with no page shift when it opens
  • aria-expanded state on the hamburger trigger
  • Auto-closes when a link or button inside is clicked
  • Respects prefers-reduced-motion

Testing

  • Open hamburger menu on desktop, tablet, and mobile viewports
  • Check logged-out state shows Log In / Sign Up buttons

Stakeholders

@cdrini

lokesh and others added 5 commits April 8, 2026 21:58
Remove lightDismiss prop (always allow backdrop dismiss), tune animation
durations, improve swipe-to-close backdrop opacity calculation, and move
drawer to document.body in demo page. Simplify header_dropdown template
by removing hamburger-specific logic now handled by the drawer. Add
aria-expanded toggling and auto-close on link/button click to nav_head.
Fix drawer menu CSS: use design tokens, scope :hover behind media query,
fix login link underlines via :link/:visited specificity, and add
last-child border removal.
Replace the loop-based hamburgerLinks rendering with hardcoded markup.
Remove unused loginLinks, contributeLinks, and resourceLinks arrays.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the header hamburger menu’s <details> dropdown with a new ol-drawer Lit web component that renders as a root-level overlay (solving stacking-context/z-index issues) and adds a design pattern library page documenting the drawer.

Changes:

  • Added OlDrawer Lit component (<ol-drawer>) with backdrop, animation, focus trapping, scroll lock, and swipe-to-dismiss.
  • Reworked the header hamburger markup/CSS to use the new drawer + new light-DOM menu styles.
  • Added design system documentation/demo for the drawer and bumped the globals npm dependency.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
static/css/legacy.css Extends existing header icon sizing to the new hamburger markup; removes unused drawer/mask positioning rules.
static/css/components/header-bar.css Removes <details> hamburger drawer styles and adds new .hamburger-trigger + .drawer-menu* light-DOM styling.
static/css/components/header-bar--desktop.css Removes old drawer/mask sizing rules; updates icon sizing selectors.
package.json Bumps globals dependency version.
package-lock.json Updates lockfile for globals bump and related metadata normalization.
openlibrary/templates/site/head.html Hides <ol-drawer> until defined to prevent flash of unstyled content.
openlibrary/templates/lib/nav_head.html Replaces hamburger <details> dropdown with <ol-drawer> + inline wiring script.
openlibrary/templates/lib/header_dropdown.html Removes hamburger-specific rendering/mask/drawer logic now that hamburger is handled separately.
openlibrary/templates/design/drawer.html Adds drawer component documentation + live demos.
openlibrary/templates/design.html Adds “Drawer” section to the design system index and renders the new drawer template.
openlibrary/plugins/openlibrary/js/index.js Removes old hamburger mask click handler tied to the <details> implementation.
openlibrary/i18n/messages.pot Updates message catalog references/strings for the new drawer docs and hamburger markup.
openlibrary/components/lit/OlDrawer.js Introduces the new <ol-drawer> Lit component implementation.
openlibrary/components/lit/index.js Exports/registers OlDrawer in the Lit components entrypoint.

Comment thread openlibrary/templates/lib/nav_head.html Outdated
Comment on lines +174 to +179
var trigger = document.querySelector('.hamburger-trigger');
var drawer = document.getElementById('hamburger-drawer');
if (trigger && drawer) {
trigger.addEventListener('click', function() {
drawer.open = !drawer.open;
});

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ol-components.js (which defines ol-drawer) is loaded in the footer, but this click handler toggles the drawer via the open property immediately. If a user clicks before the custom element upgrades, this will set an expando property on the undefined element and the value will be lost on upgrade (menu appears to not open until a second click). Prefer toggling the open attribute (toggleAttribute/setAttribute/removeAttribute) or waiting for customElements.whenDefined('ol-drawer') before wiring the handler.

Copilot uses AI. Check for mistakes.
Comment thread openlibrary/components/lit/OlDrawer.js Outdated
Comment on lines +544 to +548
disconnectedCallback() {
super.disconnectedCallback();
this._removeListeners();
this._unlockBodyScroll();
}

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disconnectedCallback() unconditionally calls _unlockBodyScroll(), which will call window.scrollTo(0, this._savedScrollY) even if the drawer was never opened/locked. If the element is removed/reparented while closed, this can unexpectedly scroll the page to the top. Guard this so unlock/scroll restoration only happens if a scroll lock is currently active.

Copilot uses AI. Check for mistakes.
@mekarpeles

mekarpeles commented Apr 9, 2026

Copy link
Copy Markdown
Member

Thank you for this detailed PR, @lokesh! The description is clear and well-structured.

🤖 Copilot has been assigned for an initial review.

There's no assignee on this PR or a linked issue yet, so it will enter the general review queue. There are currently 76 open non-draft PRs ahead of yours.

Possible improvements for this PR

  • Missing issue reference — The Closes # in the PR body is unfilled. If this change addresses an existing issue, please link it. If not, consider opening one to track the motivation (z-index/stacking context cleanup) — it helps with prioritization and triage.
  • Screenshot or video needed — The ### Screenshot section is empty and the testing checklist items are unchecked. Since this introduces an animated UI component, a screenshot or short screen recording would significantly help reviewers verify the expected behavior. See how to attach files to a GitHub comment.
PR triage checklist (maintainers / Pam)
  • PR description — not empty; explains what the change does and how to verify it
  • References an issue — PR body contains a #NNN reference
    • Linked issue is triaged — has a Priority: * label (not just Needs: Triage)
    • Linked issue is assigned — has at least one assignee
  • Commit history clean — no WIP/fixup/conflict noise; commit messages are meaningful
  • CI passing — no failing check-runs
  • Test cases present — if the change touches substantive logic, test coverage exists or is explained
  • Proof of testing — PR body includes a description of what was tested, a screenshot, or a video

Note

This comment was automatically generated by Pam, Open Library's Project AI Manager, on behalf of @mekarpeles. Pam is designed to provide status visibility, perform basic project management functions and relevant codebase research, and provide actionable feedback so contributors aren't left waiting.

@mekarpeles mekarpeles added the Needs: Submitter Input Waiting on input from the creator of the issue/pr [managed] label Apr 9, 2026
Previously, the drawer panel itself received focus on open. Pressing
Tab then hit the start focus-sentinel, which incorrectly wrapped to
the last focusable element. Now the first focusable slotted element
is focused instead, so Tab moves forward naturally.
@github-actions github-actions Bot removed the Needs: Submitter Input Waiting on input from the creator of the issue/pr [managed] label Apr 9, 2026
lokesh and others added 4 commits April 8, 2026 23:54
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@mekarpeles

Copy link
Copy Markdown
Member

Thank you @lokesh! Having an issue for this PR would help with the review process. Assigning @cdrini for now

lokesh and others added 2 commits April 14, 2026 15:32
mekarpeles added a commit that referenced this pull request Apr 21, 2026
Previously _get_pr_drift only returned head_sha/drift/merged, so stale
titles ("PR #12326") and missing author/assignee persisted indefinitely.
Now on a cache miss, _get_drift_info also writes fresh metadata back to
the state file, making "Refresh Status" actually update what you see.
mekarpeles added a commit that referenced this pull request Apr 21, 2026
- Staged UX: Add/Fetch Latest/Enable/Disable/Remove all stage changes;
  only "Perform Deploy" applies them and triggers Jenkins
- Memcache drift cache (5-min TTL) to avoid hitting GitHub API on every
  page load; "Refresh Status" evicts the cache explicitly
- On cache miss, refreshes title/author/assignee from GitHub and writes
  back to state file so stale data ("PR #12326") self-corrects
- GitHub API token support via github_api_token in openlibrary.yml
- PR author + assignee with avatars (no extra API calls)
- Columns: Enabled (was Active), Pinned Commit with pending arrow,
  Branch HEAD, Drift, Added (with added_by tooltip)
- Jenkins job URL in deploy banner; rel="noopener noreferrer" on links
- Backward-compat: old bare-array state file format still loads cleanly
mekarpeles added a commit that referenced this pull request Apr 21, 2026
- Staged UX: Add/Fetch Latest/Enable/Disable/Remove all stage changes;
  only "Perform Deploy" applies them and triggers Jenkins
- Memcache drift cache (5-min TTL) to avoid hitting GitHub API on every
  page load; "Refresh Status" evicts the cache explicitly
- On cache miss, refreshes title/author/assignee from GitHub and writes
  back to state file so stale data ("PR #12326") self-corrects
- GitHub API token support via github_api_token in openlibrary.yml
- PR author + assignee with avatars (no extra API calls)
- Columns: Enabled (was Active), Pinned Commit with pending arrow,
  Branch HEAD, Drift, Added (with added_by tooltip)
- Jenkins job URL in deploy banner; rel="noopener noreferrer" on links
- Backward-compat: old bare-array state file format still loads cleanly
lokesh added 6 commits July 6, 2026 12:53
Resolve conflicts and update OlDrawer to adopted component conventions:
- Use shared scroll-lock.js and focus-utils.js (keydown Tab-trap like
  OlDialog) instead of hand-rolled scroll lock and focus sentinels
- Design tokens without fallbacks; rename --size to --ol-drawer-width,
  add --ol-drawer-* cssprops; use --z-index-modal and semantic colors
- Guard customElements.define; regenerate custom-elements manifest
- Move ol-drawer pre-hydration FOUC rule into ol-components.css
- Drop dead .app-drawer/.mask-menu CSS; instant hover on drawer login links
- Regenerate messages.pot
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.
Match the <ol-button> press affordance: menu links and the logout button
scale to 0.97 on :active with an 0.08s transform transition. Hover color
changes stay instant per docs/ai/design.md.
A fast toggle could set open=false after _show() scheduled its
updateComplete callback but before it ran. The callback then set
_animState back to 'entering', re-opening the drawer while open was
false — leaving it stuck open and scroll-locked, since Escape and
backdrop dismissal both gate on this.open. Bail out of the open
sequence if open is no longer true.
Pinning <body> to lock background scroll removes the document scrollbar;
on platforms with classic (space-consuming) scrollbars, reclaiming that
width shifted the page. Measure the scrollbar width before locking and
hold it as body padding (border-box) so the layout stays put. No-op on
overlay-scrollbar platforms where the width is 0.
lokesh added 2 commits July 9, 2026 23:53
When open was toggled true then false within the same tick (before the
enter transition was armed), _hide() moved the still-parked panel to
'exiting'. That's a closed→closed no-op transition that never fires
transitionend, so cleanup never ran and the drawer was left scroll-locked
with its keydown/touch listeners attached. Finish synchronously from the
'preparing' state, mirroring the reduced-motion path.
# Conflicts:
#	openlibrary/components/lit/index.js

@accesslint accesslint Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 3 issues across 3 rules (2 WCAG, 1 Best Practice).


$:render_template("lib/header_dropdown", hamburgerProps, track_prefix="Hamburger")
<div class="hamburger-component" data-ol-link-track="HeaderBar|Hamburger">
<button class="hamburger-trigger" aria-label="$_('Menu')" aria-expanded="false">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best Practice: Accessible name "$_('Menu')" does not contain visible text "$if ctx.user:

    $if is_privileged_user and cached_get_counts_by_mode(mode='open') > 0:
      $(cached_get_counts_by_mode(mode='open'))".

Interactive elements with visible text must have accessible names that contain that text.

Details

For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.

</header>

<ol-drawer id="hamburger-drawer" label="$_('Menu')">
<ul class="drawer-menu hamburger-drawer-menu">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WCAG 1.3.1: <ul> contains direct text content. Wrap in <li>.

<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.

Details

Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.

}

/* The row holding the pressed link stays lit. `is-target-row` is set on the
<li> (not the link) because opacity on the row compounds onto its children,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WCAG 1.3.1: <li> is not contained in a <ul>, <ol>, or <menu>.

<li> elements must be contained in a <ul>, <ol>, or <menu>.

Details

List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants