import { useEffect, useRef, type ReactNode } from 'react'; /** * FocusTrapProps - Propriétés du composant FocusTrap * * @interface FocusTrapProps */ interface FocusTrapProps { /** * Contenu à encapsuler dans le piège de focus */ children: ReactNode; /** * Si `true`, active le piège de focus * * @default true */ active?: boolean; /** * Fonction appelée lorsque la touche Escape est pressée * * @example * ```tsx * setOpen(false)}> * * * ``` */ onEscape?: () => void; } /** * FocusTrap - Composant pour piéger le focus dans une zone * * Composant qui piège le focus à l'intérieur d'une zone spécifique, * utile pour les modales et les dialogues. Le focus reste dans la zone * et revient au début lorsqu'on atteint la fin avec Tab. * * @example * ```tsx * // Piège de focus pour une modale * setIsOpen(false)}> *
* * * *
*
* ``` * * @component * @param {FocusTrapProps} props - Propriétés du composant * @returns {JSX.Element} Div avec tabIndex={-1} contenant les enfants */ export function FocusTrap({ children, active = true, onEscape, }: FocusTrapProps) { const containerRef = useRef(null); const previousActiveElement = useRef(null); useEffect(() => { if (!active || !containerRef.current) return; // Sauvegarder l'élément actuellement actif previousActiveElement.current = document.activeElement; // Trouver tous les éléments focusables const focusableElements = containerRef.current.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); const firstElement = focusableElements[0] as HTMLElement; const lastElement = focusableElements[ focusableElements.length - 1 ] as HTMLElement; // Focuser le premier élément if (firstElement) { firstElement.focus(); } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onEscape?.(); return; } if (event.key === 'Tab') { if (event.shiftKey) { // Shift + Tab if (document.activeElement === firstElement) { event.preventDefault(); lastElement?.focus(); } } else { // Tab if (document.activeElement === lastElement) { event.preventDefault(); firstElement?.focus(); } } } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); // Restaurer le focus à l'élément précédent if (previousActiveElement.current instanceof HTMLElement) { previousActiveElement.current.focus(); } }; }, [active, onEscape]); return (
{children}
); }