152 lines
4.4 KiB
JavaScript
152 lines
4.4 KiB
JavaScript
/*
|
|
* CTAO portal mockup: header behaviour
|
|
*
|
|
* 1. The sticky header turns solid Galaxy Blue after 40px of scroll.
|
|
* 2. Submenus: every button with data-submenu-toggle (Example, language)
|
|
* toggles the element named in its aria-controls. One open at a time.
|
|
* 3. The search button toggles the search panel.
|
|
* 4. The burger button toggles the main nav on small screens.
|
|
* 5. Escape and clicks outside the header close open menus.
|
|
*
|
|
* Review helper: index.html#open=<id> opens a panel on load,
|
|
* for example #open=submenu-example, #open=submenu-lang,
|
|
* #open=search-panel or #open=site-nav.
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
// Keep in sync with the small-screen media query in css/styles.css
|
|
const MOBILE_QUERY = window.matchMedia("(max-width: 1079px)");
|
|
|
|
const header = document.getElementById("site-header");
|
|
const nav = document.getElementById("site-nav");
|
|
const searchPanel = document.getElementById("search-panel");
|
|
const searchButton = header.querySelector('[aria-controls="search-panel"]');
|
|
const menuButton = header.querySelector('[aria-controls="site-nav"]');
|
|
const submenuButtons = Array.from(
|
|
header.querySelectorAll("[data-submenu-toggle]"),
|
|
);
|
|
|
|
/* 1. Scrolled state */
|
|
|
|
function updateScrolled() {
|
|
header.classList.toggle("is-scrolled", window.scrollY > 40);
|
|
}
|
|
|
|
window.addEventListener("scroll", updateScrolled, { passive: true });
|
|
updateScrolled();
|
|
|
|
/* Keeps the header solid while a full-width panel is open */
|
|
function updateHeaderState() {
|
|
const panelOpen =
|
|
!searchPanel.hidden || header.classList.contains("is-nav-open");
|
|
header.classList.toggle("is-open", panelOpen);
|
|
}
|
|
|
|
/* 2. Submenus */
|
|
|
|
function setSubmenu(button, open) {
|
|
const submenu = document.getElementById(
|
|
button.getAttribute("aria-controls"),
|
|
);
|
|
button.setAttribute("aria-expanded", String(open));
|
|
submenu.hidden = !open;
|
|
}
|
|
|
|
function closeSubmenus(except) {
|
|
submenuButtons.forEach(function (button) {
|
|
if (button !== except) setSubmenu(button, false);
|
|
});
|
|
}
|
|
|
|
submenuButtons.forEach(function (button) {
|
|
button.addEventListener("click", function () {
|
|
const open = button.getAttribute("aria-expanded") !== "true";
|
|
closeSubmenus(button);
|
|
setSearch(false);
|
|
setSubmenu(button, open);
|
|
});
|
|
});
|
|
|
|
/* 3. Search */
|
|
|
|
function setSearch(open) {
|
|
searchButton.setAttribute("aria-expanded", String(open));
|
|
searchPanel.hidden = !open;
|
|
updateHeaderState();
|
|
if (open) searchPanel.querySelector("input").focus();
|
|
}
|
|
|
|
searchButton.addEventListener("click", function () {
|
|
const open = searchPanel.hidden;
|
|
closeSubmenus();
|
|
setMobileNav(false);
|
|
setSearch(open);
|
|
});
|
|
|
|
/* 4. Mobile nav */
|
|
|
|
function setMobileNav(open) {
|
|
menuButton.setAttribute("aria-expanded", String(open));
|
|
header.classList.toggle("is-nav-open", open);
|
|
document.body.classList.toggle("no-scroll", open);
|
|
updateHeaderState();
|
|
}
|
|
|
|
menuButton.addEventListener("click", function () {
|
|
const open = !header.classList.contains("is-nav-open");
|
|
closeSubmenus();
|
|
setSearch(false);
|
|
setMobileNav(open);
|
|
});
|
|
|
|
/* 5. Dismiss */
|
|
|
|
function closeAll() {
|
|
closeSubmenus();
|
|
setSearch(false);
|
|
setMobileNav(false);
|
|
}
|
|
|
|
document.addEventListener("click", function (event) {
|
|
if (!header.contains(event.target)) closeAll();
|
|
});
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key !== "Escape") return;
|
|
|
|
const openSubmenuButton = submenuButtons.find(function (button) {
|
|
return button.getAttribute("aria-expanded") === "true";
|
|
});
|
|
|
|
if (openSubmenuButton) {
|
|
setSubmenu(openSubmenuButton, false);
|
|
openSubmenuButton.focus();
|
|
} else if (!searchPanel.hidden) {
|
|
setSearch(false);
|
|
searchButton.focus();
|
|
} else if (header.classList.contains("is-nav-open")) {
|
|
setMobileNav(false);
|
|
menuButton.focus();
|
|
}
|
|
});
|
|
|
|
/* Switching between desktop and small-screen layouts starts clean */
|
|
MOBILE_QUERY.addEventListener("change", closeAll);
|
|
|
|
/* Review helpers */
|
|
|
|
const openMatch = location.hash.match(/^#open=([\w-]+)$/);
|
|
if (openMatch) {
|
|
const target = header.querySelector(
|
|
'[aria-controls="' + openMatch[1] + '"]',
|
|
);
|
|
if (target) {
|
|
if (MOBILE_QUERY.matches && nav.contains(target)) menuButton.click();
|
|
target.click();
|
|
}
|
|
}
|
|
|
|
if (location.hash === "#scrolled") window.scrollTo(0, 420);
|
|
})();
|