# 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 } from "@seed-design/lynx-react"; export default function Example() { 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 ## Usage ```tsx import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton } from "@seed-design/lynx-react"; export function App() { return ( 다이얼로그 열기 본문 콘텐츠 닫기 ); } ``` `DialogTrigger`는 Content 밖에 두세요. `DialogContent`는 기본으로 헤더 닫기 버튼을 표시하며, `showCloseButton={false}`로 숨길 수 있습니다. `DialogBody`는 Content의 최대 높이 안에서 세로로 스크롤합니다. `DialogAction`은 탭하면 Dialog를 닫지만, `disabled` 또는 `loading` 상태에서는 닫지 않습니다. ## Props ### `DialogRoot` ### `DialogTrigger` 고유 prop이 없습니다. ### `DialogContent` ### `DialogBody` 고유 prop이 없습니다. `className`, `style`, `children`과 `padding`·`gap` 같은 스타일 prop을 받습니다. ### `DialogFooter` 고유 prop이 없습니다. `className`, `style`, `children`과 `padding`·`gap` 같은 스타일 prop을 받습니다. ### `DialogAction` ## Examples ### Trigger ``를 탭하면 Dialog가 열립니다. Lynx는 React의 `asChild`를 지원하지 않으며, Trigger가 자식 요소를 감싸는 native view를 렌더링합니다. Trigger가 버튼 역할과 이름을 노출하고 안쪽 `ActionButton`은 별도 접근성 요소가 되지 않습니다. 이름은 문자열 children에서 만들며 직접 전달한 `accessibility-label`이 우선합니다. raw `` 등에서 텍스트를 파생할 수 없는 경우 이름을 명시하세요. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton } from "@seed-design/lynx-react"; export default function Example() { 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, HStack } from "@seed-design/lynx-react"; export default function Example() { 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`의 기본값은 `"medium"`이며 `"large"`도 지원합니다. 작은 화면에서는 화면 너비에 맞추고, 넓은 화면에서는 size별 최대 너비를 적용합니다. Content 높이도 화면에 맞게 제한됩니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, HStack } 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() { return ( ); } ``` ### Body `DialogBody`는 Content의 최대 높이 안에서 세로로 스크롤합니다. Lynx에서는 overflow 상태에 따른 헤더 구분선과 하단 fade mask를 표시하지 않습니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, HStack } 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() { return ( ); } ``` ### Custom Body `className`과 `style`로 높이와 정렬을 바꿀 수 있습니다. 기본 가로 여백을 없애는 full-bleed 콘텐츠에는 `style`의 `paddingLeft`와 `paddingRight`를 지정하세요. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, HStack } 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() { return ( ); } ``` ### Long Content 본문이 최대 높이에 닿으면 `DialogBody`만 스크롤되고 Header와 Footer는 그대로 보입니다. 최대 높이는 화면 높이의 80%와, 위아래 inset 중 큰 값을 양쪽에서 뺀 높이 중 작은 쪽입니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton } from "@seed-design/lynx-react"; export default function Example() { return ( Open Dialog {Array.from({ length: 40 }, (_, index) => index + 1).map((line) => ( {line}. 본문이 길어지면 Body 영역만 스크롤됩니다. ))} 취소 확인 ); } ``` ### Show Close Button `showCloseButton={false}`로 헤더 닫기 버튼을 숨길 수 있습니다. 이 경우 푸터 등에 닫기 action을 제공하세요. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton, HStack } from "@seed-design/lynx-react"; function DialogExample({ showCloseAction }: { showCloseAction: boolean }) { return ( {showCloseAction ? "닫기 액션 있음" : "닫기 액션 없음"} {showCloseAction ? "DialogContent의 showCloseButton을 사용합니다." : "showCloseButton={false}인 경우 푸터 액션만 제공합니다."} 취소 확인 ); } export default function Example() { 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 } from "@seed-design/lynx-react"; export default function Example() { 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 } from "@seed-design/lynx-react"; export default function Example() { 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 `container`를 지정하지 않으면 Dialog는 Lynx view 안에 렌더링됩니다. `container="window"`처럼 native container 이름을 지정하면 host 화면까지 덮는 native overlay에 렌더링됩니다. `overlayLevel`은 overlay의 표시 순서를 정합니다. ```tsx import "./styles"; import { DialogAction, DialogBody, DialogContent, DialogFooter, DialogRoot, DialogTrigger, } from "@/components/ui/dialog"; import { ActionButton } from "@seed-design/lynx-react"; export default function Example() { return ( 열기 콘텐츠는 window 컨테이너와 overlay level 2 위에 표시됩니다. 취소 확인 ); } ``` ### Responsive React의 `ResponsiveDialog`와 같은 자동 전환 컴포넌트는 Lynx에서 제공하지 않습니다. 화면 크기에 따라 `Dialog`와 `BottomSheet`를 앱에서 직접 선택하거나, 동일한 controlled 상태를 두 컴포넌트에 연결하세요. ## 웹 버전과의 차이 - Trigger는 자식을 native `view`로 감싸며 `asChild`를 제공하지 않습니다. - `onOpenChange`는 닫힌 원인 없이 boolean 값만 전달합니다. - `DialogBody`는 native scroll view로 렌더링하며, overflow 구분선과 fade mask를 표시하지 않습니다. - 제목에는 heading 접근성을 적용하지만 Content와 제목을 DOM id로 연결하지 않습니다. - `container`와 `overlayLevel`로 native overlay의 위치와 표시 순서를 지정합니다. - `ResponsiveDialog`, `lazyMount`, `unmountOnExit`은 제공하지 않습니다. 반응형 전환이 필요하면 같은 controlled 상태로 Dialog와 Bottom Sheet를 선택하세요.