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
옵션
| Prop | Type | Default |
|---|---|---|
| title | ReactNode | — |
| 제목 | ||
| description | ReactNode | — |
| 설명 | ||
| confirmLabel | ReactNode | '확인' |
| 확인 버튼 텍스트 | ||
| cancelLabel | ReactNode | '취소' |
| 취소 버튼 텍스트 | ||
| tone | 'default'|'danger' | 'default' |
| 시각적 톤 | ||
| onConfirm | () => void|Promise<void> | — |
| 확인 버튼 클릭 시 실행할 핸들러. Promise를 반환하면 비동기 플로우가 자동으로 동작합니다. | ||
| loading | MessageStep | — |
| onConfirm 실행 중 표시할 loading step 내용 | ||
| success | MessageStep | — |
| onConfirm 성공 후 표시할 success step 내용. 미지정 시 성공 직후 바로 닫힙니다. | ||
| error | MessageStep|(err: unknown) => MessageStep | — |
| onConfirm 실패 시 표시할 error step 내용. 함수 형태로 에러별 메시지를 구분할 수 있습니다. | ||
| retryOnError | boolean | false |
| true로 설정하면 에러 시 error step 대신 confirm step으로 돌아갑니다. | ||
| render | (ctx: ConfirmRenderContext) => ReactNode | — |
| 이 호출에서만 UI를 교체하는 커스텀 렌더 함수. 전역 등록보다 우선순위가 높습니다. | ||
DialogOptions (overlay, trapFocus, scrollLock 등)도 모두 전달할 수 있습니다.
Note
closeOnOverlayClick은 기본값이 false입니다. 실수로 닫히는 것을 방지합니다.
비동기 플로우
onConfirm에 Promise를 반환하는 함수를 전달하면 단계별 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의 기본값입니다.
| Step | title | confirmLabel | cancelLabel |
|---|---|---|---|
| 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 }} />MyConfirm은 ConfirmRenderContext를 props로 받는 컴포넌트입니다.
Tip
렌더 우선순위 (높음 → 낮음): render prop → DialogRuntime components.confirm → 내장 기본 UI
ConfirmRenderContext
render 옵션 또는 DialogRuntime components.confirm에 등록한 컴포넌트가 받는 컨텍스트입니다.
현재 step에 맞는 표시값이 미리 계산되어 있어 그대로 사용할 수 있습니다.
| Prop | Type | Default |
|---|---|---|
| options | DialogConfirmOptions | — |
| 원본 옵션 | ||
| step | 'confirm'|'loading'|'success'|'error' | — |
| 현재 단계 | ||
| error | unknown | — |
| 에러 객체 (error step일 때) | ||
| title | ReactNode | — |
| 현재 step의 제목 (기본값 계산 완료) | ||
| description | ReactNode | — |
| 현재 step의 설명 (기본값 계산 완료) | ||
| confirmLabel | ReactNode | — |
| 현재 step의 확인 버튼 텍스트 (기본값 계산 완료) | ||
| cancelLabel | ReactNode | — |
| 현재 step의 취소 버튼 텍스트 (기본값 계산 완료) | ||
| showCancel | boolean | — |
| 취소 버튼 표시 여부 | ||
| 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 무시)
- 닫힐 때 이전 포커스 요소로 복귀