[공통컴포넌트] alert 제작

This commit is contained in:
2026-02-13 16:12:08 +09:00
parent 7c194d7452
commit b73867c65d
6 changed files with 427 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
"use client";
import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { useGlobalAlertStore } from "@/features/layout/stores/use-global-alert-store";
import { cn } from "@/lib/utils";
export function GlobalAlertModal() {
const {
isOpen,
type,
title,
message,
confirmLabel,
cancelLabel,
onConfirm,
onCancel,
isSingleButton,
closeAlert,
} = useGlobalAlertStore();
const handleOpenChange = (open: boolean) => {
if (!open) {
closeAlert();
}
};
const handleConfirm = () => {
onConfirm?.();
closeAlert();
};
const handleCancel = () => {
onCancel?.();
closeAlert();
};
const Icon = {
success: CheckCircle2,
error: AlertCircle,
warning: AlertTriangle,
info: Info,
}[type];
const iconColor = {
success: "text-emerald-500",
error: "text-red-500",
warning: "text-amber-500",
info: "text-blue-500",
}[type];
const bgColor = {
success: "bg-emerald-50 dark:bg-emerald-950/20",
error: "bg-red-50 dark:bg-red-950/20",
warning: "bg-amber-50 dark:bg-amber-950/20",
info: "bg-blue-50 dark:bg-blue-950/20",
}[type];
return (
<AlertDialog open={isOpen} onOpenChange={handleOpenChange}>
<AlertDialogContent className="sm:max-w-[400px]">
<AlertDialogHeader>
<div className="flex flex-col items-center gap-4 text-center sm:flex-row sm:text-left">
<div
className={cn(
"flex h-12 w-12 shrink-0 items-center justify-center rounded-full",
bgColor,
iconColor,
)}
>
<Icon className="h-6 w-6" />
</div>
<div className="flex-1 space-y-2">
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription className="text-sm leading-relaxed">
{message}
</AlertDialogDescription>
</div>
</div>
</AlertDialogHeader>
<AlertDialogFooter className="mt-4 sm:justify-end">
{!isSingleButton && (
<AlertDialogCancel onClick={handleCancel} className="mt-2 sm:mt-0">
{cancelLabel || "취소"}
</AlertDialogCancel>
)}
<AlertDialogAction
onClick={handleConfirm}
className={cn(
type === "error" && "bg-red-600 hover:bg-red-700",
type === "warning" && "bg-amber-600 hover:bg-amber-700",
type === "success" && "bg-emerald-600 hover:bg-emerald-700",
)}
>
{confirmLabel || "확인"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}

View File

@@ -0,0 +1,84 @@
import { ReactNode } from "react";
import {
AlertType,
useGlobalAlertStore,
} from "@/features/layout/stores/use-global-alert-store";
interface AlertOptions {
title?: ReactNode;
confirmLabel?: string;
cancelLabel?: string;
onConfirm?: () => void;
onCancel?: () => void;
type?: AlertType;
}
export function useGlobalAlert() {
const openAlert = useGlobalAlertStore((state) => state.openAlert);
const closeAlert = useGlobalAlertStore((state) => state.closeAlert);
const show = (
message: ReactNode,
type: AlertType = "info",
options?: AlertOptions,
) => {
openAlert({
message,
type,
title: options?.title || getDefaultTitle(type),
confirmLabel: options?.confirmLabel || "확인",
cancelLabel: options?.cancelLabel,
onConfirm: options?.onConfirm,
onCancel: options?.onCancel,
isSingleButton: true,
});
};
const confirm = (
message: ReactNode,
type: AlertType = "warning",
options?: AlertOptions,
) => {
openAlert({
message,
type,
title: options?.title || "확인",
confirmLabel: options?.confirmLabel || "확인",
cancelLabel: options?.cancelLabel || "취소",
onConfirm: options?.onConfirm,
onCancel: options?.onCancel,
isSingleButton: false,
});
};
return {
alert: {
success: (message: ReactNode, options?: AlertOptions) =>
show(message, "success", options),
warning: (message: ReactNode, options?: AlertOptions) =>
show(message, "warning", options),
error: (message: ReactNode, options?: AlertOptions) =>
show(message, "error", options),
info: (message: ReactNode, options?: AlertOptions) =>
show(message, "info", options),
confirm: (message: ReactNode, options?: AlertOptions) =>
confirm(message, options?.type || "warning", options),
},
close: closeAlert,
};
}
function getDefaultTitle(type: AlertType) {
switch (type) {
case "success":
return "성공";
case "error":
return "오류";
case "warning":
return "주의";
case "info":
return "알림";
default:
return "알림";
}
}

View File

@@ -0,0 +1,43 @@
import { ReactNode } from "react";
import { create } from "zustand";
export type AlertType = "success" | "warning" | "error" | "info";
export interface AlertState {
isOpen: boolean;
type: AlertType;
title: ReactNode;
message: ReactNode;
confirmLabel?: string;
cancelLabel?: string;
onConfirm?: () => void;
onCancel?: () => void;
// 단일 버튼 모드 여부 (Confirm 모달이 아닌 단순 Alert)
isSingleButton?: boolean;
}
interface AlertActions {
openAlert: (params: Omit<AlertState, "isOpen">) => void;
closeAlert: () => void;
}
const initialState: AlertState = {
isOpen: false,
type: "info",
title: "",
message: "",
confirmLabel: "확인",
cancelLabel: "취소",
isSingleButton: true,
};
export const useGlobalAlertStore = create<AlertState & AlertActions>((set) => ({
...initialState,
openAlert: (params) =>
set({
...initialState, // 초기화 후 설정
...params,
isOpen: true,
}),
closeAlert: () => set({ isOpen: false }),
}));