From 7b5082e99d319609c18e5492bb11709f69f77c5c Mon Sep 17 00:00:00 2001 From: Philip Michael Date: Sat, 25 Jul 2026 14:01:29 +0000 Subject: [PATCH] docs(dialogs): document ConfirmDialog focus and escape contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs/components/ConfirmDialog.md as the canonical reference for the dialog's focus-trap contract and caller responsibilities. Document every ConfirmDialogProp including the 'Confirm' / 'Cancel' label defaults, the role='dialog' vs role='alertdialog' tone behavior, the shared useDialogFocusTrap wiring, the FOCUSABLE_SELECTORS list with its empty-focusable fallback, the Escape and backdrop contracts, the aria-hidden + inert background hiding rules, and the explicit rule that callers must retain a trigger ref and restore focus themselves. Use src/components/ActionPanel.tsx as the worked example — including the 'capture event.currentTarget at click time' pattern that beats static ref={triggerRef} props when several buttons share a dialog. Expand src/components/__tests__/ConfirmDialog.test.tsx to lock in the contract as an executable spec: defaults, backdrop click, ARIA wiring including useId across renders, repeated open/close cycles, pre-existing aria-hidden and inert preservation across cleanup, no-focusable fallback via the shared hook, caller-owned focus restoration, and per-button focus spies that catch any future regression where Escape causes ConfirmDialog to refocus itself. Adds two jest-axe audits. Module coverage: Lines 100%, Statements >= 95%. --- docs/components/ConfirmDialog.md | 251 +++++++ .../__tests__/ConfirmDialog.test.tsx | 670 +++++++++++++++++- 2 files changed, 882 insertions(+), 39 deletions(-) create mode 100644 docs/components/ConfirmDialog.md diff --git a/docs/components/ConfirmDialog.md b/docs/components/ConfirmDialog.md new file mode 100644 index 00000000..3d122152 --- /dev/null +++ b/docs/components/ConfirmDialog.md @@ -0,0 +1,251 @@ +# ConfirmDialog + +`ConfirmDialog` is the shared, accessible confirmation modal used by destructive and irreversible flows in TalentTrust (e.g. submitting a milestone for approval, releasing escrow funds, opening a dispute). It traps keyboard focus inside the dialog, dismisses on **Escape** or backdrop click, and surfaces a `role="alertdialog"` when `tone="destructive"`. + +The component is intentionally minimal: it does **not** own focus restoration after close, do its own routing, or talk to the network. The owning component is responsible for opening, closing, and returning focus to its trigger once the dialog finishes. + +## Location + +`src/components/ConfirmDialog.tsx` + +## Props + +| Prop | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `isOpen` | `boolean` | Yes | — | Whether the dialog is open. When `false`, the component renders nothing. | +| `title` | `string` | Yes | — | Dialog heading. Rendered as `

` and linked via `aria-labelledby` (id generated by React `useId`). | +| `description` | `string` | Yes | — | Body copy explaining the consequence. Rendered as `

` and linked via `aria-describedby` (id generated by React `useId`). | +| `confirmLabel` | `string` | No | `"Confirm"` | Accessible name of the confirm button. | +| `cancelLabel` | `string` | No | `"Cancel"` | Accessible name of the cancel button. | +| `tone` | `'default' \| 'destructive'` | No | `'default'` | When `'destructive'`, the dialog uses `role="alertdialog"`. Otherwise it uses `role="dialog"`. | +| `onConfirm` | `() => void` | Yes | — | Callback fired when the user clicks the confirm button. | +| `onCancel` | `() => void` | Yes | — | Callback fired when the user clicks the cancel button, hits **Escape**, or clicks the backdrop. The caller decides what "cancel" means — typically closing the dialog and restoring focus. | + +All props are required unless marked otherwise. The component never mutates any of them. + +## Output contract + +When `isOpen` is `true`, the rendered tree is: + +```html +