Alert Dialog
사용자의 확인이 반드시 필요한 경우 강력한 표현 및 경고 수단으로 활용하는 컴포넌트입니다.
<overlay>import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>주의</AlertDialogTitle>
<AlertDialogDescription>이 작업은 되돌릴 수 없습니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Installation
npx @seed-design/cli add ui:alert-dialogpnpm dlx @seed-design/cli add ui:alert-dialogyarn dlx @seed-design/cli add ui:alert-dialogbun x @seed-design/cli add ui:alert-dialog의존성 설치
npm install @seed-design/lynx-reactyarn add @seed-design/lynx-reactpnpm add @seed-design/lynx-reactbun add @seed-design/lynx-react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:alert-dialog
* @requires @seed-design/lynx-react@^1.0.0
* @requires @seed-design/lynx-css@^1.0.0
**/
import * as React from "@lynx-js/react";
import {
ActionButton,
AlertDialog as SeedAlertDialog,
type ActionButtonProps,
} from "@seed-design/lynx-react";
export interface AlertDialogRootProps extends SeedAlertDialog.RootProps {}
/**
* @see https://seed-design.io/lynx/components/alert-dialog
*/
export const AlertDialogRoot = SeedAlertDialog.Root;
export interface AlertDialogTriggerProps extends SeedAlertDialog.TriggerProps {}
export const AlertDialogTrigger = SeedAlertDialog.Trigger;
export interface AlertDialogHeaderProps extends SeedAlertDialog.HeaderProps {}
export const AlertDialogHeader = SeedAlertDialog.Header;
export interface AlertDialogTitleProps extends SeedAlertDialog.TitleProps {}
export const AlertDialogTitle = SeedAlertDialog.Title;
export interface AlertDialogDescriptionProps extends SeedAlertDialog.DescriptionProps {}
export const AlertDialogDescription = SeedAlertDialog.Description;
export interface AlertDialogFooterProps extends SeedAlertDialog.FooterProps {}
export const AlertDialogFooter = SeedAlertDialog.Footer;
export interface AlertDialogActionProps
extends Omit<SeedAlertDialog.ActionProps, "children">,
ActionButtonProps {}
/**
* `ActionButton`을 닫기 동작과 함께 렌더링합니다. `disabled`·`loading`이면 닫지 않습니다.
*/
export const AlertDialogAction = React.forwardRef<unknown, AlertDialogActionProps>(
({ children, transition, ...actionButtonProps }, ref) => {
const { disabled, loading } = actionButtonProps;
return (
<SeedAlertDialog.Action
transition={transition}
disabled={disabled === true || loading === true}
>
<ActionButton ref={ref} {...actionButtonProps}>
{children}
</ActionButton>
</SeedAlertDialog.Action>
);
},
);
AlertDialogAction.displayName = "AlertDialogAction";
export interface AlertDialogContentProps extends Omit<SeedAlertDialog.ContentProps, "children"> {
children?: React.ReactNode;
container?: SeedAlertDialog.PositionerProps["container"];
overlayLevel?: SeedAlertDialog.PositionerProps["overlayLevel"];
}
/**
* Positioner, Backdrop, Content를 조립합니다. Lynx에서는 container와 overlayLevel로
* overlay 위치를 지정하며, alert dialog의 backdrop을 탭해도 닫히지 않습니다.
*/
export const AlertDialogContent = (props: AlertDialogContentProps) => {
const { children, container, overlayLevel, ...contentProps } = props;
return (
<SeedAlertDialog.Positioner container={container} overlayLevel={overlayLevel}>
<SeedAlertDialog.Backdrop />
<SeedAlertDialog.Content {...contentProps}>{children}</SeedAlertDialog.Content>
</SeedAlertDialog.Positioner>
);
};
AlertDialogContent.displayName = "AlertDialogContent";
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
Usage
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export function App() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>주의</AlertDialogTitle>
<AlertDialogDescription>이 작업은 되돌릴 수 없습니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}AlertDialogTrigger는 자식을 native view로 감쌉니다. 문자열 children에서 접근성 이름을 만들며, accessibility-label을 직접 지정하면 그 값을 사용합니다. raw <text>처럼 이름을 만들 수 없는 자식에는 이름을 명시하세요.
AlertDialogAction은 탭하면 Alert Dialog를 닫지만, disabled 또는 loading 상태에서는 닫지 않습니다. 사용자의 확인이 필요한 흐름을 위해 Backdrop 탭으로는 닫히지 않습니다.
Props
AlertDialogRoot
Prop
Type
open, defaultOpen, onOpenChange로 상태를 제어할 수 있으며 skipAnimation으로 열림·닫힘 transition을 건너뛸 수 있습니다.
AlertDialogTrigger
고유 prop이 없습니다.
AlertDialogContent
Prop
Type
children?React.ReactNodecontainer?string | undefinedoverlayLevel?1 | 2 | 3 | 4 | undefinedcontainer와 overlayLevel로 native overlay의 위치와 표시 순서를 지정합니다.
AlertDialogHeader
고유 prop이 없습니다. className, style, children과 padding·gap 같은 스타일 prop을 받습니다.
AlertDialogTitle
Prop
Type
AlertDialogDescription
고유 prop이 없습니다. className, style, children과 padding·gap 같은 스타일 prop을 받습니다.
AlertDialogFooter
고유 prop이 없습니다. className, style, children과 padding·gap 같은 스타일 prop을 받습니다.
AlertDialogAction
Prop
Type
flexGrow?boolean | Property.FlexGrow | undefinedbleed?BleedValue | undefinedbleedX?BleedValue | undefinedbleedY?BleedValue | undefinedExamples
Responsive Wrapping
React의 ResponsivePair 대신 Footer 안에 명시적인 세로 action layout을 구성합니다. Lynx에서는 native view의 flex-direction 등을 사용해 긴 버튼 레이블이 세로로 배치되도록 조정합니다.
import "./styles";
import IconCheckmarkFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFill";
import { ActionButton, PrefixIcon } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Wrapping</AlertDialogTitle>
<AlertDialogDescription>
Lynx에서는 긴 버튼 레이블을 위해 action을 세로로 배치할 수 있습니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view
style={{
display: "flex",
flexDirection: "column",
alignItems: "stretch",
width: "100%",
gap: "8px",
}}
>
<AlertDialogAction variant="neutralWeak" style={{ width: "100%" }}>
취소
</AlertDialogAction>
<AlertDialogAction variant="neutralSolid" style={{ width: "100%" }}>
<PrefixIcon icon={<IconCheckmarkFill />} />긴 레이블 예시
</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Long Content
내용이 최대 높이를 넘으면 AlertDialogContent 안에서 스크롤되고, 끝까지 스크롤하면 Footer의 버튼이 보입니다. Content는 위아래 safe area inset과 recipe의 위아래 margin을 뺀 높이를 넘지 않습니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>긴 내용</AlertDialogTitle>
<AlertDialogDescription>
{Array.from(
{ length: 30 },
(_, index) =>
`${index + 1}. 내용이 최대 높이를 넘으면 AlertDialog 안에서 스크롤됩니다.`,
).join(" ")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Single Action
확인 또는 닫기처럼 하나의 action만 제공하는 Alert Dialog입니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>제목</AlertDialogTitle>
<AlertDialogDescription>단일 선택지를 제공합니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Neutral Secondary Action
보조 action에 neutralWeak를 사용하고, 주요 action과 시각적 우선순위를 구분합니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>제목</AlertDialogTitle>
<AlertDialogDescription>중립적인 선택지를 제공합니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Nonpreferred
주요 흐름에서 권장하지 않는 action 조합을 표현하는 예제입니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>제목</AlertDialogTitle>
<AlertDialogDescription>중립적인 선택지를 제공합니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
alignSelf: "stretch",
}}
>
<AlertDialogAction size="medium" variant="neutralSolid" layout="withText">
라벨
</AlertDialogAction>
<view style={{ marginTop: "-10px", marginBottom: "-10px" }}>
<AlertDialogAction size="medium" variant="ghost" layout="withText">
라벨
</AlertDialogAction>
</view>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Critical Action
삭제처럼 되돌리기 어렵거나 위험한 action에는 criticalSolid를 사용합니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>제목</AlertDialogTitle>
<AlertDialogDescription>파괴적, 비가역적 작업을 경고합니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view
style={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-end",
gap: "8px",
}}
>
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="criticalSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Controlled
Trigger 외의 방식으로 Alert Dialog를 열고 닫으려면 open과 boolean을 받는 onOpenChange로 상태를 제어합니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
export default function Example() {
const [open, setOpen] = useState(false);
function handleOpen() {
"background only";
setOpen(true);
}
return (
<view
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
gap: "16px",
}}
>
<text className="alert-dialog-example-text">열림 상태: {open ? "true" : "false"}</text>
<ActionButton variant="neutralSolid" bindtap={handleOpen} disabled={open}>
열기
</ActionButton>
<AlertDialogRoot open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>주의</AlertDialogTitle>
<AlertDialogDescription>이 작업은 되돌릴 수 없습니다.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
</view>
);
}Prevent Close
Lynx의 AlertDialogAction에는 React 이벤트의 preventDefault() 방식이 없습니다. controlled Root에서 onOpenChange(false) 요청을 조건에 따라 무시하면 닫힘을 막을 수 있습니다.
import "./styles";
import { useState } from "@lynx-js/react";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
export default function Example() {
const [open, setOpen] = useState(false);
const [preventClose, setPreventClose] = useState(true);
function handleOpen() {
"background only";
setOpen(true);
}
function handleTogglePreventClose() {
"background only";
setPreventClose((previous) => !previous);
}
function handleOpenChange(nextOpen: boolean) {
"background only";
if (nextOpen || !preventClose) {
setOpen(nextOpen);
}
}
return (
<view
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
gap: "16px",
}}
>
<text className="alert-dialog-example-text">열림 상태: {open ? "true" : "false"}</text>
<ActionButton variant="neutralSolid" bindtap={handleOpen} disabled={open}>
열기
</ActionButton>
<AlertDialogRoot open={open} onOpenChange={handleOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>닫기 방지</AlertDialogTitle>
<AlertDialogDescription>
닫힘 방지 상태에서는 확인 버튼을 눌러도 Alert Dialog가 닫히지 않습니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<view
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "8px",
}}
>
<text className="alert-dialog-example-text">
닫힘 방지: {preventClose ? "켜짐" : "꺼짐"}
</text>
<ActionButton variant="neutralWeak" bindtap={handleTogglePreventClose}>
{preventClose ? "닫힘 허용" : "닫힘 방지"}
</ActionButton>
</view>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
</view>
);
}Portalled
AlertDialogContent의 container를 지정하면 native overlay 레이어에 렌더링됩니다. overlayLevel로 overlay 레이어의 표시 순서를 지정할 수 있습니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent container="window" overlayLevel={2}>
<AlertDialogHeader>
<AlertDialogTitle>window overlay</AlertDialogTitle>
<AlertDialogDescription>
Lynx에서는 container와 overlayLevel로 네이티브 overlay 위치를 지정합니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}Skip Animation
AlertDialogRoot의 skipAnimation prop을 사용하여 Alert Dialog의 enter/exit transition을 건너뛸 수 있습니다.
import "./styles";
import { ActionButton } from "@seed-design/lynx-react";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
export default function Example() {
return (
<AlertDialogRoot skipAnimation>
<AlertDialogTrigger>
<ActionButton variant="neutralSolid">애니메이션 없이 열기</ActionButton>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>즉시 표시</AlertDialogTitle>
<AlertDialogDescription>
skipAnimation이 설정되어 열고 닫을 때 전환 애니메이션을 사용하지 않습니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<view className="alert-dialog-example-actions">
<AlertDialogAction variant="neutralWeak">취소</AlertDialogAction>
<AlertDialogAction variant="neutralSolid">확인</AlertDialogAction>
</view>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogRoot>
);
}웹 버전과의 차이
onOpenChange는 변경된 boolean 값만 전달합니다. 닫힌 원인이 필요하면 Trigger와 action의 이벤트 핸들러에서 별도 상태로 기록하세요.- Backdrop 탭과 ESC 키로 닫히지 않습니다. 필요한 닫기 흐름은 버튼과 controlled 상태로 구성하세요.
- Trigger는 자식을 native
view로 감싸며asChild를 제공하지 않습니다. - DOM ref forwarding과 웹의 자동 focus 관리 API를 제공하지 않습니다.
container와overlayLevel로 native overlay의 위치와 표시 순서를 지정합니다.ResponsivePair는 제공하지 않습니다. Footer 안에서 action의 방향과 간격을 직접 지정하세요.
Last updated on