"use client"; import React, { type FormEvent, type ReactNode, useEffect, useRef } from "react"; interface FormModalProps { children: ReactNode; description?: string; dialogSize?: "large" | "viewport"; isSaving: boolean; onClose: () => void; onSubmit: (event: FormEvent) => void; submitDisabled?: boolean; submitLabel: string; title: string; } const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; export function FormModal({ children, description, dialogSize = "large", isSaving, onClose, onSubmit, submitDisabled, submitLabel, title, }: FormModalProps) { const dialogRef = useRef(null); // Focuses the dialog on open and returns focus to the element that // triggered it on close, so keyboard users never lose their place in the // grid behind the backdrop. useEffect(() => { const previouslyFocused = document.activeElement as HTMLElement | null; const firstFocusable = dialogRef.current?.querySelector(FOCUSABLE_SELECTOR); firstFocusable?.focus(); return () => { previouslyFocused?.focus(); }; }, []); function handleKeyDown(event: React.KeyboardEvent) { if (event.key === "Escape") { if (!isSaving) { event.stopPropagation(); onClose(); } return; } if (event.key !== "Tab" || !dialogRef.current) { return; } const focusable = Array.from( dialogRef.current.querySelectorAll(FOCUSABLE_SELECTOR) ); if (focusable.length === 0) { return; } const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } return ( <>

{title}

{description ? (

{description}

) : null}
{children}
); }