UNPKG

@casoon/dragonfly

Version:

Modular, lightweight CSS framework and design system for modern web projects. Optimized for Astro JS, LightningCSS and Container Queries with @layer-based architecture and comprehensive accessibility.

179 lines (151 loc) 5.11 kB
/** * Menu JavaScript * * Enhanced menu functionality for headers, off-canvas, and nested navigation. * Supports mobile hamburger menus, dropdowns, megamenus, and accessibility. */ class MenuSystem { constructor() { this.init(); } init() { this.initHeaderMenu(); this.initOffCanvas(); this.initDropdowns(); this.initNestedNavigation(); this.initKeyboardNavigation(); this.initAccessibility(); } // Header mobile menu functionality initHeaderMenu() { const headerToggle = document.querySelector('.header-toggle'); const headerMobile = document.querySelector('.header-mobile'); if (headerToggle && headerMobile) { headerToggle.addEventListener('click', (e) => { e.stopPropagation(); const isExpanded = headerToggle.getAttribute('aria-expanded') === 'true'; headerToggle.setAttribute('aria-expanded', !isExpanded); headerMobile.classList.toggle('visible'); const hamburger = headerToggle.querySelector('.hamburger'); if (hamburger) { hamburger.classList.toggle('active'); } }); } } // Off-canvas menu functionality initOffCanvas() { const triggers = document.querySelectorAll('.off-canvas-trigger'); const closeButtons = document.querySelectorAll('.off-canvas-close'); const overlays = document.querySelectorAll('.off-canvas-overlay'); for (const trigger of triggers) { trigger.addEventListener('click', (e) => { e.stopPropagation(); const targetId = trigger.getAttribute('data-target'); const offCanvas = document.querySelector(targetId || '.off-canvas'); const overlay = document.querySelector('.off-canvas-overlay'); if (offCanvas) { offCanvas.classList.toggle('open'); overlay?.classList.toggle('visible'); const hamburger = trigger.querySelector('.hamburger'); if (hamburger) { hamburger.classList.toggle('active'); } } }); } for (const button of closeButtons) { button.addEventListener('click', () => { this.closeAllOffCanvas(); }); } for (const overlay of overlays) { overlay.addEventListener('click', () => { this.closeAllOffCanvas(); }); } } // Desktop dropdown functionality initDropdowns() { const dropdownItems = document.querySelectorAll('.header-nav .has-dropdown'); for (const item of dropdownItems) { item.addEventListener('mouseenter', () => { item.classList.add('open'); }); item.addEventListener('mouseleave', () => { item.classList.remove('open'); }); } } // Nested navigation (mobile and off-canvas) initNestedNavigation() { const nestedItems = document.querySelectorAll('.has-submenu'); for (const item of nestedItems) { const link = item.querySelector('a'); if (link) { link.addEventListener('click', (e) => { e.preventDefault(); item.classList.toggle('open'); }); } } } // Keyboard navigation initKeyboardNavigation() { document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this.closeAllMenus(); } }); } // Accessibility enhancements initAccessibility() { // Add ARIA attributes const dropdownToggles = document.querySelectorAll('.has-dropdown > a, .has-submenu > a'); for (const toggle of dropdownToggles) { toggle.setAttribute('aria-haspopup', 'true'); toggle.setAttribute('aria-expanded', 'false'); } // Update ARIA states const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const target = mutation.target; const isOpen = target.classList.contains('open'); const toggle = target.querySelector('a'); if (toggle) { toggle.setAttribute('aria-expanded', isOpen.toString()); } } } }); for (const item of document.querySelectorAll('.has-dropdown, .has-submenu')) { observer.observe(item, { attributes: true }); } } // Helper methods closeAllOffCanvas() { for (const menu of document.querySelectorAll('.off-canvas')) { menu.classList.remove('open'); } for (const overlay of document.querySelectorAll('.off-canvas-overlay')) { overlay.classList.remove('visible'); } for (const hamburger of document.querySelectorAll('.hamburger')) { hamburger.classList.remove('active'); } } closeAllMenus() { document.querySelector('.header-mobile')?.classList.remove('visible'); document.querySelector('.header-toggle')?.setAttribute('aria-expanded', 'false'); this.closeAllOffCanvas(); } } // Initialize menu system when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new MenuSystem(); }); // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = MenuSystem; }