/** * ListDeleteDialog — two-tap delete confirmation for whole lists (D-06). * * Mirrors the structure of DeleteConfirmationDialog.tsx but driven by props * rather than calendarStore — the calendar dialog is NOT modified (stable). * * Layout (matches Phase 3 pattern): * - Backdrop: --color-overlay * - Centered modal, max-width 320px * - Focus trap while open * - Escape to cancel * * Accessibility: * - role="dialog", aria-modal="true" * - Focus moves to dialog on open * - Escape key cancels without deleting * * UI-SPEC §"List Delete": * - Heading: "Delete list?" * - Body: "{name}" and all its items will be permanently removed." * - Cancel (ghost) + Delete (destructive) * - On confirm: optimistic — navigate back, invalidate ['lists'] * - On error: failure toast "Couldn't delete. Try again." * * T-04-06 XSS guard: list.name rendered as plain-text JSX child only. * * Props: * list — the List to delete (null = dialog closed) * onClose — called on cancel or after successful delete * onConfirm — called to initiate the delete (parent handles mutation) * isPending — whether delete mutation is in-flight */ import { useEffect, useRef } from 'react'; import { Trash2 } from 'lucide-react'; import type { List } from '../api/listsClient.js'; interface ListDeleteDialogProps { list: List | null; onClose: () => void; onConfirm: () => void; isPending?: boolean; } export function ListDeleteDialog({ list, onClose, onConfirm, isPending = false, }: ListDeleteDialogProps) { const dialogRef = useRef(null); // Focus trap — focus the dialog when it opens useEffect(() => { if (list && dialogRef.current) { dialogRef.current.focus(); } }, [list]); // Escape key listener — cancel without deleting (D-06: two-tap confirmation) useEffect(() => { if (!list) return; const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); } }; document.addEventListener('keydown', onKeyDown); return () => document.removeEventListener('keydown', onKeyDown); }, [list, onClose]); if (!list) return null; return ( <> {/* Backdrop */}