Dialog
화면 중앙에 떠서 사용자의 주의를 모으고, 제목·본문·푸터를 담는 모달형 컴포넌트입니다.
<overlay>Installation
의존성 설치
아래 코드를 복사 후 붙여넣고 사용하세요
@seed-design/lynx-react의 Dialog는 @lynx-js/lynx-ui-dialog를 래핑합니다. Registry의 DialogContent는 Positioner, Backdrop, Header를 내부에서 조립하고, DialogAction은 ActionButton과 닫기 동작을 함께 제공합니다.
Usage
Registry의 DialogContent는 Positioner, Backdrop, Header를 내부에서 조립합니다. DialogTrigger는 Content 밖에 두어야 탭으로 Dialog를 열 수 있습니다. DialogContent의 showCloseButton은 기본값이 true이며, 헤더에 닫기 ActionButton을 렌더링합니다. 닫기 버튼이 필요하지 않으면 showCloseButton={false}로 설정하세요.
DialogBody는 native <scroll-view scroll-y>로 렌더링되며 Content의 최대 높이 안에서 세로로 스크롤합니다. DialogAction은 ActionButton props를 받아 닫기 동작과 함께 렌더링합니다.
Props
DialogRoot
Prop
Type
DialogTrigger
Prop
Type
DialogContent
Prop
Type
title?React.ReactNodedescription?React.ReactNodechildren?React.ReactNodecontainer?string | undefinedoverlayLevel?1 | 2 | 3 | 4 | undefinedDialogBody
Prop
Type
bg?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefinedbackground?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefinedcolor?(string & {}) | ScopedColorPalette | ScopedColorFg | undefinedborderColor?(string & {}) | ScopedColorPalette | ScopedColorStroke | undefinedborderWidth?0 | (string & {}) | 1 | undefinedborderTopWidth?0 | (string & {}) | 1 | undefinedborderRightWidth?0 | (string & {}) | 1 | undefinedborderBottomWidth?0 | (string & {}) | 1 | undefinedborderLeftWidth?0 | (string & {}) | 1 | undefinedborderRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderTopLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderTopRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderBottomRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderBottomLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedwidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedminWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedmaxWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedheight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedminHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedmaxHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedtop?0 | (string & {}) | undefinedleft?0 | (string & {}) | undefinedright?0 | (string & {}) | undefinedbottom?0 | (string & {}) | undefinedpadding?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedp?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingX?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpx?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingY?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpy?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingTop?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpt?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpaddingRight?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpr?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingBottom?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpb?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpaddingLeft?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpl?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefineddisplay?(string & {}) | "flex" | "none" | undefinedposition?(string & {}) | "relative" | "absolute" | "fixed" | undefinedoverflowX?(string & {}) | "visible" | "hidden" | undefinedoverflowY?(string & {}) | "visible" | "hidden" | undefinedzIndex?Property.ZIndex | undefinedflexGrow?boolean | Property.FlexGrow | undefinedflexShrink?boolean | Property.FlexShrink | undefinedflexDirection?(string & {}) | "row" | "column" | "rowReverse" | "columnReverse" | undefinedflexWrap?boolean | "nowrap" | "wrap" | undefinedjustifyContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "spaceBetween" | "spaceAround" | undefinedjustifySelf?Property.JustifySelf | undefinedalignItems?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefinedalignContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefinedalignSelf?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefinedgap?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedstyle?CSSProperties | undefinedchildren?React.ReactNodeclassName?string | undefinedDialogFooter
DialogAction
Prop
Type
icon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedprefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedsuffixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefinedflexGrow?boolean | Property.FlexGrow | undefinedchildren?React.ReactNodebindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedaccessibility-label?string | undefinedaccessibility-element?boolean | undefinedaccessibility-traits?"disabled" | "none" | "text" | "image" | "button" | "link" | "header" | "search" | "selected" | "playable" | "keyboard" | "summary" | "updating" | "adjustable" | "tabbar" | undefinedExamples
Trigger
<DialogTrigger>를 탭하면 Dialog가 열립니다. Lynx는 React의 asChild를 지원하지 않으며, Trigger가 자식 요소를 감싸는 native view를 렌더링합니다.
Controlled
Trigger 외의 방식으로 Dialog를 열고 닫으려면 open과 onOpenChange로 상태를 제어합니다.
Size
<DialogRoot>의 size prop으로 Dialog 너비를 변경할 수 있습니다. "medium"이 기본값이며 "large"를 지원합니다. 두 size 모두 작은 화면에서는 recipe의 뷰포트 기반 너비를 사용하고, 더 넓은 화면에서는 Dialog token의 size별 최대 너비가 적용됩니다. Content 높이는 recipe의 최대 높이로 제한됩니다.
Body
<DialogBody>는 seed-dialog__body selector가 적용되는 본문 slot입니다. literal native <scroll-view scroll-y>로 렌더링되어 Content의 최대 높이 안에서 세로로 스크롤하며, 기본 가로 패딩은 recipe의 body token(x6)에서 제공합니다.
웹 버전과 달리 Lynx는 overflow 상태를 관찰해 헤더 구분선이나 하단 fade 마스크를 표시하지 않습니다.
Custom Body
<DialogBody>는 기존 Lynx styled props를 받습니다. className 또는 style로 height, min-height, 정렬을 재정의할 수 있습니다. Recipe 기본 padding을 확실히 덮어써야 하는 경우에는 CSS 로드 순서의 영향을 받지 않도록 style에 paddingLeft와 paddingRight를 직접 지정하세요. 기본 가로 패딩을 제거해 full-bleed 콘텐츠를 배치할 때만 명시적으로 지정하세요.
Show Close Button
Registry의 DialogContent는 showCloseButton을 지원하며 기본값은 true입니다. false로 설정하면 헤더의 닫기 ActionButton을 숨기고, 푸터 등에 명시적인 닫기 액션을 제공할 수 있습니다.
Footer Layout
DialogFooter는 flex 레이아웃만 제공합니다. 버튼 배치는 자식 view에 flex-direction, justify-content, gap 등을 지정해 직접 구성합니다.
Prevent Close
Lynx의 DialogAction에는 React 이벤트의 preventDefault()를 사용하는 방식이 없습니다. Dialog를 controlled 상태로 만들고 onOpenChange(false)를 조건에 따라 무시하면 닫힘을 막을 수 있습니다.
onOpenChange Details
Lynx의 onOpenChange는 변경된 open 값만 전달합니다. React 버전의 두 번째 인자인 details.reason은 지원하지 않습니다. 닫힘 원인이 필요하면 앱이 소유한 버튼과 Trigger의 이벤트 핸들러에서 원인을 별도 상태로 기록하세요.
Confirm Before Close
React 예제처럼 closeButton, escapeKeyDown, interactOutside별로 확인 Dialog를 띄우는 동작은 Lynx에서 지원하지 않습니다. Lynx의 callback만으로는 닫힘 원인을 구분할 수 없으므로, 확인이 필요한 액션은 앱이 소유한 버튼의 핸들러에서 확인 Dialog를 먼저 열고 원본 Dialog의 open을 직접 제어하세요.
Portalled
DialogContent의 container를 지정하면 native overlay 레이어에 렌더링됩니다. overlayLevel로 overlay 레이어의 표시 순서를 지정할 수 있습니다.
Responsive
React의 ResponsiveDialog와 같은 자동 전환 컴포넌트는 Lynx에서 제공하지 않습니다. 화면 크기에 따라 Dialog와 BottomSheet를 앱에서 직접 선택하거나, 동일한 controlled 상태를 두 컴포넌트에 연결하세요.
웹 버전과의 차이
| 항목 | 웹 | Lynx |
|---|---|---|
| 사용자 이벤트 | onClick | bindtap 또는 공개 callback |
| 렌더링 요소 | HTML 요소와 Portal | native <view>, <text>, <scroll-view>와 overlay |
| 상태 prop | open, defaultOpen, onOpenChange | 동일. 내부에서 lynx-ui-dialog의 show, defaultShow, onShowChange로 연결 |
| Trigger 합성 | asChild 지원 | 미지원. Trigger가 자식 요소를 감싸는 view 렌더링 |
| 본문 slot | DialogBody 제공 | DialogBody 제공. native <scroll-view scroll-y>와 seed-dialog__body selector를 사용하며 기본 가로 패딩은 recipe body token(x6)에서 제공 |
| 닫기 버튼 | showCloseButton 지원 | Registry의 DialogContent에서 기본 지원. showCloseButton={false}로 숨김 |
| 닫힘 원인 | onOpenChange의 details.reason 제공 | boolean open 값만 제공 |
| Portal / overlay | Portal 컴포넌트 | DialogContent의 container, overlayLevel |
| Responsive Dialog | ResponsiveDialog 제공 | 미제공. Dialog와 BottomSheet를 앱에서 직접 조합 |
| 마운트 제어 | lazyMount, unmountOnExit | forceMount 지원 |
| imperative ref API | 없음 | 지원하지 않음 |
Last updated on