updateweb
This commit is contained in:
@@ -1,79 +1,203 @@
|
||||
// Dynamic Menu Loader for Sky Art Shop
|
||||
// Include this in all public pages to load menu from database
|
||||
/**
|
||||
* Navigation Component
|
||||
* Handles mobile menu, dropdowns, and accessibility
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Load and render navigation menu from API
|
||||
async function loadNavigationMenu() {
|
||||
try {
|
||||
const response = await fetch("/api/menu");
|
||||
const data = await response.json();
|
||||
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;
|
||||
|
||||
if (data.success && data.items && data.items.length > 0) {
|
||||
renderDesktopMenu(data.items);
|
||||
renderMobileMenu(data.items);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load menu:", error);
|
||||
// Keep existing hardcoded menu as fallback
|
||||
}
|
||||
}
|
||||
|
||||
function renderDesktopMenu(items) {
|
||||
const desktopMenuList = document.querySelector(".nav-menu-list");
|
||||
if (!desktopMenuList) return;
|
||||
|
||||
desktopMenuList.innerHTML = items
|
||||
.map(
|
||||
(item) => `
|
||||
<li class="nav-item">
|
||||
<a href="${item.url}" class="nav-link">
|
||||
${item.icon ? `<i class="bi ${item.icon}"></i> ` : ""}${item.label}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Set active state based on current page
|
||||
const currentPath = window.location.pathname;
|
||||
document.querySelectorAll(".nav-link").forEach((link) => {
|
||||
if (link.getAttribute("href") === currentPath) {
|
||||
link.classList.add("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderMobileMenu(items) {
|
||||
const mobileMenuList = document.querySelector(".mobile-menu-list");
|
||||
if (!mobileMenuList) return;
|
||||
|
||||
mobileMenuList.innerHTML = items
|
||||
.map(
|
||||
(item) => `
|
||||
<li>
|
||||
<a href="${item.url}" class="mobile-link">
|
||||
${item.icon ? `<i class="bi ${item.icon}"></i> ` : ""}${item.label}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
// Set active state for mobile menu
|
||||
const currentPath = window.location.pathname;
|
||||
document.querySelectorAll(".mobile-link").forEach((link) => {
|
||||
if (link.getAttribute("href") === currentPath) {
|
||||
link.classList.add("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load menu when DOM is ready
|
||||
// Initialize navigation when DOM is ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", loadNavigationMenu);
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new Navigation();
|
||||
});
|
||||
} else {
|
||||
loadNavigationMenu();
|
||||
new Navigation();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user