Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/ui/modals/ConfirmDialog.tsx
T

117 lines
4.1 KiB
TypeScript

import { Fragment, forwardRef, useEffect, useImperativeHandle, useState, Ref } from "react";
import { useTranslation } from "react-i18next";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "~/components/ui/alert-dialog";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
export interface RefConfirmDialog {
show: (options: {
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
destructive?: boolean;
typeToConfirm?: string;
onConfirm?: () => void;
}) => void;
}
interface Props {
destructive?: boolean;
}
const ConfirmDialog = (props: Props, ref: Ref<RefConfirmDialog>) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [title, setTitle] = useState("");
const [description, setDescription] = useState<string | undefined>();
const [confirmText, setConfirmText] = useState(t("shared.confirm"));
const [cancelText, setCancelText] = useState(t("shared.cancel"));
const [destructive, setDestructive] = useState(props.destructive || false);
const [typeToConfirm, setTypeToConfirm] = useState<string | undefined>();
const [typedConfirmation, setTypedConfirmation] = useState("");
const [onConfirmCallback, setOnConfirmCallback] = useState<(() => void) | undefined>();
const isConfirmEnabled = !typeToConfirm || typedConfirmation.trim().toLowerCase() === typeToConfirm.toLowerCase();
const handleCancel = () => {
setOpen(false);
setTypedConfirmation("");
};
const handleConfirm = () => {
if (isConfirmEnabled) {
setOpen(false);
setTypedConfirmation("");
if (onConfirmCallback) {
onConfirmCallback();
}
}
};
useImperativeHandle(ref, () => ({
show: ({ title, description, confirmText, cancelText, destructive, typeToConfirm, onConfirm }) => {
setTitle(title || t("shared.confirm"));
setDescription(description || t("shared.warningCannotUndo"));
setConfirmText(confirmText || t("shared.confirm"));
setCancelText(cancelText || t("shared.cancel"));
setDestructive(destructive ?? props.destructive ?? false);
setTypeToConfirm(typeToConfirm);
setTypedConfirmation("");
setOnConfirmCallback(() => onConfirm);
setOpen(true);
},
}));
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
{description && <AlertDialogDescription>{description}</AlertDialogDescription>}
</AlertDialogHeader>
{typeToConfirm && (
// Subframe ConfirmDialog spacing handled by AlertDialogContent gap-6 grid; input block uses its own gap-2.
<div className="flex flex-col gap-2">
<Label htmlFor="confirm-text" className="block">
{t("components.confirmDialog.typePrefix")} <span className="font-bold">{typeToConfirm}</span> {t("components.confirmDialog.typeSuffix")}
</Label>
<Input
id="confirm-text"
autoComplete="off"
value={typedConfirmation}
onChange={(e) => setTypedConfirmation(e.target.value)}
placeholder={typeToConfirm}
className={destructive ? "border-border-negative-solid focus-visible:shadow-focus" : ""}
autoFocus
/>
</div>
)}
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel}>{cancelText}</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirm}
disabled={!isConfirmEnabled}
className={destructive ? "bg-surface-feedback-solid-negative-default hover:bg-surface-feedback-solid-negative-hover text-text-neutral-on-solid disabled:bg-surface-neutral-disabled disabled:text-text-neutral-disabled" : undefined}
>
{confirmText}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default forwardRef(ConfirmDialog);