diff --git a/resources/css/layout/menu-mobile-brand.css b/resources/css/layout/menu-mobile-brand.css index ee07837..826ab58 100644 --- a/resources/css/layout/menu-mobile-brand.css +++ b/resources/css/layout/menu-mobile-brand.css @@ -6,13 +6,15 @@ } #mobile-menu-toggle { - @apply underline underline-offset-4 bg-red-500 flex gap-2 items-center; + @apply underline underline-offset-4 flex gap-2 items-center; &[aria-expanded] { - @apply bg-green-500; &::after { - @apply block w-10 h-10 bg-red-500 content-['Fermer']; - content: 'Fermer'; - + @apply block w-4 h-4; + content: ''; + background-image: url('../resources/img/close_menu_icon.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; } } } diff --git a/resources/js/header.js b/resources/js/header.js index 11b91e6..a0f4e4c 100644 --- a/resources/js/header.js +++ b/resources/js/header.js @@ -1,76 +1,141 @@ -export default function menuInit() { - let main_navigation = document.querySelector('#primary-menu'); - const header = document.querySelector('#primary-header'); - if (!header) return; +/** + * Menu mobile + sous-menus du header (équivalent logique de header.ts côté dynamiques). + */ +class HeaderMenu { + constructor() { + this.header = document.querySelector('#primary-header'); + this.primaryMenu = this.header?.querySelector('#primary-menu') ?? null; + this.mobileMenuToggle = this.header?.querySelector('#mobile-menu-toggle') ?? null; + this.menusWrapper = this.header?.querySelector('.menus-wrapper') ?? null; + this.submenuToggles = this.header + ? this.header.querySelectorAll('.menu-item__submenu-toggle') + : []; - const primary_menu = header.querySelector('#primary-menu'); - const mobileMenuToggle = header.querySelector('#mobile-menu-toggle'); - const submenuToggles = header.querySelectorAll('.menu-item__submenu-toggle'); - const menusWrapper = header.querySelector('.menus-wrapper'); - - function closeSubmenus() { - submenuToggles.forEach((button) => { - button.setAttribute('aria-expanded', 'false'); - const sub = button.parentElement?.querySelector('.sub-menu'); - if (sub) sub.classList.remove('sub-menu-open'); - }); + this.init(); } - // #### Open/close burger nav - mobileMenuToggle.addEventListener('click', function (e) { - e.preventDefault(); - const isOpen = header.classList.contains('nav-open'); - header.classList.toggle('nav-open'); - mobileMenuToggle.toggleAttribute('aria-expanded'); - if (!isOpen) { - gsap.from(menusWrapper, { + /** + * Vérifie les éléments nécessaires et attache les écouteurs. + */ + init() { + if (!this.header || !this.mobileMenuToggle) { + console.warn('HeaderMenu : #primary-header ou #mobile-menu-toggle introuvable'); + return; + } + this.bindEvents(); + } + + /** + * Lie tous les événements (toggle, focus, sous-menus, document). + */ + bindEvents() { + this.mobileMenuToggle.addEventListener('click', (e) => this.handleToggleClick(e)); + + document.addEventListener('focusin', (e) => this.handleFocusOut(e), true); + + this.submenuToggles.forEach((button) => { + button.addEventListener('click', (e) => this.handleSubmenuToggle(e, button)); + }); + + document.addEventListener('click', (e) => this.handleDocumentClick(e)); + document.addEventListener('keydown', (e) => this.handleDocumentKeydown(e)); + } + + isMenuOpen() { + return this.header?.classList.contains('nav-open') ?? false; + } + + openMenu() { + if (!this.header || !this.mobileMenuToggle || this.isMenuOpen()) return; + + this.header.classList.add('nav-open'); + this.mobileMenuToggle.setAttribute('aria-expanded', 'true'); + + this.mobileMenuToggle.textContent = 'Fermer'; + + if (typeof gsap !== 'undefined' && this.menusWrapper) { + gsap.from(this.menusWrapper, { opacity: 20, y: '-100vh', duration: 0.5, ease: Power4.easeOut, }); } - }); + } - // // #### Close nav when reaching the end of the menu with tab - // document.addEventListener( - // 'focusin', - // (e) => { - // const header = document.querySelector('#primary-header'); - // if ( - // header.classList.contains('nav-open') && - // !header.contains(document.activeElement) - // ) { - // header.classList.remove('nav-open'); - // burgerMenuToggle.setAttribute('aria-expanded', false); + closeMenu() { + if (!this.header || !this.mobileMenuToggle || !this.isMenuOpen()) return; - // burgerMenuToggle.focus(); - // } - // }, - // true, - // ); + this.header.classList.remove('nav-open'); + this.mobileMenuToggle.removeAttribute('aria-expanded'); + this.mobileMenuToggle.textContent = 'Menu'; + } - submenuToggles.forEach((button) => { - button.addEventListener('click', function (e) { - let isExpanded = button.getAttribute('aria-expanded') === 'true'; + toggleMenu() { + if (this.isMenuOpen()) { + this.closeMenu(); + } else { + this.openMenu(); + } + } - closeSubmenus(); - if (!isExpanded) { - button.setAttribute('aria-expanded', true); - button.parentElement.querySelector('.sub-menu').classList.add('sub-menu-open'); - } + closeAllSubmenus() { + this.submenuToggles.forEach((button) => { + button.setAttribute('aria-expanded', 'false'); + const sub = button.parentElement?.querySelector('.sub-menu'); + if (sub) sub.classList.remove('sub-menu-open'); }); - }); + } - document.addEventListener('click', (e) => { - if (!header.contains(e.target)) { - closeSubmenus(); - } - }); + handleToggleClick(e) { + e.preventDefault(); + this.toggleMenu(); + } - document.addEventListener('keydown', function (e) { + /** + * Tab / focus hors du header : ferme la nav et renvoie le focus sur le bouton. + */ + handleFocusOut(e) { + if (!this.header || !this.mobileMenuToggle || !this.isMenuOpen()) return; + + const target = e.target; + if (!(target instanceof Node)) return; + if (this.header.contains(target)) return; + + this.closeMenu(); + this.closeAllSubmenus(); + this.mobileMenuToggle.focus(); + } + + /** + * Clic en dehors du header : ferme uniquement les sous-menus (comportement actuel). + */ + handleDocumentClick(e) { + const target = e.target; + if (!(target instanceof Node) || !this.header) return; + if (this.header.contains(target)) return; + this.closeAllSubmenus(); + } + + handleDocumentKeydown(e) { if (e.key === 'Escape') { - closeSubmenus(); + this.closeAllSubmenus(); } - }); + } + + /** + * Accordéon : un seul sous-menu ouvert à la fois. + */ + handleSubmenuToggle(e, button) { + const isExpanded = button.getAttribute('aria-expanded') === 'true'; + this.closeAllSubmenus(); + if (!isExpanded) { + button.setAttribute('aria-expanded', 'true'); + button.parentElement?.querySelector('.sub-menu')?.classList.add('sub-menu-open'); + } + } +} + +export default function menuInit() { + return new HeaderMenu(); }