From 8b7d9877748f92fcbb9af001558e3c8c2b506561 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 10:25:37 +0530 Subject: [PATCH 1/6] feat: add accessible bulk-action review modal (fixes #236) --- .../src/components/BulkActionReviewModal.tsx | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 frontend/src/components/BulkActionReviewModal.tsx diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx new file mode 100644 index 00000000..ec49ecfb --- /dev/null +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -0,0 +1,111 @@ +import { useEffect, useRef } from "react"; + +interface BulkActionReviewModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + actionLabel?: string; + selectedCount?: number; +} + +export default function BulkActionReviewModal({ + isOpen, + onClose, + onConfirm, + actionLabel = "Delete", + selectedCount = 0, +}: BulkActionReviewModalProps) { + const cancelRef = useRef(null); + const modalRef = useRef(null); + + // Auto-focus Cancel button when modal opens (safer for destructive actions) + useEffect(() => { + if (isOpen) cancelRef.current?.focus(); + }, [isOpen]); + + // Keyboard: Escape closes, Tab traps focus inside modal + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") onClose(); + if (e.key === "Tab") { + const focusable = modalRef.current?.querySelectorAll( + 'button, [href], input, [tabindex]:not([tabindex="-1"])' + ); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }; + + if (!isOpen) return null; + + return ( + // Backdrop + + ); +} From e970e2c50a31f33274bc7fda6098bad13b9a3351 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 10:42:09 +0530 Subject: [PATCH 2/6] style: fix formatting for BulkActionReviewModal --- frontend/src/components/BulkActionReviewModal.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx index ec49ecfb..9700abdd 100644 --- a/frontend/src/components/BulkActionReviewModal.tsx +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -28,7 +28,7 @@ export default function BulkActionReviewModal({ if (e.key === "Escape") onClose(); if (e.key === "Tab") { const focusable = modalRef.current?.querySelectorAll( - 'button, [href], input, [tabindex]:not([tabindex="-1"])' + 'button, [href], input, [tabindex]:not([tabindex="-1"])', ); if (!focusable || focusable.length === 0) return; const first = focusable[0]; @@ -76,8 +76,7 @@ export default function BulkActionReviewModal({ id="bulk-action-desc" className="text-sm text-gray-600 dark:text-gray-300 mb-6" > - You are about to{" "} - {actionLabel.toLowerCase()}{" "} + You are about to {actionLabel.toLowerCase()}{" "} {selectedCount} item{selectedCount !== 1 ? "s" : ""} From 5659289f4715db1522da32c52c9764fd38d406a9 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 10:59:04 +0530 Subject: [PATCH 3/6] style: remove trailing whitespace from classNames --- frontend/src/components/BulkActionReviewModal.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx index 9700abdd..6980d822 100644 --- a/frontend/src/components/BulkActionReviewModal.tsx +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -88,18 +88,14 @@ export default function BulkActionReviewModal({ From aa112b956c08664a37a24701d37ceb9a26ec1587 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 11:00:08 +0530 Subject: [PATCH 4/6] style: trim trailing whitespace --- .../src/components/BulkActionReviewModal.tsx | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx index 6980d822..42416fc2 100644 --- a/frontend/src/components/BulkActionReviewModal.tsx +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -1,106 +1,106 @@ -import { useEffect, useRef } from "react"; - -interface BulkActionReviewModalProps { - isOpen: boolean; - onClose: () => void; - onConfirm: () => void; - actionLabel?: string; - selectedCount?: number; -} - -export default function BulkActionReviewModal({ - isOpen, - onClose, - onConfirm, - actionLabel = "Delete", - selectedCount = 0, -}: BulkActionReviewModalProps) { - const cancelRef = useRef(null); - const modalRef = useRef(null); - - // Auto-focus Cancel button when modal opens (safer for destructive actions) - useEffect(() => { - if (isOpen) cancelRef.current?.focus(); - }, [isOpen]); - - // Keyboard: Escape closes, Tab traps focus inside modal - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Escape") onClose(); - if (e.key === "Tab") { - const focusable = modalRef.current?.querySelectorAll( - 'button, [href], input, [tabindex]:not([tabindex="-1"])', - ); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); - } - } - }; - - if (!isOpen) return null; - - return ( - // Backdrop - - ); -} +import { useEffect, useRef } from "react"; + +interface BulkActionReviewModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + actionLabel?: string; + selectedCount?: number; +} + +export default function BulkActionReviewModal({ + isOpen, + onClose, + onConfirm, + actionLabel = "Delete", + selectedCount = 0, +}: BulkActionReviewModalProps) { + const cancelRef = useRef(null); + const modalRef = useRef(null); + + // Auto-focus Cancel button when modal opens (safer for destructive actions) + useEffect(() => { + if (isOpen) cancelRef.current?.focus(); + }, [isOpen]); + + // Keyboard: Escape closes, Tab traps focus inside modal + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") onClose(); + if (e.key === "Tab") { + const focusable = modalRef.current?.querySelectorAll( + 'button, [href], input, [tabindex]:not([tabindex="-1"])', + ); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }; + + if (!isOpen) return null; + + return ( + // Backdrop + + ); +} From 29dfbe6e394c1f1e51626d6c1e3f12af9db705c6 Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 11:01:52 +0530 Subject: [PATCH 5/6] style: convert CRLF to LF line endings --- .../src/components/BulkActionReviewModal.tsx | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx index 42416fc2..6980d822 100644 --- a/frontend/src/components/BulkActionReviewModal.tsx +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -1,106 +1,106 @@ -import { useEffect, useRef } from "react"; - -interface BulkActionReviewModalProps { - isOpen: boolean; - onClose: () => void; - onConfirm: () => void; - actionLabel?: string; - selectedCount?: number; -} - -export default function BulkActionReviewModal({ - isOpen, - onClose, - onConfirm, - actionLabel = "Delete", - selectedCount = 0, -}: BulkActionReviewModalProps) { - const cancelRef = useRef(null); - const modalRef = useRef(null); - - // Auto-focus Cancel button when modal opens (safer for destructive actions) - useEffect(() => { - if (isOpen) cancelRef.current?.focus(); - }, [isOpen]); - - // Keyboard: Escape closes, Tab traps focus inside modal - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Escape") onClose(); - if (e.key === "Tab") { - const focusable = modalRef.current?.querySelectorAll( - 'button, [href], input, [tabindex]:not([tabindex="-1"])', - ); - if (!focusable || focusable.length === 0) return; - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (e.shiftKey && document.activeElement === first) { - e.preventDefault(); - last.focus(); - } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); - first.focus(); - } - } - }; - - if (!isOpen) return null; - - return ( - // Backdrop - - ); -} +import { useEffect, useRef } from "react"; + +interface BulkActionReviewModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + actionLabel?: string; + selectedCount?: number; +} + +export default function BulkActionReviewModal({ + isOpen, + onClose, + onConfirm, + actionLabel = "Delete", + selectedCount = 0, +}: BulkActionReviewModalProps) { + const cancelRef = useRef(null); + const modalRef = useRef(null); + + // Auto-focus Cancel button when modal opens (safer for destructive actions) + useEffect(() => { + if (isOpen) cancelRef.current?.focus(); + }, [isOpen]); + + // Keyboard: Escape closes, Tab traps focus inside modal + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") onClose(); + if (e.key === "Tab") { + const focusable = modalRef.current?.querySelectorAll( + 'button, [href], input, [tabindex]:not([tabindex="-1"])', + ); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }; + + if (!isOpen) return null; + + return ( + // Backdrop + + ); +} From 149948fd46081ce33085e70d6f1b8d707d2d0cfc Mon Sep 17 00:00:00 2001 From: Tanisha-sharma7302 Date: Sun, 24 May 2026 11:03:14 +0530 Subject: [PATCH 6/6] style: recreate file with LF line endings --- frontend/src/components/BulkActionReviewModal.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/BulkActionReviewModal.tsx b/frontend/src/components/BulkActionReviewModal.tsx index 6980d822..e0cf76b2 100644 --- a/frontend/src/components/BulkActionReviewModal.tsx +++ b/frontend/src/components/BulkActionReviewModal.tsx @@ -18,17 +18,15 @@ export default function BulkActionReviewModal({ const cancelRef = useRef(null); const modalRef = useRef(null); - // Auto-focus Cancel button when modal opens (safer for destructive actions) useEffect(() => { if (isOpen) cancelRef.current?.focus(); }, [isOpen]); - // Keyboard: Escape closes, Tab traps focus inside modal const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") onClose(); if (e.key === "Tab") { const focusable = modalRef.current?.querySelectorAll( - 'button, [href], input, [tabindex]:not([tabindex="-1"])', + 'button, [href], input, [tabindex]:not([tabindex="-1"])' ); if (!focusable || focusable.length === 0) return; const first = focusable[0]; @@ -46,13 +44,11 @@ export default function BulkActionReviewModal({ if (!isOpen) return null; return ( - // Backdrop