Lynx

Dialog

화면 중앙에 떠서 사용자의 주의를 모으고, 제목·본문·푸터를 담는 모달형 컴포넌트입니다.

Engine ≥ 3.6<overlay>
사용 가능 버전@seed-design/[email protected], @seed-design/[email protected]
Lynx 예제를 불러오는 중입니다.

Installation

npx @seed-design/cli add ui:dialog

@seed-design/lynx-reactDialog@lynx-js/lynx-ui-dialog를 래핑합니다. Registry의 DialogContent는 Positioner, Backdrop, Header를 내부에서 조립하고, DialogActionActionButton과 닫기 동작을 함께 제공합니다.

Usage

import {
  DialogAction,
  DialogBody,
  DialogContent,
  DialogFooter,
  DialogRoot,
  DialogTrigger,
} from "@/components/ui/dialog";
import { ActionButton } from "@seed-design/lynx-react";

export function App() {
  return (
    <DialogRoot size="medium">
      <DialogTrigger>
        <ActionButton variant="neutralSolid">다이얼로그 열기</ActionButton>
      </DialogTrigger>
      <DialogContent title="제목" description="부가 설명">
        <DialogBody>
          <text>본문 콘텐츠</text>
        </DialogBody>
        <DialogFooter>
          <DialogAction variant="neutralSolid">닫기</DialogAction>
        </DialogFooter>
      </DialogContent>
    </DialogRoot>
  );
}

Registry의 DialogContentPositioner, Backdrop, Header를 내부에서 조립합니다. DialogTrigger는 Content 밖에 두어야 탭으로 Dialog를 열 수 있습니다. DialogContentshowCloseButton은 기본값이 true이며, 헤더에 닫기 ActionButton을 렌더링합니다. 닫기 버튼이 필요하지 않으면 showCloseButton={false}로 설정하세요.

DialogBody는 native <scroll-view scroll-y>로 렌더링되며 Content의 최대 높이 안에서 세로로 스크롤합니다. DialogActionActionButton props를 받아 닫기 동작과 함께 렌더링합니다.

Props

DialogRoot

Prop

Type

DialogTrigger

Prop

Type

DialogContent

Prop

Type

title?React.ReactNode
description?React.ReactNode
children?React.ReactNode
container?string | undefined
overlayLevel?1 | 2 | 3 | 4 | undefined

DialogBody

Prop

Type

bg?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefined
background?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefined
color?(string & {}) | ScopedColorPalette | ScopedColorFg | undefined
borderColor?(string & {}) | ScopedColorPalette | ScopedColorStroke | undefined
borderWidth?0 | (string & {}) | 1 | undefined
borderTopWidth?0 | (string & {}) | 1 | undefined
borderRightWidth?0 | (string & {}) | 1 | undefined
borderBottomWidth?0 | (string & {}) | 1 | undefined
borderLeftWidth?0 | (string & {}) | 1 | undefined
borderRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
width?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
height?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
top?0 | (string & {}) | undefined
left?0 | (string & {}) | undefined
right?0 | (string & {}) | undefined
bottom?0 | (string & {}) | undefined
padding?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
p?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingX?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
px?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingY?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
py?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingTop?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
pt?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingRight?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
pr?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingBottom?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
pb?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingLeft?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
pl?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
display?(string & {}) | "flex" | "none" | undefined
position?(string & {}) | "relative" | "absolute" | "fixed" | undefined
overflowX?(string & {}) | "visible" | "hidden" | undefined
overflowY?(string & {}) | "visible" | "hidden" | undefined
zIndex?Property.ZIndex | undefined
flexGrow?boolean | Property.FlexGrow | undefined
flexShrink?boolean | Property.FlexShrink | undefined
flexDirection?(string & {}) | "row" | "column" | "rowReverse" | "columnReverse" | undefined
flexWrap?boolean | "nowrap" | "wrap" | undefined
justifyContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "spaceBetween" | "spaceAround" | undefined
justifySelf?Property.JustifySelf | undefined
alignItems?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
alignContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
alignSelf?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
gap?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined

DialogFooter

Prop

Type

bg?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefined
background?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefined
color?(string & {}) | ScopedColorPalette | ScopedColorFg | undefined
borderColor?(string & {}) | ScopedColorPalette | ScopedColorStroke | undefined
borderWidth?0 | (string & {}) | 1 | undefined
borderTopWidth?0 | (string & {}) | 1 | undefined
borderRightWidth?0 | (string & {}) | 1 | undefined
borderBottomWidth?0 | (string & {}) | 1 | undefined
borderLeftWidth?0 | (string & {}) | 1 | undefined
borderRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
width?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
height?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
top?0 | (string & {}) | undefined
left?0 | (string & {}) | undefined
right?0 | (string & {}) | undefined
bottom?0 | (string & {}) | undefined
padding?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
p?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingX?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
px?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingY?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
py?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingTop?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
pt?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingRight?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
pr?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingBottom?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
pb?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingLeft?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
pl?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
display?(string & {}) | "flex" | "none" | undefined
position?(string & {}) | "relative" | "absolute" | "fixed" | undefined
overflowX?(string & {}) | "visible" | "hidden" | undefined
overflowY?(string & {}) | "visible" | "hidden" | undefined
zIndex?Property.ZIndex | undefined
flexGrow?boolean | Property.FlexGrow | undefined
flexShrink?boolean | Property.FlexShrink | undefined
flexDirection?(string & {}) | "row" | "column" | "rowReverse" | "columnReverse" | undefined
flexWrap?boolean | "nowrap" | "wrap" | undefined
justifyContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "spaceBetween" | "spaceAround" | undefined
justifySelf?Property.JustifySelf | undefined
alignItems?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
alignContent?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
alignSelf?(string & {}) | "flexStart" | "flexEnd" | "center" | "stretch" | undefined
gap?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined

DialogAction

Prop

Type

icon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
prefixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
suffixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
flexGrow?boolean | Property.FlexGrow | undefined
children?React.ReactNode
bindtap?EventHandler<BaseTouchEvent<Target>> | undefined
main-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefined
accessibility-label?string | undefined
accessibility-element?boolean | undefined
accessibility-traits?"disabled" | "none" | "text" | "image" | "button" | "link" | "header" | "search" | "selected" | "playable" | "keyboard" | "summary" | "updating" | "adjustable" | "tabbar" | undefined

Examples

Trigger

<DialogTrigger>를 탭하면 Dialog가 열립니다. Lynx는 React의 asChild를 지원하지 않으며, Trigger가 자식 요소를 감싸는 native view를 렌더링합니다.

Lynx 예제를 불러오는 중입니다.

Controlled

Trigger 외의 방식으로 Dialog를 열고 닫으려면 openonOpenChange로 상태를 제어합니다.

Lynx 예제를 불러오는 중입니다.

Size

<DialogRoot>size prop으로 Dialog 너비를 변경할 수 있습니다. "medium"이 기본값이며 "large"를 지원합니다. 두 size 모두 작은 화면에서는 recipe의 뷰포트 기반 너비를 사용하고, 더 넓은 화면에서는 Dialog token의 size별 최대 너비가 적용됩니다. Content 높이는 recipe의 최대 높이로 제한됩니다.

Lynx 예제를 불러오는 중입니다.

Body

<DialogBody>seed-dialog__body selector가 적용되는 본문 slot입니다. literal native <scroll-view scroll-y>로 렌더링되어 Content의 최대 높이 안에서 세로로 스크롤하며, 기본 가로 패딩은 recipe의 body token(x6)에서 제공합니다.

웹 버전과 달리 Lynx는 overflow 상태를 관찰해 헤더 구분선이나 하단 fade 마스크를 표시하지 않습니다.

Lynx 예제를 불러오는 중입니다.

Custom Body

<DialogBody>는 기존 Lynx styled props를 받습니다. className 또는 styleheight, min-height, 정렬을 재정의할 수 있습니다. Recipe 기본 padding을 확실히 덮어써야 하는 경우에는 CSS 로드 순서의 영향을 받지 않도록 stylepaddingLeftpaddingRight를 직접 지정하세요. 기본 가로 패딩을 제거해 full-bleed 콘텐츠를 배치할 때만 명시적으로 지정하세요.

Lynx 예제를 불러오는 중입니다.

Show Close Button

Registry의 DialogContentshowCloseButton을 지원하며 기본값은 true입니다. false로 설정하면 헤더의 닫기 ActionButton을 숨기고, 푸터 등에 명시적인 닫기 액션을 제공할 수 있습니다.

Lynx 예제를 불러오는 중입니다.

DialogFooter는 flex 레이아웃만 제공합니다. 버튼 배치는 자식 viewflex-direction, justify-content, gap 등을 지정해 직접 구성합니다.

Lynx 예제를 불러오는 중입니다.

Prevent Close

Lynx의 DialogAction에는 React 이벤트의 preventDefault()를 사용하는 방식이 없습니다. Dialog를 controlled 상태로 만들고 onOpenChange(false)를 조건에 따라 무시하면 닫힘을 막을 수 있습니다.

Lynx 예제를 불러오는 중입니다.

onOpenChange Details

Lynx의 onOpenChange는 변경된 open 값만 전달합니다. React 버전의 두 번째 인자인 details.reason은 지원하지 않습니다. 닫힘 원인이 필요하면 앱이 소유한 버튼과 Trigger의 이벤트 핸들러에서 원인을 별도 상태로 기록하세요.

Confirm Before Close

React 예제처럼 closeButton, escapeKeyDown, interactOutside별로 확인 Dialog를 띄우는 동작은 Lynx에서 지원하지 않습니다. Lynx의 callback만으로는 닫힘 원인을 구분할 수 없으므로, 확인이 필요한 액션은 앱이 소유한 버튼의 핸들러에서 확인 Dialog를 먼저 열고 원본 Dialog의 open을 직접 제어하세요.

Portalled

DialogContentcontainer를 지정하면 native overlay 레이어에 렌더링됩니다. overlayLevel로 overlay 레이어의 표시 순서를 지정할 수 있습니다.

Lynx 예제를 불러오는 중입니다.

Responsive

React의 ResponsiveDialog와 같은 자동 전환 컴포넌트는 Lynx에서 제공하지 않습니다. 화면 크기에 따라 DialogBottomSheet를 앱에서 직접 선택하거나, 동일한 controlled 상태를 두 컴포넌트에 연결하세요.

웹 버전과의 차이

항목Lynx
사용자 이벤트onClickbindtap 또는 공개 callback
렌더링 요소HTML 요소와 Portalnative <view>, <text>, <scroll-view>와 overlay
상태 propopen, defaultOpen, onOpenChange동일. 내부에서 lynx-ui-dialog의 show, defaultShow, onShowChange로 연결
Trigger 합성asChild 지원미지원. Trigger가 자식 요소를 감싸는 view 렌더링
본문 slotDialogBody 제공DialogBody 제공. native <scroll-view scroll-y>seed-dialog__body selector를 사용하며 기본 가로 패딩은 recipe body token(x6)에서 제공
닫기 버튼showCloseButton 지원Registry의 DialogContent에서 기본 지원. showCloseButton={false}로 숨김
닫힘 원인onOpenChangedetails.reason 제공boolean open 값만 제공
Portal / overlayPortal 컴포넌트DialogContentcontainer, overlayLevel
Responsive DialogResponsiveDialog 제공미제공. Dialog와 BottomSheet를 앱에서 직접 조합
마운트 제어lazyMount, unmountOnExitforceMount 지원
imperative ref API없음지원하지 않음

Last updated on

목차