@olundot/ui · Surfaces
ConfirmDialog
되돌리기 어렵거나 결과가 큰 action을 실행하기 전, 사용자의 명시적 확인을 받는 모달 조합입니다.
- 상태 제어
- open과 onOpenChange를 필수로 받아 호출부가 열림 상태를 소유합니다.
- tone
- default는 일반 승인, destructive는 삭제·취소처럼 위험한 action에 사용합니다.
- onConfirm
- 동기와 비동기 함수를 모두 지원하며 처리 중에는 중복 실행과 닫기를 막습니다.
- 레이블
- confirmLabel은 action 결과를 구체적으로 말해야 합니다. '확인'만으로 끝내지 않습니다.
- 본문 슬롯
- children은 설명과 footer 사이에 들어갑니다. 인원 수 입력처럼 확인에 필요한 입력 한 개만 두고, confirmDisabled로 조건이 맞을 때까지 확인 버튼을 막습니다.
기본 확인
기본 tone과 비동기 확인코드 보기tsx
import { useState } from "react";
import { Button, ConfirmDialog } from "@olundot/ui";
export function ConfirmDialogDefault() {
const [open, setOpen] = useState(false);
const [confirmed, setConfirmed] = useState(false);
return (
<div style={{ display: "grid", gap: "12px" }}>
<Button variant="primary" onClick={() => setOpen(true)}>
승인 요청
</Button>
<ConfirmDialog
open={open}
onOpenChange={setOpen}
title="요청을 승인할까요?"
description="승인하면 담당자에게 검토 완료 알림이 전송됩니다."
confirmLabel="승인"
cancelLabel="취소"
onConfirm={async () => {
await new Promise((resolve) => setTimeout(resolve, 800));
setConfirmed(true);
}}
/>
{confirmed ? <p>승인 처리되었습니다.</p> : null}
</div>
);
}파괴적 확인
destructive tone과 구체적인 action label코드 보기tsx
import { useState } from "react";
import { Button, ConfirmDialog } from "@olundot/ui";
export function ConfirmDialogDestructive() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="destructive" onClick={() => setOpen(true)}>
제출 취소
</Button>
<ConfirmDialog
open={open}
onOpenChange={setOpen}
title="제출을 취소하시겠습니까?"
description="취소하면 현재 채점 대기열에서 빠지고, 학생에게 다시 제출 요청을 보내야 합니다."
tone="destructive"
confirmLabel="제출 취소"
cancelLabel="돌아가기"
onConfirm={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
</>
);
}외부 loading
loading prop으로 닫기 방지코드 보기tsx
import { useState } from "react";
import { Button, ConfirmDialog } from "@olundot/ui";
export function ConfirmDialogLoading() {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
async function handleConfirm() {
setLoading(true);
await new Promise((resolve) => setTimeout(resolve, 1200));
setLoading(false);
}
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
재계산 실행
</Button>
<ConfirmDialog
open={open}
onOpenChange={setOpen}
title="점수를 다시 계산할까요?"
description="계산 중에는 창을 닫을 수 없고, 완료 후 결과가 갱신됩니다."
confirmLabel="재계산"
loading={loading}
onConfirm={handleConfirm}
/>
</>
);
}입력 확인
children 본문 슬롯, confirmDisabled코드 보기tsx
import { useState } from "react";
import { Button, ConfirmDialog, Input } from "@olundot/ui";
export function ConfirmDialogTypedCount() {
const total = 464;
const [open, setOpen] = useState(false);
const [typed, setTyped] = useState("");
return (
<>
<Button variant="destructive" onClick={() => setOpen(true)}>
선택한 사용자 삭제
</Button>
<ConfirmDialog
open={open}
onOpenChange={(next) => {
setOpen(next);
if (!next) setTyped("");
}}
title={`${total.toLocaleString("ko-KR")}명을 삭제할까요?`}
description="삭제한 계정은 되돌릴 수 없습니다. 확인을 위해 인원 수를 입력해 주세요."
tone="destructive"
confirmLabel="삭제"
confirmDisabled={typed !== String(total)}
onConfirm={async () => {
await new Promise((resolve) => setTimeout(resolve, 800));
}}
>
<Input
aria-label="삭제할 인원 수"
inputMode="numeric"
placeholder={String(total)}
value={typed}
onChange={(event) => setTyped(event.target.value.trim())}
/>
</ConfirmDialog>
</>
);
}