# Dialog URL: /lynx/components/dialog Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/dialog.mdx 화면 중앙에 떠서 사용자의 주의를 모으고, 제목·본문·푸터를 담는 모달형 컴포넌트입니다. Lynx Engine 최소 버전: 3.6 사용 XElement: 사용 가능 버전: @seed-design/lynx-react@0.7.0, @seed-design/lynx-css@0.11.0 ## Preview ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( Open Dialog 본문에는 사용자가 확인해야 할 내용이나 추가 입력 폼을 배치할 수 있습니다. 취소 확인 ); } ``` ## Installation - npm: npx @seed-design/cli add ui:dialog - pnpm: pnpm dlx @seed-design/cli add ui:dialog - yarn: yarn dlx @seed-design/cli add ui:dialog - bun: bun x @seed-design/cli add ui:dialog `@seed-design/lynx-react`의 `Dialog`는 [`@lynx-js/lynx-ui-dialog`](https://github.com/lynx-family/lynx-ui/tree/main/packages/lynx-ui-dialog)를 래핑합니다. Registry의 `DialogContent`는 Positioner, Backdrop, Header를 내부에서 조립하고, `DialogAction`은 `ActionButton`과 닫기 동작을 함께 제공합니다. ## Usage ```tsx import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton } from "@seed-design/lynx-react"; export function App() { return ( 다이얼로그 열기 본문 콘텐츠 닫기 ); } ``` Registry의 `DialogContent`는 `Positioner`, `Backdrop`, `Header`를 내부에서 조립합니다. `DialogTrigger`는 Content 밖에 두어야 탭으로 Dialog를 열 수 있습니다. `DialogContent`의 `showCloseButton`은 기본값이 `true`이며, 헤더에 닫기 `ActionButton`을 렌더링합니다. 닫기 버튼이 필요하지 않으면 `showCloseButton={false}`로 설정하세요. `DialogBody`는 native ``로 렌더링되며 Content의 최대 높이 안에서 세로로 스크롤합니다. `DialogAction`은 `ActionButton` props를 받아 닫기 동작과 함께 렌더링합니다. ## Props ### `DialogRoot` ### `DialogTrigger` ### `DialogContent` ### `DialogBody` ### `DialogFooter` ### `DialogAction` ## Examples ### Trigger ``를 탭하면 Dialog가 열립니다. Lynx는 React의 `asChild`를 지원하지 않으며, Trigger가 자식 요소를 감싸는 native view를 렌더링합니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( Open Trigger를 탭하면 현재 화면 위에 Dialog가 열립니다. 취소 확인 ); } ``` ### Controlled Trigger 외의 방식으로 Dialog를 열고 닫으려면 `open`과 `onOpenChange`로 상태를 제어합니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [open, setOpen] = useState(false); function handleOpen() { "background only"; setOpen(true); } return ( 열기 Labore do culpa dolore irure nisi dolor dolor laboris veniam ipsum excepteur adipisicing laboris non quis. 취소 확인 ); } ``` ### Size ``의 `size` prop으로 Dialog 너비를 변경할 수 있습니다. `"medium"`이 기본값이며 `"large"`를 지원합니다. 두 size 모두 작은 화면에서는 recipe의 뷰포트 기반 너비를 사용하고, 더 넓은 화면에서는 Dialog token의 size별 최대 너비가 적용됩니다. Content 높이는 recipe의 최대 높이로 제한됩니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; function DialogExample({ size }: { size: "medium" | "large" }) { const isLarge = size === "large"; return ( {isLarge ? "Large Dialog" : "Medium Dialog"} {isLarge ? "large size는 넓은 화면에서 더 큰 Dialog token 너비를 사용합니다." : "medium은 Dialog의 기본 size입니다."} 취소 확인 ); } export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( ); } ``` ### Body ``는 `seed-dialog__body` selector가 적용되는 본문 slot입니다. literal native ``로 렌더링되어 Content의 최대 높이 안에서 세로로 스크롤하며, 기본 가로 패딩은 recipe의 body token(`x6`)에서 제공합니다. 웹 버전과 달리 Lynx는 overflow 상태를 관찰해 헤더 구분선이나 하단 fade 마스크를 표시하지 않습니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; function DialogExample({ long }: { long: boolean }) { return ( {long ? "긴 본문 (스크롤 적용)" : "짧은 본문 (스크롤 없음)"} {long ? ( Array.from({ length: 16 }, (_, index) => index + 1).map((line) => ( {line}. Lynx의 DialogBody는 native scroll-view로 렌더링됩니다. )) ) : ( 내용이 짧으면 마지막 줄까지 바로 확인할 수 있습니다. )} 취소 확인 ); } export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( ); } ``` ### Custom Body ``는 기존 Lynx styled props를 받습니다. `className` 또는 `style`로 `height`, `min-height`, 정렬을 재정의할 수 있습니다. Recipe 기본 padding을 확실히 덮어써야 하는 경우에는 CSS 로드 순서의 영향을 받지 않도록 `style`에 `paddingLeft`와 `paddingRight`를 직접 지정하세요. 기본 가로 패딩을 제거해 full-bleed 콘텐츠를 배치할 때만 명시적으로 지정하세요. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; function DialogExample({ variant }: { variant: "maxHeight" | "minHeight" | "fullBleed" }) { const isMaxHeight = variant === "maxHeight"; const isMinHeight = variant === "minHeight"; const isFullBleed = variant === "fullBleed"; return ( {isMaxHeight ? "maxHeight 200px" : isMinHeight ? "minHeight + 가운데 정렬" : "paddingX 0"} {isMaxHeight ? ( {Array.from({ length: 12 }, (_, index) => index + 1).map((line) => ( {line}. 본문이 200px을 넘으면 그 안에서 스크롤됩니다. ))} ) : isMinHeight ? ( 아직 항목이 없습니다 ) : ( 가장자리까지 닿는 영역입니다 )} 취소 확인 ); } export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( ); } ``` ### Show Close Button Registry의 `DialogContent`는 `showCloseButton`을 지원하며 기본값은 `true`입니다. `false`로 설정하면 헤더의 닫기 `ActionButton`을 숨기고, 푸터 등에 명시적인 닫기 액션을 제공할 수 있습니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; function DialogExample({ showCloseAction }: { showCloseAction: boolean }) { return ( {showCloseAction ? "닫기 액션 있음" : "닫기 액션 없음"} {showCloseAction ? "DialogContent의 showCloseButton을 사용합니다." : "showCloseButton={false}인 경우 푸터 액션만 제공합니다."} 취소 확인 ); } export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( ); } ``` ### Footer Layout `DialogFooter`는 flex 레이아웃만 제공합니다. 버튼 배치는 자식 `view`에 `flex-direction`, `justify-content`, `gap` 등을 지정해 직접 구성합니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( Footer 레이아웃 DialogFooter는 flex 레이아웃만 제공합니다. 넓은 다이얼로그에서는 주요 액션을 우측에 가로로 정렬할 수 있습니다. 취소 확인 ); } ``` ### Prevent Close Lynx의 `DialogAction`에는 React 이벤트의 `preventDefault()`를 사용하는 방식이 없습니다. Dialog를 controlled 상태로 만들고 `onOpenChange(false)`를 조건에 따라 무시하면 닫힘을 막을 수 있습니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [open, setOpen] = useState(false); const [preventClose, setPreventClose] = useState(true); function handleTogglePreventClose() { "background only"; setPreventClose((previous) => !previous); } function handleOpenChange(nextOpen: boolean) { if (nextOpen || !preventClose) { setOpen(nextOpen); } } return ( 열기 닫힘 방지: {JSON.stringify(preventClose)} 전환 취소 확인 ); } ``` ### `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 레이어의 표시 순서를 지정할 수 있습니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, useSeedClassName } from "@seed-design/lynx-react"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 열기 콘텐츠는 window 컨테이너와 overlay level 2 위에 표시됩니다. 취소 확인 ); } ``` ### Responsive React의 `ResponsiveDialog`와 같은 자동 전환 컴포넌트는 Lynx에서 제공하지 않습니다. 화면 크기에 따라 `Dialog`와 `BottomSheet`를 앱에서 직접 선택하거나, 동일한 controlled 상태를 두 컴포넌트에 연결하세요. ## 웹 버전과의 차이 | 항목 | 웹 | Lynx | | ------------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | 사용자 이벤트 | `onClick` | `bindtap` 또는 공개 callback | | 렌더링 요소 | HTML 요소와 Portal | native ``, ``, ``와 overlay | | 상태 prop | `open`, `defaultOpen`, `onOpenChange` | 동일. 내부에서 lynx-ui-dialog의 `show`, `defaultShow`, `onShowChange`로 연결 | | Trigger 합성 | `asChild` 지원 | 미지원. Trigger가 자식 요소를 감싸는 view 렌더링 | | 본문 slot | `DialogBody` 제공 | `DialogBody` 제공. native ``와 `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 | 없음 | 지원하지 않음 |