Files
SkyArtShop/website/public/assets/js/navigation.js

204 lines
5.6 KiB
JavaScript
Raw Normal View History

2026-01-01 22:24:30 -06:00
/**
* Navigation Component
* Handles mobile menu, dropdowns, and accessibility
*/
(function () {
"use strict";
class Navigation {
constructor() {
this.mobileMenuToggle = document.getElementById("mobileMenuToggle");
this.mobileMenu = document.getElementById("mobileMenu");
this.mobileMenuClose = document.getElementById("mobileMenuClose");
this.overlay = document.getElementById("mobileMenuOverlay");
this.body = document.body;
this.init();
}
init() {
this.setupMobileMenu();
this.setupAccessibility();
this.highlightCurrentPage();
this.setupKeyboardNavigation();
}
setupMobileMenu() {
// Open mobile menu
if (this.mobileMenuToggle) {
this.mobileMenuToggle.addEventListener("click", () =>
this.openMobileMenu()
);
}
// Close mobile menu
if (this.mobileMenuClose) {
this.mobileMenuClose.addEventListener("click", () =>
this.closeMobileMenu()
);
}
if (this.overlay) {
this.overlay.addEventListener("click", () => this.closeMobileMenu());
}
// Close on ESC key
document.addEventListener("keydown", (e) => {
if (
e.key === "Escape" &&
this.mobileMenu &&
this.mobileMenu.classList.contains("active")
) {
this.closeMobileMenu();
}
});
}
openMobileMenu() {
if (this.mobileMenu) {
this.mobileMenu.classList.add("active");
this.mobileMenu.setAttribute("aria-hidden", "false");
this.body.style.overflow = "hidden";
if (this.overlay) {
this.overlay.classList.add("active");
}
// Focus first link
const firstLink = this.mobileMenu.querySelector("a");
if (firstLink) {
setTimeout(() => firstLink.focus(), 100);
}
}
}
closeMobileMenu() {
if (this.mobileMenu) {
this.mobileMenu.classList.remove("active");
this.mobileMenu.setAttribute("aria-hidden", "true");
this.body.style.overflow = "";
if (this.overlay) {
this.overlay.classList.remove("active");
}
// Return focus to toggle button
if (this.mobileMenuToggle) {
this.mobileMenuToggle.focus();
}
}
}
setupAccessibility() {
// Wait for body to exist
if (!document.body) return;
// Add ARIA labels to nav items
const navLinks = document.querySelectorAll(".nav-link");
navLinks.forEach((link) => {
if (!link.getAttribute("aria-label")) {
link.setAttribute(
"aria-label",
`Navigate to ${link.textContent.trim()}`
);
}
});
// Add skip to main content link
if (!document.getElementById("skip-to-main")) {
const skipLink = document.createElement("a");
skipLink.id = "skip-to-main";
skipLink.href = "#main-content";
skipLink.textContent = "Skip to main content";
skipLink.className = "skip-link";
document.body.insertBefore(skipLink, document.body.firstChild);
// Add styles for skip link
if (!document.getElementById("skip-link-styles")) {
const style = document.createElement("style");
style.id = "skip-link-styles";
style.textContent = `
.skip-link {
position: fixed;
top: -100px;
left: 0;
padding: 10px 20px;
background: #000;
color: #fff;
z-index: 10001;
text-decoration: none;
border-radius: 0 0 8px 0;
}
.skip-link:focus {
top: 0;
}
`;
document.head.appendChild(style);
}
}
}
highlightCurrentPage() {
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll(".nav-link, .mobile-link");
navLinks.forEach((link) => {
const href = link.getAttribute("href");
if (
href &&
(currentPath === href || currentPath.startsWith(href + "/"))
) {
link.classList.add("active");
link.setAttribute("aria-current", "page");
} else {
link.classList.remove("active");
link.removeAttribute("aria-current");
}
});
}
setupKeyboardNavigation() {
// Tab trap in mobile menu when open
if (this.mobileMenu) {
const focusableElements = this.mobileMenu.querySelectorAll(
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length > 0) {
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
this.mobileMenu.addEventListener("keydown", (e) => {
if (
e.key === "Tab" &&
this.mobileMenu.classList.contains("active")
) {
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
});
}
}
}
}
// Initialize navigation when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
new Navigation();
});
} else {
new Navigation();
}
})();