[공통컴포넌트] alert 제작
This commit is contained in:
110
features/layout/components/GlobalAlertModal.tsx
Normal file
110
features/layout/components/GlobalAlertModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
84
features/layout/hooks/use-global-alert.ts
Normal file
84
features/layout/hooks/use-global-alert.ts
Normal 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 "알림";
|
||||
}
|
||||
}
|
||||
43
features/layout/stores/use-global-alert-store.ts
Normal file
43
features/layout/stores/use-global-alert-store.ts
Normal 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 }),
|
||||
}));
|
||||
Reference in New Issue
Block a user