From bfe099deda028362af2877274e39c0b2dbcea4c0 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Fri, 17 Apr 2026 22:04:53 +0000 Subject: [PATCH] fix(GRO-786): remove duplicate dialog role and restore focus trap - Remove role="dialog" and aria-modal="true" from outer backdrop div - Keep ARIA attributes only on inner dialog div (the actual modal) - Restore useEffect focus management: auto-focus first element, Tab cycle wrapping, Escape key handler, focus restore on close Co-Authored-By: Paperclip --- apps/web/src/pages/Clients.tsx | 42 ++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/Clients.tsx b/apps/web/src/pages/Clients.tsx index 4606a13..af4b47b 100644 --- a/apps/web/src/pages/Clients.tsx +++ b/apps/web/src/pages/Clients.tsx @@ -854,14 +854,52 @@ export function ClientsPage() { function Modal({ children, onClose, title, titleStyle }: { children: React.ReactNode; onClose: () => void; title: string; titleStyle?: React.CSSProperties }) { const titleId = useId(); + const modalRef = useRef(null); + + useEffect(() => { + const previouslyFocused = document.activeElement as HTMLElement; + const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + const focusableElements = modalRef.current?.querySelectorAll(focusableSelectors); + const firstFocusable = focusableElements?.[0]; + firstFocusable?.focus(); + + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") { + onClose(); + return; + } + if (e.key !== "Tab") return; + if (!modalRef.current) return; + const focusables = modalRef.current.querySelectorAll(focusableSelectors); + const first = focusables[0]; + const last = focusables[focusables.length - 1]; + if (e.shiftKey) { + if (document.activeElement === first) { + e.preventDefault(); + last?.focus(); + } + } else { + if (document.activeElement === last) { + e.preventDefault(); + first?.focus(); + } + } + } + + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + previouslyFocused?.focus(); + }; + }, [onClose]); + return (
{ if (e.target === e.currentTarget) onClose(); }} >