Files
community-rule/app/components/modals/Create/useCreateModalA11y.ts
T
2026-04-29 07:20:16 -06:00

83 lines
2.3 KiB
TypeScript

"use client";
import type { RefObject } from "react";
import { useEffect, useRef } from "react";
/**
* Escape-to-close, body scroll lock, focus move-in and tab trap for Create-shell modals.
*/
export function useCreateModalA11y(
isOpen: boolean,
onClose: () => void,
dialogRef: RefObject<HTMLDivElement | null>,
): void {
const previousActiveElementRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!isOpen) return;
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("keydown", handleEscape);
};
}, [isOpen, onClose]);
useEffect(() => {
if (!isOpen) return;
previousActiveElementRef.current = document.activeElement as HTMLElement;
document.body.style.overflow = "hidden";
if (dialogRef.current) {
const focusableElements = dialogRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
const firstElement = focusableElements[0] as HTMLElement;
if (firstElement) {
firstElement.focus();
} else {
dialogRef.current.setAttribute("tabindex", "-1");
dialogRef.current.focus();
}
}
const handleTab = (e: KeyboardEvent) => {
if (e.key !== "Tab" || !dialogRef.current) return;
const focusableElements = dialogRef.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;
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement?.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement?.focus();
}
}
};
document.addEventListener("keydown", handleTab);
return () => {
document.body.style.overflow = "";
document.removeEventListener("keydown", handleTab);
previousActiveElementRef.current?.focus();
};
}, [dialogRef, isOpen]);
}