FEATURE Refactor header nav behaviours
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
dabedfa4af
commit
e29da0a151
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user