119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* ConfirmDialog — 공통 확인/취소 다이얼로그
|
|
*
|
|
* 사용법:
|
|
* const { confirm, ConfirmDialogComponent } = useConfirmDialog();
|
|
*
|
|
* // JSX에 넣기
|
|
* <ConfirmDialogComponent />
|
|
*
|
|
* // 호출
|
|
* const ok = await confirm("삭제하시겠습니까?", { description: "이 작업은 되돌릴 수 없습니다." });
|
|
* if (ok) { ... }
|
|
*/
|
|
|
|
import React, { useState, useCallback, useRef } from "react";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogContent,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogCancel,
|
|
AlertDialogAction,
|
|
} from "@/components/ui/alert-dialog";
|
|
import { AlertTriangle, Info, Trash2 } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type ConfirmVariant = "default" | "destructive" | "info";
|
|
|
|
interface ConfirmOptions {
|
|
description?: string;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
variant?: ConfirmVariant;
|
|
}
|
|
|
|
const VARIANT_CONFIG = {
|
|
default: {
|
|
icon: Info,
|
|
iconClass: "text-primary",
|
|
buttonClass: "",
|
|
},
|
|
destructive: {
|
|
icon: Trash2,
|
|
iconClass: "text-destructive",
|
|
buttonClass: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
},
|
|
info: {
|
|
icon: Info,
|
|
iconClass: "text-blue-500",
|
|
buttonClass: "",
|
|
},
|
|
};
|
|
|
|
export function useConfirmDialog() {
|
|
const [open, setOpen] = useState(false);
|
|
const [title, setTitle] = useState("");
|
|
const [options, setOptions] = useState<ConfirmOptions>({});
|
|
const resolveRef = useRef<((value: boolean) => void) | null>(null);
|
|
|
|
const confirm = useCallback((msg: string, opts?: ConfirmOptions): Promise<boolean> => {
|
|
setTitle(msg);
|
|
setOptions(opts || {});
|
|
setOpen(true);
|
|
return new Promise<boolean>((resolve) => {
|
|
resolveRef.current = resolve;
|
|
});
|
|
}, []);
|
|
|
|
const handleConfirm = () => {
|
|
setOpen(false);
|
|
resolveRef.current?.(true);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setOpen(false);
|
|
resolveRef.current?.(false);
|
|
};
|
|
|
|
const variant = options.variant || "default";
|
|
const config = VARIANT_CONFIG[variant];
|
|
const Icon = config.icon;
|
|
|
|
const ConfirmDialogComponent = (
|
|
<AlertDialog open={open} onOpenChange={(v) => { if (!v) handleCancel(); }}>
|
|
<AlertDialogContent className="max-w-[420px]">
|
|
<AlertDialogHeader>
|
|
<div className="flex items-start gap-3">
|
|
<div className={cn("mt-0.5 shrink-0", config.iconClass)}>
|
|
<Icon className="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<AlertDialogTitle className="text-base">{title}</AlertDialogTitle>
|
|
{options.description && (
|
|
<AlertDialogDescription className="mt-1.5 text-sm">
|
|
{options.description}
|
|
</AlertDialogDescription>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel onClick={handleCancel}>
|
|
{options.cancelText || "취소"}
|
|
</AlertDialogCancel>
|
|
<AlertDialogAction onClick={handleConfirm} className={cn(config.buttonClass)}>
|
|
{options.confirmText || "확인"}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
);
|
|
|
|
return { confirm, ConfirmDialogComponent };
|
|
}
|