# All files / src/components/display ConfirmDialog.tsx

0% Statements 0/55
0% Branches 0/1
0% Functions 0/1
0% Lines 0/55

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>
      );
    }