add confirm dialog component

This commit is contained in:
Andrew Davis 2025-04-03 23:51:43 +10:00
parent 8a056960bf
commit 5fcc1f4079
No known key found for this signature in database
GPG Key ID: 30AB5B89A109D044
3 changed files with 120 additions and 39 deletions

View File

@ -0,0 +1,106 @@
import { ExclamationTriangleIcon, CheckCircleIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
import { cx } from "@/cva.config";
import { Button } from "@/components/Button";
import Modal from "@/components/Modal";
type Variant = "danger" | "success" | "warning" | "info";
interface ConfirmDialogProps {
open: boolean;
onClose: () => void;
title: string;
description: string;
variant?: Variant;
confirmText?: string;
cancelText?: string | null;
onConfirm: () => void;
isConfirming?: boolean;
}
const variantConfig = {
danger: {
icon: ExclamationTriangleIcon,
iconClass: "text-red-600",
iconBgClass: "bg-red-100",
buttonTheme: "danger",
},
success: {
icon: CheckCircleIcon,
iconClass: "text-green-600",
iconBgClass: "bg-green-100",
buttonTheme: "primary",
},
warning: {
icon: ExclamationTriangleIcon,
iconClass: "text-yellow-600",
iconBgClass: "bg-yellow-100",
buttonTheme: "lightDanger",
},
info: {
icon: InformationCircleIcon,
iconClass: "text-blue-600",
iconBgClass: "bg-blue-100",
buttonTheme: "primary",
},
} as Record<Variant, {
icon: React.ElementType;
iconClass: string;
iconBgClass: string;
buttonTheme: "danger" | "primary" | "blank" | "light" | "lightDanger";
}>;
export function ConfirmDialog({
open,
onClose,
title,
description,
variant = "info",
confirmText = "Confirm",
cancelText = "Cancel",
onConfirm,
isConfirming = false,
}: ConfirmDialogProps) {
const { icon: Icon, iconClass, iconBgClass, buttonTheme } = variantConfig[variant];
return (
<Modal open={open} onClose={onClose}>
<div className="mx-auto max-w-xl px-4 transition-all duration-300 ease-in-out">
<div className="relative w-full overflow-hidden rounded-lg bg-white p-6 text-left align-middle shadow-xl transition-all dark:bg-slate-800 pointer-events-auto">
<div className="space-y-4">
<div className="sm:flex sm:items-start">
<div className={cx("mx-auto flex size-12 shrink-0 items-center justify-center rounded-full sm:mx-0 sm:size-10", iconBgClass)}>
<Icon aria-hidden="true" className={cx("size-6", iconClass)} />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h2 className="text-lg font-bold leading-tight text-black dark:text-white">
{title}
</h2>
<div className="mt-2 text-sm leading-snug text-slate-600 dark:text-slate-400">
{description}
</div>
</div>
</div>
<div className="flex justify-end gap-x-2">
{cancelText && (
<Button
size="SM"
theme="blank"
text={cancelText}
onClick={onClose}
/>
)}
<Button
size="SM"
theme={buttonTheme}
text={isConfirming ? `${confirmText}...` : confirmText}
onClick={onConfirm}
disabled={isConfirming}
/>
</div>
</div>
</div>
</div>
</Modal>
);
}

View File

@ -1,15 +1,15 @@
import { useState } from "react"; import { useState } from "react";
import { LuPlus, LuInfo } from "react-icons/lu"; import { LuPlus } from "react-icons/lu";
import { KeySequence } from "@/hooks/stores"; import { KeySequence } from "@/hooks/stores";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { InputFieldWithLabel, FieldError } from "@/components/InputField"; import { InputFieldWithLabel, FieldError } from "@/components/InputField";
import Fieldset from "@/components/Fieldset"; import Fieldset from "@/components/Fieldset";
import { MacroStepCard } from "@/components/MacroStepCard"; import { MacroStepCard } from "@/components/MacroStepCard";
import Modal from "@/components/Modal";
import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP } from "@/constants/macros"; import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP } from "@/constants/macros";
import FieldLabel from "@/components/FieldLabel"; import FieldLabel from "@/components/FieldLabel";
import { ConfirmDialog } from "@/components/ConfirmDialog";
interface ValidationErrors { interface ValidationErrors {
name?: string; name?: string;
@ -312,44 +312,19 @@ export function MacroForm({
</div> </div>
</div> </div>
<Modal <ConfirmDialog
open={showDeleteConfirm} open={showDeleteConfirm}
onClose={() => setShowDeleteConfirm(false)} onClose={() => setShowDeleteConfirm(false)}
> title="Delete Macro"
<div className="mx-auto max-w-xl px-4 transition-all duration-300 ease-in-out"> description="Are you sure you want to delete this macro? This action cannot be undone."
<div className="relative w-full overflow-hidden rounded-lg bg-white p-6 text-left align-middle shadow-xl transition-all dark:bg-slate-800 pointer-events-auto"> variant="danger"
<div className="space-y-4"> confirmText={isDeleting ? "Deleting" : "Delete"}
<div className="space-y-0"> onConfirm={() => {
<h2 className="text-lg font-bold leading-tight text-black dark:text-white"> onDelete?.();
Delete Macro setShowDeleteConfirm(false);
</h2> }}
<div className="text-sm leading-snug text-slate-600 dark:text-slate-400"> isConfirming={isDeleting}
Are you sure you want to delete this macro? This action cannot be undone. />
</div>
</div>
<div className="flex justify-end gap-x-2">
<Button
size="SM"
theme="blank"
text="Cancel"
onClick={() => setShowDeleteConfirm(false)}
/>
<Button
size="SM"
theme="danger"
text={isDeleting ? "Deleting..." : "Delete"}
onClick={() => {
onDelete?.();
setShowDeleteConfirm(false);
}}
disabled={isDeleting}
/>
</div>
</div>
</div>
</div>
</Modal>
</> </>
); );
} }

View File

@ -1,4 +1,4 @@
import { LuArrowUp, LuArrowDown, LuX, LuInfo } from "react-icons/lu"; import { LuArrowUp, LuArrowDown, LuX } from "react-icons/lu";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { Combobox } from "@/components/Combobox"; import { Combobox } from "@/components/Combobox";