MENU
Wwoon

Dialog

Confirm

확인/취소 선택과 비동기 처리 플로우를 지원하는 명령형 API입니다.

import { confirm } from '@woon-ui/dialog'

confirm()은 사용자의 확인/취소 선택을 받는 함수입니다. Promise<DialogConfirmResult>를 반환하며, onConfirm에 비동기 함수를 전달하면 confirm → loading → success/error 플로우가 자동으로 동작합니다.

앱 루트에 DialogRuntime이 있으면 어디서든 호출할 수 있습니다.

기본 사용법

import { confirm } from '@woon-ui/dialog'

const result = await confirm({
  title: '삭제하시겠습니까?',
  description: '이 작업은 되돌릴 수 없습니다.',
  tone: 'danger',
})

if (result.status === 'confirmed') {
  await deleteItem()
} else if (result.status === 'cancelled') {
  // 취소 버튼 클릭
}

DialogConfirmResult

status설명
'confirmed'확인 버튼 클릭
'cancelled'취소 버튼 클릭
'dismissed'ESC로 닫힘

Preview

옵션

PropTypeDefault
titleReactNode
제목
descriptionReactNode
설명
confirmLabelReactNode'확인'
확인 버튼 텍스트
cancelLabelReactNode'취소'
취소 버튼 텍스트
tone'default'|'danger''default'
시각적 톤
onConfirm() => void|Promise<void>
확인 버튼 클릭 시 실행할 핸들러. Promise를 반환하면 비동기 플로우가 자동으로 동작합니다.
loadingMessageStep
onConfirm 실행 중 표시할 loading step 내용
successMessageStep
onConfirm 성공 후 표시할 success step 내용. 미지정 시 성공 직후 바로 닫힙니다.
errorMessageStep|(err: unknown) => MessageStep
onConfirm 실패 시 표시할 error step 내용. 함수 형태로 에러별 메시지를 구분할 수 있습니다.
retryOnErrorbooleanfalse
true로 설정하면 에러 시 error step 대신 confirm step으로 돌아갑니다.
render(ctx: ConfirmRenderContext) => ReactNode
이 호출에서만 UI를 교체하는 커스텀 렌더 함수. 전역 등록보다 우선순위가 높습니다.

DialogOptions (overlay, trapFocus, scrollLock 등)도 모두 전달할 수 있습니다.

Note

closeOnOverlayClick은 기본값이 false입니다. 실수로 닫히는 것을 방지합니다.

비동기 플로우

onConfirmPromise를 반환하는 함수를 전달하면 단계별 UI가 자동으로 전환됩니다.

확인 버튼 클릭

loading step  ← onConfirm() 실행 중

success step  ← onConfirm() 성공 (success 설정 시)

다이얼로그 닫힘, result.status === 'confirmed'
await confirm({
  title: '배포하시겠습니까?',
  onConfirm: async () => {
    await deployToProduction()
  },
  loading: { title: '배포 중입니다', description: '잠시만 기다려주세요.' },
  success: { title: '배포 완료', description: '정상적으로 배포되었습니다.' },
})

success를 설정하지 않으면 onConfirm 성공 직후 바로 닫힙니다.

Preview

기본 라벨

onConfirm이 있을 때 각 step의 기본값입니다.

SteptitleconfirmLabelcancelLabel
confirm(사용자 지정)확인취소
loading처리 중입니다처리 중
success완료되었습니다확인
error처리하지 못했습니다다시 시도닫기

MessageStep 타입

type MessageStep = {
  title?: ReactNode
  description?: ReactNode
  confirmLabel?: ReactNode
  cancelLabel?: ReactNode
}

에러 처리

onConfirm이 throw하면 error step으로 전환됩니다.

onConfirm() throw

error step  ← "다시 시도" 또는 "닫기" 선택
await confirm({
  title: '전송하시겠습니까?',
  onConfirm: async () => {
    const res = await fetch('/api/send')
    if (!res.ok) throw new Error('전송 실패')
  },
  error: { title: '전송 실패', description: '잠시 후 다시 시도해주세요.' },
})

에러 내용에 따라 다른 메시지를 표시하려면 함수 형태를 사용합니다.

error: (err) => ({
  title: '오류 발생',
  description: err instanceof Error ? err.message : '알 수 없는 오류',
})

retryOnError

retryOnError: true를 설정하면 에러 시 error step 대신 confirm step으로 돌아갑니다. 사용자가 직접 다시 시도할 수 있습니다.

await confirm({
  title: '전송하시겠습니까?',
  onConfirm: sendData,
  retryOnError: true,
})

Preview

커스텀 렌더링

render prop

await confirm({
  title: '삭제',
  render: (ctx) => (
    <div>
      <p>{ctx.title}</p>
      <button onClick={ctx.onConfirm} disabled={ctx.step === 'loading'}>
        {ctx.confirmLabel}
      </button>
      {ctx.showCancel && (
        <button onClick={ctx.onCancel}>{ctx.cancelLabel}</button>
      )}
    </div>
  ),
})

DialogRuntime 전역 등록

앱 전체 기본 Confirm 컴포넌트를 등록합니다. 자세한 내용은 Dialog를 참고하세요.

import { DialogRuntime } from '@woon-ui/dialog'
import { MyConfirm } from './ui/Confirm'

<DialogRuntime components={{ confirm: MyConfirm }} />

MyConfirmConfirmRenderContext를 props로 받는 컴포넌트입니다.

Tip

렌더 우선순위 (높음 → 낮음): render prop → DialogRuntime components.confirm → 내장 기본 UI

ConfirmRenderContext

render 옵션 또는 DialogRuntime components.confirm에 등록한 컴포넌트가 받는 컨텍스트입니다. 현재 step에 맞는 표시값이 미리 계산되어 있어 그대로 사용할 수 있습니다.

PropTypeDefault
optionsDialogConfirmOptions
원본 옵션
step'confirm'|'loading'|'success'|'error'
현재 단계
errorunknown
에러 객체 (error step일 때)
titleReactNode
현재 step의 제목 (기본값 계산 완료)
descriptionReactNode
현재 step의 설명 (기본값 계산 완료)
confirmLabelReactNode
현재 step의 확인 버튼 텍스트 (기본값 계산 완료)
cancelLabelReactNode
현재 step의 취소 버튼 텍스트 (기본값 계산 완료)
showCancelboolean
취소 버튼 표시 여부
onConfirm() => void
확인 핸들러
onCancel() => void
취소 핸들러

스타일

@woon-ui/dialog/css/confirm은 confirm 전용 data 속성과 base motion을 함께 포함합니다. 버튼 색상 등 특정 값만 바꾸려면 선택자로 override하세요.

[data-woon-confirm-confirm] {
  background: #6d28d9;
}

완전히 교체하려면 @woon-ui/dialog/css/confirm import를 제거하고 아래 CSS를 복사해서 수정하세요.

/* ── Confirm Base (위치·애니메이션) ──────────────────────────────────────────── */
[data-woon-scroll-lock] {
  overflow: hidden;
}

[data-woon-confirm-overlay],
[data-woon-confirm-content],
[data-woon-confirm-content] *,
[data-woon-confirm-content] *::before,
[data-woon-confirm-content] *::after { box-sizing: border-box; }

[data-woon-confirm-overlay] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 160ms ease-out;
}

[data-woon-confirm-overlay][data-entered] {
  opacity: 1;
}

[data-woon-confirm-overlay][data-state="closed"] {
  animation: woon-confirm-overlay-out 120ms ease-in forwards;
  transition: none;
}

[data-woon-confirm-content] {
  position: fixed;
  top: 50%; left: 50%;
  width: min(100%, 28rem);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 1rem; line-height: 1.5; color: inherit;
  opacity: 0;
  transform: translate(-50%, calc(-50% + 8px)) scale(0.98);
  transition:
    opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

[data-woon-confirm-content][data-entered] {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

[data-woon-confirm-content][data-state="closed"] {
  animation: woon-confirm-content-out 140ms ease-in forwards;
  transition: none;
}

@keyframes woon-confirm-overlay-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes woon-confirm-content-out {
  from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  to   { opacity: 0; transform: translate(-50%, calc(-50% + 6px)) scale(0.98); }
}

/* ── Content 시각 스타일 ── */
[data-woon-confirm-content]     { padding: 1.25rem 1.5rem; background: #fff; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.12); }
[data-woon-confirm-title]       { display: block; margin: 0 0 0.35rem; font-size: 1rem; font-weight: 600; }
[data-woon-confirm-description] { display: block; margin: 0 0 0.7rem; }

/* ── Confirm 버튼 ── */
[data-woon-confirm-actions] {
  display: flex; justify-content: flex-end; align-items: center;
  gap: 0.375rem; margin-top: 1.25rem;
}

[data-woon-confirm-cancel],
[data-woon-confirm-confirm] {
  -webkit-appearance: none; appearance: none; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0.4375rem 0.875rem; border: none; border-radius: 6px;
  font-family: inherit; font-size: 0.875rem; font-weight: 500; line-height: 1.25;
  cursor: pointer; transition: opacity 120ms, background 120ms; white-space: nowrap;
}

[data-woon-confirm-cancel]                      { background: transparent; color: #374151; }
[data-woon-confirm-cancel]:hover                { background: #f1f5f9; }
[data-woon-confirm-confirm]                     { background: #0f172a; color: #fff; }
[data-woon-confirm-confirm]:hover               { opacity: 0.85; }
[data-woon-confirm-confirm][data-tone='danger'] { background: #dc2626; }
[data-woon-confirm-confirm]:disabled            { opacity: 0.5; cursor: not-allowed; }

접근성

  • role="dialog", aria-modal="true" 자동 적용
  • loading step 중 확인 버튼 비활성화 (이중 제출 방지)
  • ESC로 닫기 가능 (loading step 중에는 ESC 무시)
  • 닫힐 때 이전 포커스 요소로 복귀