FEATURE Refactor header nav behaviours
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Antoine M 2026-05-11 17:03:01 +02:00
parent dabedfa4af
commit e29da0a151
2 changed files with 129 additions and 62 deletions

View File

@ -6,13 +6,15 @@
} }
#mobile-menu-toggle { #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] { &[aria-expanded] {
@apply bg-green-500;
&::after { &::after {
@apply block w-10 h-10 bg-red-500 content-['Fermer']; @apply block w-4 h-4;
content: 'Fermer'; content: '';
background-image: url('../resources/img/close_menu_icon.svg');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
} }
} }
} }

View File

@ -1,76 +1,141 @@
export default function menuInit() { /**
let main_navigation = document.querySelector('#primary-menu'); * Menu mobile + sous-menus du header (équivalent logique de header.ts côté dynamiques).
const header = document.querySelector('#primary-header'); */
if (!header) return; 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'); this.init();
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');
});
} }
// #### Open/close burger nav /**
mobileMenuToggle.addEventListener('click', function (e) { * Vérifie les éléments nécessaires et attache les écouteurs.
e.preventDefault(); */
const isOpen = header.classList.contains('nav-open'); init() {
header.classList.toggle('nav-open'); if (!this.header || !this.mobileMenuToggle) {
mobileMenuToggle.toggleAttribute('aria-expanded'); console.warn('HeaderMenu : #primary-header ou #mobile-menu-toggle introuvable');
if (!isOpen) { return;
gsap.from(menusWrapper, { }
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, opacity: 20,
y: '-100vh', y: '-100vh',
duration: 0.5, duration: 0.5,
ease: Power4.easeOut, ease: Power4.easeOut,
}); });
} }
}); }
// // #### Close nav when reaching the end of the menu with tab closeMenu() {
// document.addEventListener( if (!this.header || !this.mobileMenuToggle || !this.isMenuOpen()) return;
// '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);
// burgerMenuToggle.focus(); this.header.classList.remove('nav-open');
// } this.mobileMenuToggle.removeAttribute('aria-expanded');
// }, this.mobileMenuToggle.textContent = 'Menu';
// true, }
// );
submenuToggles.forEach((button) => { toggleMenu() {
button.addEventListener('click', function (e) { if (this.isMenuOpen()) {
let isExpanded = button.getAttribute('aria-expanded') === 'true'; this.closeMenu();
} else {
this.openMenu();
}
}
closeSubmenus(); closeAllSubmenus() {
if (!isExpanded) { this.submenuToggles.forEach((button) => {
button.setAttribute('aria-expanded', true); button.setAttribute('aria-expanded', 'false');
button.parentElement.querySelector('.sub-menu').classList.add('sub-menu-open'); const sub = button.parentElement?.querySelector('.sub-menu');
} if (sub) sub.classList.remove('sub-menu-open');
}); });
}); }
document.addEventListener('click', (e) => { handleToggleClick(e) {
if (!header.contains(e.target)) { e.preventDefault();
closeSubmenus(); 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') { 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();
} }