@chaesunbak/registry
Hooks

useDialog

Alert와 Confirm 형태의 다이얼로그를 쉽게 표시할 수 있습니다.

examples/use-dialog.tsx
"use client";import { useDialog } from "@/hooks/use-dialog";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";export function UseDialogExample() {  const { confirm, alert } = useDialog();  const delay = async (ms: number) => {    await new Promise((resolve) => setTimeout(resolve, ms));  };  const handleAsyncConfirm = async () => {    await confirm(      "작업을 진행할까요?",      "이 작업은 되돌릴 수 없습니다.",      <Input placeholder="계정 삭제 사유를 입력해주세요." />,      {        isDestructive: true,        onConfirm: async () => {          await delay(1000); // 1초 대기 (로딩 상태 자동 반영)        },      },    );  };  return (    <div className="flex flex-col gap-4 sm:flex-row">      <Button onClick={handleAsyncConfirm} variant="default">        계정 삭제하기      </Button>    </div>  );}

설치

pnpm

pnpm dlx shadcn@latest add @chaesunbak/use-dialog

npm

npx shadcn@latest add @chaesunbak/use-dialog

다음 코드를 프로젝트에 복사/붙혀넣기 하세요.

hooks/useDialog.ts
import { create } from "zustand";import { ReactNode } from "react";interface DialogOptions {  cancelText?: string;  confirmText?: string;  isDestructive?: boolean;  closeOnDimmerClick?: boolean;  onConfirm?: () => void | Promise<void>;  onCancel?: () => void | Promise<void>;}interface DialogState {  isOpen: boolean;  isLoading: boolean;  title: string;  description: string;  content: ReactNode | null;  cancelText: string;  confirmText: string;  isDestructive: boolean;  closeOnDimmerClick: boolean;  hasCancel: boolean;  onConfirm: () => void | Promise<void>;  onCancel: () => void | Promise<void>;}interface DialogActions {  confirm: (    title: string,    description?: string,    content?: ReactNode | null,    options?: DialogOptions,  ) => Promise<boolean>;  alert: (    title: string,    description?: string,    content?: ReactNode | null,    options?: Omit<DialogOptions, "cancelText" | "onCancel">,  ) => Promise<boolean>;  setLoading: (isLoading: boolean) => void;  close: () => void;}type DialogStore = DialogState & DialogActions;const INITIAL_STATE: DialogState = {  isOpen: false,  isLoading: false,  title: "",  description: "",  content: null,  cancelText: "취소",  confirmText: "확인",  isDestructive: false,  closeOnDimmerClick: true,  hasCancel: true,  onConfirm: () => {},  onCancel: () => {},};export const useDialogStore = create<DialogStore>((set, get) => ({  ...INITIAL_STATE,  setLoading: (isLoading) => set({ isLoading }),  close: () => {    const { onCancel, isOpen } = get();    if (isOpen) {      onCancel();    }    set({ isOpen: false, isLoading: false });  },  confirm: (title, description = "", content = null, options) => {    return new Promise((resolve) => {      const cancelText = options?.cancelText ?? INITIAL_STATE.cancelText;      const confirmText = options?.confirmText ?? INITIAL_STATE.confirmText;      const isDestructive =        options?.isDestructive ?? INITIAL_STATE.isDestructive;      const closeOnDimmerClick =        options?.closeOnDimmerClick ?? INITIAL_STATE.closeOnDimmerClick;      const onConfirmAction = options?.onConfirm;      const onCancelAction = options?.onCancel;      set({        isOpen: true,        isLoading: false,        title,        description,        content,        cancelText,        confirmText,        isDestructive,        closeOnDimmerClick,        hasCancel: true,        onConfirm: async () => {          if (onConfirmAction) {            set({ isLoading: true });            try {              await onConfirmAction();            } finally {              set({ isOpen: false, isLoading: false });              resolve(true);            }          } else {            set({ isOpen: false });            resolve(true);          }        },        onCancel: async () => {          if (onCancelAction) {            set({ isLoading: true });            try {              await onCancelAction();            } finally {              set({ isOpen: false, isLoading: false });              resolve(false);            }          } else {            set({ isOpen: false });            resolve(false);          }        },      });    });  },  alert: (title, description = "", content = null, options) => {    return new Promise((resolve) => {      const confirmText = options?.confirmText ?? INITIAL_STATE.confirmText;      const isDestructive =        options?.isDestructive ?? INITIAL_STATE.isDestructive;      const closeOnDimmerClick =        options?.closeOnDimmerClick ?? INITIAL_STATE.closeOnDimmerClick;      const onConfirmAction = options?.onConfirm;      set({        isOpen: true,        isLoading: false,        title,        description,        content,        cancelText: INITIAL_STATE.cancelText,        confirmText,        isDestructive,        closeOnDimmerClick,        hasCancel: false,        onConfirm: async () => {          if (onConfirmAction) {            set({ isLoading: true });            try {              await onConfirmAction();            } finally {              set({ isOpen: false, isLoading: false });              resolve(true);            }          } else {            set({ isOpen: false });            resolve(true);          }        },        onCancel: () => {          set({ isOpen: false });          resolve(false);        },      });    });  },}));export const useDialog = () => {  const confirm = useDialogStore((state) => state.confirm);  const alert = useDialogStore((state) => state.alert);  return { confirm, alert };};

다음 코드를 프로젝트에 복사/붙혀넣기 하세요.

components/global-dialog.tsx
"use client";import { Loader2 } from "lucide-react";import { Button } from "@/components/ui/button";import {  Dialog,  DialogContent,  DialogDescription,  DialogFooter,  DialogHeader,  DialogTitle,} from "@/components/ui/dialog";import { useDialogStore } from "@/hooks/use-dialog";type PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;export const GlobalDialog = () => {  const {    isOpen,    isLoading,    title,    description,    content,    cancelText,    confirmText,    isDestructive,    closeOnDimmerClick,    hasCancel,    onConfirm,    onCancel,  } = useDialogStore();  function handleOnOpenChange(open: boolean) {    if (!open && !isLoading) {      onCancel();    }  }  function handlePointerDownOutside(e: PointerDownOutsideEvent) {    if (isLoading || !closeOnDimmerClick) {      e.preventDefault();    }  }  return (    <Dialog open={isOpen} onOpenChange={handleOnOpenChange}>      <DialogContent        showCloseButton={false}        onPointerDownOutside={handlePointerDownOutside}      >        <DialogHeader>          <DialogTitle>{title}</DialogTitle>          <DialogDescription>{description}</DialogDescription>          {content}        </DialogHeader>        <DialogFooter>          {hasCancel && (            <Button variant="outline" onClick={onCancel} disabled={isLoading}>              {cancelText}            </Button>          )}          <Button            variant={isDestructive ? "destructive" : "default"}            onClick={onConfirm}            disabled={isLoading}          >            {isLoading && <Loader2 className="mr-2 size-4 animate-spin" />}            {confirmText}          </Button>        </DialogFooter>      </DialogContent>    </Dialog>  );};

루트 레이아웃에 GlobalDialog를 추가해주세요.

관련 의존성을 설치해주세요

사용법

루트 레이아웃에 GlobalDialog를 추가해주세요

import { GlobalDialog } from "@/components/global-dialog";
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <GlobalDialog />
        {children}
      </body>
    </html>
  );
}

Alert 다이얼로그 표시하기

alert 메서드를 사용하여 기본적인 다이얼로그를 표시할 수 있어요. closeOnDimmerClick 속성을 false로 설정하면 배경 클릭으로 다이얼로그가 닫히는 것을 방지할 수 있어요.

"use client";import { useDialog } from "@/hooks/use-dialog";import { Button } from "@/components/ui/button";export function UseDialogAlertExample() {  const { alert } = useDialog();  return (    <Button      onClick={() => {        alert("알려드릴게요", "작업이 완료됐어요.", undefined, {          confirmText: "확인하기",          closeOnDimmerClick: false,        });      }}    >      기본 Alert 다이얼로그 열기    </Button>  );}

Confirm 다이얼로그 표시하기

confirm 메서드는 사용자의 결정을 요구하는 상황에서 유용해요. isDestructive 속성을 true로 설정하면 위험한 액션을 나타낼 수 있어요.

"use client";import { useDialog } from "@/hooks/use-dialog";import { Button } from "@/components/ui/button";export function UseDialogConfirmExample() {  const { confirm } = useDialog();  return (    <Button      onClick={() => {        confirm("삭제할까요?", "이 작업은 되돌릴 수 없어요.", undefined, {          confirmText: "삭제하기",          cancelText: "취소",          isDestructive: true,        });      }}    >      기본 Confirm 다이얼로그 열기    </Button>  );}

비동기 작업 처리하기

confirm 메서드의 onConfirm 속성에 비동기 함수(Promise를 반환하는 함수)를 전달하면, 버튼을 클릭했을 때 작업이 완료될 때까지 자동으로 로딩 상태가 처리돼요.

"use client";import { useDialog } from "@/hooks/use-dialog";import { Button } from "@/components/ui/button";export function UseDialogAsyncExample() {  const { confirm } = useDialog();  const delay = async (milliseconds: number) => {    await new Promise((res) => setTimeout(res, milliseconds));  };  return (    <Button      onClick={() => {        confirm(          "상담을 종료할까요?",          "상담을 종료하면 대화를 이어갈 수 없어요.",          undefined,          {            confirmText: "종료하기",            cancelText: "취소",            onConfirm: () => delay(2000),          },        );      }}    >      비동기 Confirm 다이얼로그 열기    </Button>  );}

인터페이스

useDialog 반환 객체

메서드타입설명
confirm(title: string, description?: string, content?: ReactNode, options?: DialogOptions) => Promise<boolean>확인 및 취소 버튼이 있는 Confirm 다이얼로그를 띄웁니다. 사용자의 선택에 따라 true 또는 false를 반환합니다.
alert(title: string, description?: string, content?: ReactNode, options?: Omit<DialogOptions, "cancelText" | "onCancel">) => Promise<boolean>확인 버튼만 있는 Alert 다이얼로그를 띄웁니다. 확인 시 true를 반환합니다.

DialogOptions

속성타입설명
cancelTextstring취소 버튼에 표시될 텍스트입니다.
confirmTextstring확인 버튼에 표시될 텍스트입니다.
isDestructiveboolean확인 버튼을 위험한 액션(destructive) 스타일로 표시할지 여부입니다.
closeOnDimmerClickboolean배경(Dimmer) 클릭 시 다이얼로그를 닫을지 여부입니다. 기본값은 true입니다.
onConfirm() => void | Promise<void>확인 버튼 클릭 시 실행될 (비동기) 콜백 함수입니다. 프로미스를 반환하면 대기하는 동안 버튼이 로딩 상태로 변경됩니다.
onCancel() => void | Promise<void>취소 버튼 클릭 시 실행될 (비동기) 콜백 함수입니다.

On this page