Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | import { useId, type JSX } from "react"; import { AnimatePresence, motion } from "motion/react"; import styles from "./ConfirmDialog.module.scss"; /** Props for the ConfirmDialog component. */ interface ConfirmDialogProps { /** Whether the dialog is currently visible. */ isOpen: boolean; /** Short, action-oriented title (e.g. "Delete Task?"). */ title: string; /** Consequence description shown below the title. */ description?: string; /** Label for the danger confirm button. Defaults to "Delete". */ confirmLabel?: string; /** Called when the user confirms the destructive action. */ onConfirm: () => void; /** Called when the user cancels or clicks the overlay backdrop. */ onCancel: () => void; } /** * Modal confirmation dialog with glass card aesthetic and motion animation. * * Replaces native `window.confirm()` for destructive actions, providing a * styled in-app dialog that matches the dark glass UI. */ export function ConfirmDialog({ isOpen, title, description, confirmLabel = "Delete", onConfirm, onCancel, }: ConfirmDialogProps): JSX.Element { const titleId = useId(); const descriptionId = useId(); return ( <AnimatePresence> {isOpen && ( <motion.div className={styles.overlay} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }} onClick={onCancel} onKeyDown={(e) => { if (e.key === "Escape") onCancel(); }} role="dialog" aria-modal="true" aria-labelledby={titleId} aria-describedby={description ? descriptionId : undefined} > <motion.div className={styles.dialog} initial={{ opacity: 0, scale: 0.93, y: -10 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.93, y: -10 }} transition={{ duration: 0.15, ease: [0.16, 1, 0.3, 1] }} onClick={(e) => e.stopPropagation()} > <h3 id={titleId} className={styles.title}> {title} </h3> {description && ( <p id={descriptionId} className={styles.description}> {description} </p> )} <div className={styles.actions}> <button type="button" className={styles.cancelButton} onClick={onCancel} autoFocus> Cancel </button> <button type="button" className={styles.confirmButton} onClick={onConfirm}> {confirmLabel} </button> </div> </motion.div> </motion.div> )} </AnimatePresence> ); } |