From 84d46a7888bb72a7c477008401f026eeb597822d Mon Sep 17 00:00:00 2001 From: SundayEmmanualEkwe Date: Sat, 27 Jun 2026 13:29:34 +0100 Subject: [PATCH] feat(a11y): add opt-in backdrop-click cancel to ConfirmDialog --- TODO.md | 17 ++++--- src/components/ConfirmDialog.tsx | 16 ++++++ .../__tests__/ConfirmDialog.test.tsx | 51 ++++++++++++++++++- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index 289737e..20d1671 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,12 @@ # TODO -- [x] Inspect existing numeric validation helper (`src/lib/validateNumber.ts`). -- [x] Verify usage/edit/new pages already import and use the helper. -- [ ] Update helper tests (`src/lib/__tests__/validateNumber.test.ts`) to cover required edge cases for both ranges. -- [ ] Adjust `src/app/usage/page.test.tsx` to assert validation message is surfaced through `TextField` error UI for non-integer requests. -- [ ] Update `README.md` with validation rule summary (price: >=0 int; requests: >=1 int). -- [ ] Run `npm run lint`, `npm run typecheck`, `npm test`, `npm run test:coverage`. -- [ ] Ensure coverage threshold (>=95%) for helper + changed pages. -- [ ] Commit with message: `refactor(forms): extract shared numeric-field validation helper`. +- [ ] Implement `dismissOnBackdrop?: boolean` prop and accessible backdrop click cancel in `src/components/ConfirmDialog.tsx` (guard clicks so only backdrop—not panel—cancels). +- [ ] Update JSDoc documentation for the new prop. +- [ ] Extend tests in `src/components/__tests__/ConfirmDialog.test.tsx`: + - [ ] backdrop click cancels only when enabled + - [ ] backdrop click does not cancel when prop is off + - [ ] clicks inside dialog panel do not cancel (when enabled) + - [ ] Escape handling remains unchanged +- [ ] Run `npm test`, `npm run lint`, `npm run typecheck`, and `npm run build`. +- [ ] Capture/record npm test output and add short a11y note in final summary. diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index 38ec955..c845d5c 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -14,6 +14,11 @@ type Props = { description?: ReactNode; confirmLabel?: string; cancelLabel?: string; + /** + * When true, clicking the backdrop (outside the dialog panel) calls `onCancel`. + * Escape and the Cancel button always dismiss the dialog. + */ + dismissOnBackdrop?: boolean; onConfirm: () => void; onCancel: () => void; }; @@ -43,6 +48,7 @@ export function ConfirmDialog({ description, confirmLabel = "Confirm", cancelLabel = "Cancel", + dismissOnBackdrop = false, onConfirm, onCancel, }: Props) { @@ -122,6 +128,15 @@ export function ConfirmDialog({ } }; + const handleBackdropMouseDown = (event: React.MouseEvent) => { + if (!dismissOnBackdrop) return; + + // Only dismiss when the user clicked directly on the backdrop, not on the dialog panel. + if (event.target !== event.currentTarget) return; + + onCancel(); + }; + if (!open) return null; return (
diff --git a/src/components/__tests__/ConfirmDialog.test.tsx b/src/components/__tests__/ConfirmDialog.test.tsx index ffce313..e0aebc6 100644 --- a/src/components/__tests__/ConfirmDialog.test.tsx +++ b/src/components/__tests__/ConfirmDialog.test.tsx @@ -2,8 +2,14 @@ import { fireEvent, render, screen } from "@testing-library/react"; import { useState } from "react"; import { ConfirmDialog } from "../ConfirmDialog"; -function ConfirmDialogHarness() { +function ConfirmDialogHarness({ + dismissOnBackdrop = false, +}: { + dismissOnBackdrop?: boolean; +}) { const [open, setOpen] = useState(false); + const onCancel = () => setOpen(false); + return ( <>