diff --git a/apps/pwa/src/components/CredentialSheet.tsx b/apps/pwa/src/components/CredentialSheet.tsx index 8c9cef2..d640c8e 100644 --- a/apps/pwa/src/components/CredentialSheet.tsx +++ b/apps/pwa/src/components/CredentialSheet.tsx @@ -32,6 +32,7 @@ import { type SaveMyCredentialPayload, } from '../api/client.js'; import { useIsPhone } from '../hooks/useIsPhone.js'; +import { useFocusTrap } from '../hooks/useFocusTrap.js'; export type CredentialSheetMode = 'admin-rotate' | 'admin-add' | 'self-service'; @@ -83,6 +84,9 @@ export function CredentialSheet({ const [validationError, setValidationError] = useState(null); // Focus the heading/first focusable element on open (a11y) const headingRef = useRef(null); + // WR-01: trap Tab/Shift+Tab inside the dialog (matches aria-modal="true"). + const dialogRef = useRef(null); + const handleDialogKeyDown = useFocusTrap(dialogRef); // Escape key closes the sheet (SettingsSheet pattern) useEffect(() => { @@ -172,9 +176,11 @@ export function CredentialSheet({ {/* Sheet */}
(null); const closeButtonRef = useRef(null); + // WR-01: trap Tab/Shift+Tab inside the dialog (matches aria-modal="true"). + const dialogRef = useRef(null); + const handleDialogKeyDown = useFocusTrap(dialogRef); // Compute initial toggle on/off state per UI-SPEC toggle initial state rule: // on when notificationsEnabled !== '0' AND permission === 'granted' AND isSubscribed @@ -198,9 +202,11 @@ export function SettingsSheet({ isOpen, onClose }: SettingsSheetProps) { {/* Sheet */}
(null); const headingRef = useRef(null); + // WR-01: trap Tab/Shift+Tab inside the dialog (matches aria-modal="true"). + const dialogRef = useRef(null); + const handleDialogKeyDown = useFocusTrap(dialogRef); useEffect(() => { if (!isOpen) return; @@ -665,9 +674,11 @@ function ChangePasswordSheet({ isOpen, onClose }: ChangePasswordSheetProps) { {/* Sheet */}
(null); const headingRef = useRef(null); + // WR-01: trap Tab/Shift+Tab inside the dialog (matches aria-modal="true"). + const dialogRef = useRef(null); + const handleDialogKeyDown = useFocusTrap(dialogRef); useEffect(() => { if (!isOpen) return; @@ -976,9 +990,11 @@ function LinkOidcSheet({ isOpen, onClose }: LinkOidcSheetProps) { {/* Sheet */}
(null); const headingRef = useRef(null); + // WR-01: trap Tab/Shift+Tab inside the dialog (matches aria-modal="true"). + const dialogRef = useRef(null); + const handleDialogKeyDown = useFocusTrap(dialogRef); // Escape closes the sheet useEffect(() => { @@ -1417,9 +1421,11 @@ function ResetPasswordSheet({ isOpen, onClose, onSuccess, member }: ResetPasswor {/* Sheet — phone: bottom-sheet / desktop: centered modal (D-09) */}