FEATURE Fixing & Refining search bar behaviour
This commit is contained in:
parent
8211ba7a6b
commit
e707843129
|
|
@ -10,7 +10,7 @@
|
||||||
bottom-0
|
bottom-0
|
||||||
/* overflow-x-hidden */
|
/* overflow-x-hidden */
|
||||||
transform translate-y-full;
|
transform translate-y-full;
|
||||||
@apply block;
|
@apply block overflow-x-hidden;
|
||||||
|
|
||||||
animation: translate-in 700ms forwards cubic-bezier(0, 0.51, 0.23, 0.99),
|
animation: translate-in 700ms forwards cubic-bezier(0, 0.51, 0.23, 0.99),
|
||||||
fade-in 600ms forwards ease-out;
|
fade-in 600ms forwards ease-out;
|
||||||
|
|
@ -33,6 +33,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes translate-out {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(calc(100% - 100px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[closed] {
|
&[closed] {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
|
|
@ -101,5 +118,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body:has(.search-module[opened]) main {
|
body:has(.search-module[opened]) main {
|
||||||
filter: blur(2px) brightness(0.9);
|
filter: blur(2px) brightness(0.8);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,34 +5,47 @@ interface SearchBarOptions {
|
||||||
|
|
||||||
export default class SearchBar {
|
export default class SearchBar {
|
||||||
private searchBar: HTMLDivElement | null;
|
private searchBar: HTMLDivElement | null;
|
||||||
private searchButton: HTMLButtonElement | null;
|
private searchButtons: NodeListOf<HTMLButtonElement> | null;
|
||||||
|
private searchInput: HTMLInputElement | null;
|
||||||
private isOpen: boolean = false;
|
private isOpen: boolean = false;
|
||||||
|
private lastScrollY: number = 0;
|
||||||
|
private activeButton: HTMLButtonElement | null = null;
|
||||||
|
|
||||||
constructor(options: SearchBarOptions = {}) {
|
constructor(options: SearchBarOptions = {}) {
|
||||||
this.searchBar = document.querySelector('#search-module') as HTMLDivElement;
|
this.searchBar = document.querySelector('#search-module') as HTMLDivElement;
|
||||||
this.searchButton = document.querySelector(
|
this.searchButtons = document.querySelectorAll(
|
||||||
'.tools-container .search-button'
|
'.tools-container .search-button'
|
||||||
) as HTMLButtonElement;
|
) as NodeListOf<HTMLButtonElement>; // Il y a 2 boutons de recherche (1 mobile; 1 desktop)
|
||||||
|
this.searchInput = document.querySelector(
|
||||||
|
'.search-module__search-form__input'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private init(): void {
|
private init(): void {
|
||||||
if (!this.searchBar || !this.searchButton) return;
|
if (!this.searchBar || !this.searchButtons) return;
|
||||||
|
|
||||||
// Initialiser l'état
|
// Initialiser l'état
|
||||||
this.isOpen = this.searchBar.hasAttribute('opened');
|
this.isOpen = this.searchBar.hasAttribute('opened');
|
||||||
|
this.lastScrollY = window.scrollY;
|
||||||
this.updateAriaHidden();
|
this.updateAriaHidden();
|
||||||
|
|
||||||
// Ajouter les event listeners
|
// Ajouter les event listeners
|
||||||
this.searchButton.addEventListener('click', this.toggle.bind(this));
|
this.searchButtons.forEach((button) =>
|
||||||
|
button.addEventListener('click', (event) => this.toggle(event))
|
||||||
|
);
|
||||||
this.searchBar.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
|
this.searchBar.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
|
||||||
document.addEventListener('keydown', this.handleKeyDown.bind(this));
|
document.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
|
window.addEventListener('scroll', this.handleScroll.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggle(): void {
|
private toggle(event: Event): void {
|
||||||
if (!this.searchBar) return;
|
if (!this.searchBar) return;
|
||||||
|
|
||||||
|
// Stocker le bouton qui a été cliqué
|
||||||
|
this.activeButton = event.currentTarget as HTMLButtonElement;
|
||||||
|
|
||||||
this.isOpen = !this.isOpen;
|
this.isOpen = !this.isOpen;
|
||||||
|
|
||||||
if (this.isOpen) {
|
if (this.isOpen) {
|
||||||
|
|
@ -48,14 +61,42 @@ export default class SearchBar {
|
||||||
this.searchBar.removeAttribute('closed');
|
this.searchBar.removeAttribute('closed');
|
||||||
this.searchBar.setAttribute('opened', '');
|
this.searchBar.setAttribute('opened', '');
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
|
||||||
|
// Focus automatique sur le champ de recherche après un petit délai pour laisser l'animation commencer
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.searchInput && this.isOpen) {
|
||||||
|
this.searchInput.focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
if (!this.searchBar) return;
|
if (!this.searchBar) return;
|
||||||
|
|
||||||
this.searchBar.setAttribute('closed', '');
|
// Défocus l'input de recherche
|
||||||
|
if (this.searchInput) {
|
||||||
|
this.searchInput.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchBar.setAttribute('closing', '');
|
||||||
this.searchBar.removeAttribute('opened');
|
this.searchBar.removeAttribute('opened');
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
|
|
||||||
|
// Écouter la fin de l'animation de fermeture (on attend la plus longue: translate-out 800ms)
|
||||||
|
const handleAnimationEnd = (event: AnimationEvent) => {
|
||||||
|
if (event.animationName === 'translate-out') {
|
||||||
|
this.searchBar?.removeAttribute('closing');
|
||||||
|
this.searchBar?.setAttribute('closed', '');
|
||||||
|
this.searchBar?.removeEventListener('animationend', handleAnimationEnd);
|
||||||
|
|
||||||
|
// Refocus sur le bouton qui avait ouvert la barre après la fermeture
|
||||||
|
if (this.activeButton) {
|
||||||
|
this.activeButton.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.searchBar.addEventListener('animationend', handleAnimationEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTransitionEnd(): void {
|
private handleTransitionEnd(): void {
|
||||||
|
|
@ -68,10 +109,16 @@ export default class SearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleScroll(): void {
|
||||||
|
if (!this.isOpen) return;
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
private updateAriaHidden(): void {
|
private updateAriaHidden(): void {
|
||||||
if (!this.searchBar) return;
|
if (!this.searchBar) return;
|
||||||
|
|
||||||
if (this.searchBar.hasAttribute('closed')) {
|
if (this.searchBar.hasAttribute('closed') || this.searchBar.hasAttribute('closing')) {
|
||||||
this.searchBar.setAttribute('aria-hidden', 'true');
|
this.searchBar.setAttribute('aria-hidden', 'true');
|
||||||
} else {
|
} else {
|
||||||
this.searchBar.setAttribute('aria-hidden', 'false');
|
this.searchBar.setAttribute('aria-hidden', 'false');
|
||||||
|
|
@ -83,10 +130,13 @@ export default class SearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (!this.searchBar || !this.searchButton) return;
|
if (!this.searchBar || !this.searchButtons) return;
|
||||||
|
|
||||||
this.searchButton.removeEventListener('click', this.toggle.bind(this));
|
// Note: Les event listeners anonymes ne peuvent pas être supprimés facilement
|
||||||
|
// Dans un vrai projet, il faudrait stocker les références des fonctions
|
||||||
this.searchBar.removeEventListener('transitionend', this.handleTransitionEnd.bind(this));
|
this.searchBar.removeEventListener('transitionend', this.handleTransitionEnd.bind(this));
|
||||||
|
document.removeEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
|
window.removeEventListener('scroll', this.handleScroll.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user