컴포넌트
Alert Dialog
작업을 실행하기 전에 확인을 받습니다.
모션
Dialog와 같은 확대 모션을 사용합니다. 등장은 300ms, 퇴장은 200ms입니다. 움직임 감소 설정에서는 전환을 생략합니다. 공통 모션 규칙을 따릅니다.
설치
pnpm dlx ply-ui add alert-dialog button layout공통 설치 설정을 먼저 적용합니다.
사용법
확인과 취소 버튼을 따로 두고 결과를 설명하는 제목과 본문을 제공합니다.
예제의 삭제는 로컬 상태 변경입니다. 실제 삭제 API는 확인 동작에 연결합니다.
구성
| 구성 요소 | 역할 |
|---|---|
Root | 확인창의 열림 상태를 관리합니다. |
Trigger | 확인창을 여는 버튼입니다. |
Portal / Backdrop | 확인창을 별도 영역에 렌더하고 배경을 덮습니다. |
Popup | 확인할 작업과 선택 버튼을 담습니다. |
Title | 확인이 필요한 작업을 설명하는 제목입니다. |
Description | 작업 결과나 영향을 설명합니다. |
Close | 취소 또는 확인 후 창을 닫습니다. 확인 동작 자체는 콜백으로 연결합니다. |
첫 포커스와 복귀 포커스
Alert Dialog는 열리면 첫 번째 버튼으로 포커스가 갑니다. "취소"를 앞에 두면 Enter를 잘못 눌러도 삭제되지 않습니다.
삭제처럼 대상이 사라지는 작업은 확인창을 연 버튼도 함께 사라집니다. 이때 그대로 두면 닫힌 뒤 포커스가 페이지 처음(body)으로 빠져 키보드·스크린 리더 사용자가 위치를 잃습니다. Popup의 finalFocus에 작업 뒤에도 남아 있는 요소(목록 위의 버튼, 다음 항목 등)의 ref를 넘깁니다.
구현 코드
components/ui/alert-dialog.tsx
"use client";
import type { ComponentProps } from "react";
import { AlertDialog as Primitive } from "@base-ui/react/alert-dialog";
import { withClassName } from "../../lib/cx";
import { createThemedPortal } from "../../lib/create-themed-portal";
import "./alert-dialog.css";
const ThemedPortal = createThemedPortal(Primitive.Portal);
// 동작과 접근성은 Base UI가 담당합니다. 이 파일은 스타일 연결만 담당합니다.
function AlertDialogBackdrop({
className,
...props
}: ComponentProps<typeof Primitive.Backdrop>) {
return (
<Primitive.Backdrop
{...props}
className={withClassName("rbx-backdrop", className)}
/>
);
}
function AlertDialogPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup
{...props}
className={withClassName("rbx-dialog", className)}
/>
);
}
function AlertDialogTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title
{...props}
className={withClassName("rbx-dialog-title", className)}
/>
);
}
function AlertDialogDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={withClassName("rbx-description", className)}
/>
);
}
function AlertDialogTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={withClassName("rbx-button", className)}
/>
);
}
function AlertDialogClose({
className,
...props
}: ComponentProps<typeof Primitive.Close>) {
return (
<Primitive.Close
{...props}
className={withClassName("rbx-button", className)}
/>
);
}
// Root/Portal 등 스타일 없는 파트와 제네릭 API는 원본을 그대로 보존합니다.
export const AlertDialog = {
...Primitive,
Portal: ThemedPortal,
Backdrop: AlertDialogBackdrop,
Popup: AlertDialogPopup,
Title: AlertDialogTitle,
Description: AlertDialogDescription,
Trigger: AlertDialogTrigger,
Close: AlertDialogClose,
};
components/ui/alert-dialog.css
@layer theme, base, components, utilities;
@layer components.ply-base, components.ply;
@import "./button.css";
@layer components.ply {
/* AlertDialog: 공통 구조는 theme.css, 컴포넌트 전용 조정은 아래에 작성합니다. */
}