FEATURE Improving the mobile menu behaviour

This commit is contained in:
Nonimart 2025-09-04 16:11:08 +02:00
parent 2c1dabec6e
commit d1610f4281

View File

@ -1,61 +1,140 @@
export default function menuInit() {
let main_navigation =
document.querySelector('#primary-menu');
const header = document.querySelector('#primary-header');
const primary_menu =
header.querySelector('#primary-menu');
const burgerMenuToggle = header.querySelector(
'#burger-menu-toggle'
);
const submenuToggles = primary_menu.querySelectorAll(
'.menu-item-submenu-toggle'
);
// Déclaration pour GSAP si disponible
declare var gsap: any;
// #### Open/close burger nav
burgerMenuToggle.addEventListener('click', function (e) {
e.preventDefault();
header.classList.toggle('nav-open');
burgerMenuToggle.toggleAttribute('aria-expanded');
gsap.from(primary_menu, {
opacity: 20,
y: '-100vh',
duration: 0.5,
ease: Power4.easeOut,
});
});
class MobileMenu {
private mobileMenu: HTMLElement | null;
private mobileMenuToggle: HTMLElement | null;
private header: HTMLElement | null;
private primaryMenu: HTMLElement | null;
private submenuToggles: NodeListOf<Element>;
// #### 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
);
constructor() {
this.mobileMenu = document.querySelector('#menu-mobile-brand');
this.header = document.querySelector('#primary-header');
this.primaryMenu = this.header?.querySelector('#primary-menu-nav') || null;
this.mobileMenuToggle = this.mobileMenu?.querySelector('#mobile-menu-toggle') || null;
this.submenuToggles =
this.primaryMenu?.querySelectorAll('.menu-item-submenu-toggle') || ([] as any);
burgerMenuToggle.focus();
this.init();
}
},
true
);
submenuToggles.forEach((button) => {
button.addEventListener('click', function (e) {
let isExpanded =
button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
/**
* Initialise le menu mobile et ses événements
*/
private init(): void {
if (!this.mobileMenu || !this.header || !this.mobileMenuToggle || !this.primaryMenu) {
console.warn('Éléments du menu mobile introuvables');
return;
}
button.parentElement
.querySelector('.sub-menu')
.classList.toggle('sub-menu-open');
});
this.bindEvents();
}
/**
* Lie tous les événements du menu mobile
*/
private bindEvents(): void {
// Événement pour le toggle du menu mobile
this.mobileMenuToggle?.addEventListener('click', (e) => this.handleToggleClick(e));
// Événement pour fermer le menu avec Tab
document.addEventListener('focusin', (e) => this.handleFocusOut(e), true);
// Événements pour les sous-menus
this.submenuToggles.forEach((button) => {
button.addEventListener('click', (e) => this.handleSubmenuToggle(e, button));
});
}
/**
* Ouvre le menu mobile
*/
public openMobileMenu(): void {
if (!this.mobileMenu || !this.mobileMenuToggle || !this.header) return;
this.mobileMenu.setAttribute('nav-open', 'true');
this.header.setAttribute('nav-open', 'true');
this.mobileMenuToggle.setAttribute('aria-expanded', 'true');
// // Animation GSAP si disponible
// if (typeof gsap !== 'undefined' && this.primaryMenu) {
// gsap.from(this.primaryMenu, {
// opacity: 0,
// y: '-100vh',
// duration: 0.5,
// ease: 'power4.out',
// });
// }
}
/**
* Ferme le menu mobile
*/
public closeMobileMenu(): void {
if (!this.mobileMenu || !this.mobileMenuToggle || !this.header) return;
this.mobileMenu.setAttribute('nav-open', 'false');
this.header.setAttribute('nav-open', 'false');
this.mobileMenuToggle.setAttribute('aria-expanded', 'false');
}
/**
* Toggle l'état du menu mobile
*/
public toggleMobileMenu(): void {
if (!this.mobileMenu) return;
if (this.mobileMenu.getAttribute('nav-open') === 'true') {
this.closeMobileMenu();
this.mobileMenuToggle.textContent = 'Menu';
} else {
this.openMobileMenu();
this.mobileMenuToggle.textContent = 'Fermer';
}
}
/**
* Vérifie si le menu mobile est ouvert
*/
public isMenuOpen(): boolean {
return this.mobileMenu?.getAttribute('nav-open') === 'true' || false;
}
/**
* Gère le clic sur le bouton toggle
*/
private handleToggleClick(e: Event): void {
e.preventDefault();
this.toggleMobileMenu();
}
/**
* Gère la fermeture du menu lors du focus en dehors
*/
private handleFocusOut(e: Event): void {
if (!this.header || !this.isMenuOpen()) return;
const target = e.target as Element;
if (!this.header.contains(target)) {
this.closeMobileMenu();
this.mobileMenuToggle?.focus();
}
}
/**
* Gère le toggle des sous-menus
*/
private handleSubmenuToggle(e: Event, button: Element): void {
e.preventDefault();
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!isExpanded));
const submenu = button.parentElement?.querySelector('.sub-menu');
submenu?.classList.toggle('sub-menu-open');
}
}
export default function menuInit(): MobileMenu {
return new MobileMenu();
}