diff --git a/src/pages/AdminBulkActions.tsx b/src/pages/AdminBulkActions.tsx index 578f215..15116da 100644 --- a/src/pages/AdminBulkActions.tsx +++ b/src/pages/AdminBulkActions.tsx @@ -24,6 +24,7 @@ import { } from '../api/adminBulkActions'; import { usePlatform } from '../platform/hooks/usePlatform'; import { useCurrency } from '../hooks/useCurrency'; +import { useFocusTrap } from '@/hooks/useFocusTrap'; import { cn } from '@/lib/utils'; // ============ Types ============ @@ -802,16 +803,10 @@ function ActionModal({ }, [modal.open, modal.action]); // Escape key handler — only when not loading - useEffect(() => { - if (!modal.open) return; - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape' && !modal.loading) { - onClose(); - } - }; - document.addEventListener('keydown', handleKeyDown); - return () => document.removeEventListener('keydown', handleKeyDown); - }, [modal.open, modal.loading, onClose]); + // Focus trap + scroll lock + Escape close (only while not loading). + const dialogRef = useFocusTrap(modal.open, { + onEscape: modal.loading ? undefined : onClose, + }); // Count active paid subscriptions among selected ones const activePaidCount = useMemo(() => { @@ -1118,8 +1113,6 @@ function ActionModal({ paddingLeft: 'max(1rem, env(safe-area-inset-left))', paddingRight: 'max(1rem, env(safe-area-inset-right))', }} - role="dialog" - aria-modal="true" > {/* Backdrop — no close on click during loading */}
- {/* Modal */} -
+ {/* Modal — role/aria-modal live on the focus-trapped element so AT + announces and Tab cycles correctly. */} +
{/* Header */}
-

{t(actionLabelKeys[modal.action])}

+

+ {t(actionLabelKeys[modal.action])} +

{!modal.loading && (