Dialog
Overlay
확인·입력처럼 집중이 필요한 작업을 띄우는 대화상자.
Usage
사용자의 확인이 필요한 중요한 행동 현재 화면을 벗어나지 않는 짧은 입력 작업
import
import
import { Dialog } from '@mildang/design-system/Dialog';예제를 복사해 쓸 때 필요한 준비
• @mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.
• @mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.
API Reference
Dialog Props
Prop
Type
Default
"xs" | "sm" | "md" | "lg" | "xl"
md
boolean
true
boolean
false
"body" | "overlay"
body
"xs" | "sm" | "md" | "lg" | "xl" | "full"
md
같은 패밀리
@mildang/design-system/Dialog 에서 같이 내보내는 컴포넌트다.
폭 선택 — size
Figma dialog 정본의 폭 5단계다. 테마 공통이며 전부 maxWidth: calc(100vw - 48px) 로
캡된다 — Figma 모달 프레임의 좌우 padding 24다.
| size | 너비 | 용도 |
|---|---|---|
xs | 240px | 한 줄 확인. header/footer padding 도 한 단계 좁아진다 |
sm | 312px | 짧은 확인·간단 선택. 모바일 폭에 가까운 기본 확인창 |
md(기본) | 600px | 필드 몇 개짜리 폼, 목록 선택 |
lg | 960px | 2단 레이아웃, 표가 들어가는 편집 화면 |
xl | 1140px | 가장 넓은 단계. 이보다 커지면 Drawer·페이지를 검토한다 |
full | 95vw × 90vh | Figma 에 없는 code-only escape hatch. 신규 사용은 지양한다 |
size 는 header/body/footer 의 padding, 타이틀 타이포그래피, Dialog.Button 의 기본
size까지 함께 결정한다 — 값은 테마(mildang/educore)별로 다르다.
size 는 모달 뷰 전용이다. isResponsive 로 바텀시트가 떠 있는 동안에는 전폭 1종으로
고정되고 이 prop 은 무시된다.
반응형 — 하나의 마크업, 두 개의 셸
isResponsive 를 켜면 마크업을 그대로 둔 채 좁은 화면에서 중앙 모달 대신 BottomSheet 가
올라온다(Figma _dialog_wrapper 의 is_responsive 대응). 과거 별도 컴포넌트였던
ResponsiveDialog 는 이 prop 으로 흡수됐다.
| Prop | 기본값 | 설명 |
|---|---|---|
isResponsive | false | breakpoint 미만에서 BottomSheet 로 전환한다. Figma 기본값은 true 지만, 기존 소비처의 모바일 동작이 한 번에 바뀌는 것을 막기 위해 코드 기본값은 false 다. 신규 화면은 명시적으로 켠다 |
breakpoint | 'md' | 전환 기준점. xs 0 / sm 600 / md 960 / lg 1280 / xl 1920(px) |
dismissible | true | false 면 scrim 클릭·ESC 로 닫히지 않는다. 모달·시트 양쪽에 동일하게 적용된다 |
시트로 전환되면 셸만 바뀐다 — 콘텐츠 스타일(header/title/body/footer)은 Dialog 가 계속 소유하고, BottomSheet 는 위치·상단 radius·overlay·드래그만 담당한다. 시트에서는 아래 모서리를 둥글리지 않고 footer 가 하단에 붙는다.
스크롤 — scrollType
| scrollType | 동작 |
|---|---|
body(기본) | content 높이를 85vh 로 캡하고 Body 만 스크롤한다. Header·Footer 는 항상 보인다 |
overlay | content 는 콘텐츠 높이만큼 자라고 scrim 전체가 스크롤된다. Header·Footer 도 함께 밀려 올라간다 |
scrollType 은 모달 뷰 전용이다 — 시트에는 overlay 스크롤이 의존하는 overlay 엘리먼트가
없어 시트로 전환되면 이 prop 자체를 넘기지 않는다.
접근성과 모달
Dialog.Title 은 필수다. 생략하면 aria-labelledby 가 끊겨 스크린리더가 대화창의 목적을
읽지 못한다.
기본값(modal 생략, true)은 열리면 배경이 aria-hidden 으로 감춰지고 body 스크롤이
잠기며 포커스가 패널 안에 트랩된다 — 밖으로 나가려 해도 다시 안으로 돌아온다. 닫히면
포커스는 트리거로 돌아간다. modal={false} 는 이 셋을 모두 실제로 푼다(실측 확인) —
Drawer 가 감싸는 vaul 과 달리 Dialog 는 modal 을 Radix 에 그대로 전달하기 때문이다.
이때도 dim 자체는 시각적으로 남아 있지만 클릭만 배경으로 통과한다.
ESC 로 닫힌다. 닫기를 막아야 하면 dismissible={false} 를 쓰고, 닫는 수단을 Footer 버튼으로
반드시 남겨준다 — dismissible={false} 는 scrim 클릭과 ESC 를 모두 막지만 명시적 액션은
막지 않는다.
scrim(dim)은 scrimStyle 유틸이 공통 토큰을 참조해 그린다. Dialog·Drawer·BottomSheet·
Sidebar 가 같은 유틸을 공유하므로 개별 컴포넌트에서 dim 색을 직접 지정하지 않는다. Dialog
에서는 scrim 이 콘텐츠 중앙 정렬까지 겸해서, hideOverlay 를 켜면 정렬도 함께 사라지고
위치는 호출자가 Content 의 style props 로 직접 잡아야 한다.
사용 가이드
권장
- 제목과 주요 행동만으로 목적이 이해되게 만든다.
- 취소와 확인의 결과를 명확히 구분한다.
지양
- 긴 다단계 흐름을 Dialog 안에 넣지 않는다.
- 닫는 방법 없이 사용자를 가두지 않는다.
예제
열린 상태
열린 상태를 상자 안에 고정해 보여줍니다 — 버튼만 찍힌 카드로는 안에 무엇이 뜨는지 알 수 없습니다.
import React from 'react';
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogLoadingSubmitExample = function LoadingSubmit() {
const [loading, setLoading] = React.useState(false);
const handleSubmit = () => {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
};
return (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">저장 확인 열기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>저장하시겠어요?</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>저장 버튼을 누르면 1.5초간 로딩 상태가 표시됩니다.</p>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary" disabled={loading}>
취소
</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary" loading={loading} onClick={handleSubmit}>
저장
</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
};
export default DialogLoadingSubmitExample;
기본 사용
Trigger·Header·Body·Footer 를 조합한 확인 다이얼로그 — 앱에서는 이 형태를 그대로 씁니다.
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogButtonHierarchyExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">버튼 위계 보기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>버튼 위계</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>primary > secondary > tertiary > quaternary 순으로 시각 강조도가 낮아진다.</p>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Button variant="quaternary">quaternary</Dialog.Button>
<Dialog.Button variant="tertiary">tertiary</Dialog.Button>
<Dialog.Button variant="secondary">secondary</Dialog.Button>
<Dialog.Button variant="primary">primary</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogButtonHierarchyExample;
Sizes
xs부터 full 까지 여섯 폭을 버튼으로 열어 비교합니다. 본문이 한 문단이면 sm, 폼이 들어가면 md 이상입니다.
import { css } from '@mildang/styled-system/css';
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogSizeExample = () => (
<div className={css({ display: 'flex', gap: '16px', flexDirection: 'column', p: '32px' })}>
{(
[
['xs', 'X-Small Dialog', 'Content for x-small dialog (240px)'],
['sm', 'Small Dialog', 'Content for small dialog (312px)'],
['md', 'Medium Dialog', 'Content for medium dialog (600px, default)'],
['lg', 'Large Dialog', 'Content for large dialog (960px)'],
['xl', 'Extra Large Dialog', 'Content for extra large dialog (1140px)'],
['full', 'Full Screen Dialog', 'Content for full screen dialog'],
] as const
).map(([size, title, content]) => (
<Dialog key={size} size={size}>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open {title}</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>{title}</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div>{content}</div>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
))}
</div>
);
export default DialogSizeExample;
입력 Dialog
Body 에 Field 와 Input 을 넣어 입력을 받습니다. 저장 결과를 되돌릴 수 없으면 ConfirmDialog 로 한 번 더 확인합니다.
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Grid } from '@mildang/styled-system/jsx';
import { Field } from '@mildang/design-system/Form';
import { FormLabel } from '@mildang/design-system/FormLabel';
import { Input } from '@mildang/design-system/Input';
const DialogFormListExample = () => {
return (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Modal Title</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Grid gap="20">
<Field required>
<FormLabel htmlFor="name">Name</FormLabel>
<Input fullWidth id="name" defaultValue="John Doe" />
</Field>
<Field>
<FormLabel htmlFor="username">Username</FormLabel>
<Input fullWidth id="username" defaultValue="johndoe" />
</Field>
</Grid>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button>취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
};
export default DialogFormListExample;
WithSelect
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Grid } from '@mildang/styled-system/jsx';
import { Field } from '@mildang/design-system/Form';
import { FormLabel } from '@mildang/design-system/FormLabel';
import { Input } from '@mildang/design-system/Input';
import { Select, createSelectCollection } from '@mildang/design-system/Select';
const frameworkCollection = createSelectCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Angular', value: 'angular' },
{ label: 'Solid', value: 'solid' },
],
});
const DialogWithSelectExample = () => {
return (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>프로젝트 설정</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Grid gap="20">
<Field>
<FormLabel htmlFor="project-name">프로젝트 이름</FormLabel>
<Input fullWidth id="project-name" defaultValue="My Project" />
</Field>
<Field>
<FormLabel htmlFor="framework">프레임워크</FormLabel>
<Select
id="framework"
fullWidth
collection={frameworkCollection}
placeholder="프레임워크를 선택하세요"
>
{frameworkCollection.items.map((item) => (
<Select.Option key={item.value} item={item} />
))}
</Select>
</Field>
</Grid>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button>취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
};
export default DialogWithSelectExample;
LongContentScrollable
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { css } from '@mildang/styled-system/css';
const DialogLongContentScrollableExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Long Content Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Dialog with Long Content</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div>
<p>This dialog has scrollable content.</p>
{Array.from({ length: 50 }, (_, i) => (
<p key={i} className={css({ mb: '8px' })}>
This is paragraph {i + 1} of a very long content that will require scrolling.
</p>
))}
</div>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">Cancel</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">Confirm</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogLongContentScrollableExample;
LongContentScrollOverlay
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { css } from '@mildang/styled-system/css';
const DialogLongContentScrollOverlayExample = () => (
<Dialog scrollType="overlay">
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Long Content Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Dialog with Long Content</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div>
<p>This dialog has scrollable content.</p>
{Array.from({ length: 50 }, (_, i) => (
<p key={i} className={css({ mb: '8px' })}>
This is paragraph {i + 1} of a very long content that will require scrolling.
</p>
))}
</div>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">Cancel</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">Confirm</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogLongContentScrollOverlayExample;
WithCloseIcon
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogWithCloseIconExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Dialog with Close Icon</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Dialog with Close Icon</Dialog.Title>
<Dialog.CloseIconButton />
</Dialog.Header>
<Dialog.Body>
<div>
<p>This dialog has a close icon button in the header.</p>
<p>You can close it by clicking the X icon, clicking outside, or using the Cancel button.</p>
</div>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">Cancel</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">Confirm</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogWithCloseIconExample;
WithLeadingIcon
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { HStack } from '@mildang/styled-system/jsx';
import DeleteIcon from '@mildang/icons/react/delete';
import { css } from '@mildang/styled-system/css';
const DialogWithLeadingIconExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Dialog with Leading Icon</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<HStack gap="8" flex="1" minW="0">
<DeleteIcon className={css({ fontSize: '24px', color: 'critical.fill.base', flexShrink: 0 })} />
<Dialog.Title>프로젝트 삭제</Dialog.Title>
</HStack>
<Dialog.CloseIconButton />
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
이 작업은 되돌릴 수 없습니다. 프로젝트와 모든 데이터가 영구적으로 삭제됩니다.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary" bg="critical.fill.base">
삭제
</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogWithLeadingIconExample;
FooterLeftAligned
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogFooterLeftAlignedExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">왼쪽 정렬 Footer 열기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>왼쪽 정렬 Footer</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
`Dialog.Footer`에 `justifyContent="flex-start"`를 주면 버튼이 왼쪽 끝에 배치됩니다.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer justifyContent="flex-start">
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogFooterLeftAlignedExample;
FooterSpaceBetween
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Grid } from '@mildang/styled-system/jsx';
const DialogFooterSpaceBetweenExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">양끝 배치 Footer 열기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>양끝 배치 Footer</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>왼쪽에 보조 액션, 오른쪽에 주 버튼 그룹을 배치합니다.</Dialog.Description>
</Dialog.Body>
<Dialog.Footer justifyContent="space-between">
<Dialog.Button variant="tertiary">도움말</Dialog.Button>
<Grid gridAutoFlow="column" gap="8">
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Grid>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogFooterSpaceBetweenExample;
ControlledDialog
import React from 'react';
import { css } from '@mildang/styled-system/css';
import { Button } from '@mildang/design-system/Button';
import { Dialog } from '@mildang/design-system/Dialog';
const DialogControlledDialogExample = function ControlledDialog() {
const [open, setOpen] = React.useState(false);
return (
<div className={css({ display: 'flex', gap: '16px' })}>
<Button variant="tertiary" onClick={() => setOpen(true)}>
Open Controlled Dialog
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Controlled Dialog</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div>
<p>This dialog is controlled by external state.</p>
</div>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Button variant="tertiary" onClick={() => setOpen(false)}>
Close
</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
</div>
);
};
export default DialogControlledDialogExample;
DestructiveActions
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogDestructiveActionsExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">강좌 삭제 확인</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>강좌를 삭제할까요?</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>삭제된 강좌는 복구할 수 없습니다.</p>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary" bg="critical.fill.base">
삭제
</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogDestructiveActionsExample;
DestructiveLowHierarchy
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import DeleteIcon from '@mildang/icons/react/delete';
const DialogDestructiveLowHierarchyExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">알림 관리 열기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>알림 관리</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>한 다이얼로그에 여러 액션이 공존할 때 위계로 강약을 조절한다.</p>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Button
variant="quaternary"
color="critical.text.base"
iconColor="critical.fill.base"
startIcon={<DeleteIcon />}
>
삭제
</Dialog.Button>
<Dialog.Button variant="tertiary" color="critical.text.base">
반려 사유
</Dialog.Button>
<Dialog.Button variant="primary">저장</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogDestructiveLowHierarchyExample;
SuccessActions
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogSuccessActionsExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">제출 완료 안내 열기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>제출이 완료되었습니다</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<p>이메일로 결과 안내를 보내드렸어요.</p>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="quaternary" color="positive.text.base">
나가기
</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary" bg="positive.fill.base">
확인
</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogSuccessActionsExample;
NestedDialogs
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { css } from '@mildang/styled-system/css';
const DialogNestedDialogsExample = () => (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open First Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>First Dialog</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div className={css({ display: 'flex', flexDirection: 'column', gap: '16px' })}>
<p>Click the button below to open a nested dialog.</p>
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Nested Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Nested Dialog</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<div>
<p>This is a nested dialog inside another dialog.</p>
<p>Nested content</p>
</div>
</Dialog.Body>
</Dialog.Content>
</Dialog>
</div>
</Dialog.Body>
</Dialog.Content>
</Dialog>
);
export default DialogNestedDialogsExample;
Responsive
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogResponsiveExample = () => (
<Dialog isResponsive>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>반응형 다이얼로그</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
데스크탑에서는 중앙 모달, 모바일에서는 아래에서 올라오는 바텀시트로 표시됩니다.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogResponsiveExample;
ResponsiveWithForm
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Grid } from '@mildang/styled-system/jsx';
import { Field } from '@mildang/design-system/Form';
import { FormLabel } from '@mildang/design-system/FormLabel';
import { Input } from '@mildang/design-system/Input';
const DialogResponsiveWithFormExample = () => (
<Dialog isResponsive>
<Dialog.Trigger asChild>
<Button variant="tertiary">프로필 수정</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>프로필 수정</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Grid gap="20">
<Field required>
<FormLabel htmlFor="rd-name">이름</FormLabel>
<Input fullWidth id="rd-name" defaultValue="John Doe" />
</Field>
<Field>
<FormLabel htmlFor="rd-username">닉네임</FormLabel>
<Input fullWidth id="rd-username" defaultValue="johndoe" />
</Field>
</Grid>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">저장</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogResponsiveWithFormExample;
ResponsiveCustomBreakpoint
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogResponsiveCustomBreakpointExample = () => (
<Dialog isResponsive breakpoint="sm">
<Dialog.Trigger asChild>
<Button variant="tertiary">Open</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>전환 기준 600px</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>600px 미만에서만 바텀시트로 전환됩니다.</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">닫기</Dialog.Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogResponsiveCustomBreakpointExample;
ResponsiveSize
import { css } from '@mildang/styled-system/css';
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogResponsiveSizeExample = () => (
<div className={css({ display: 'flex', gap: '16px', flexDirection: 'column', p: '32px' })}>
{(
[
['xs', '240px'],
['sm', '312px'],
['md', '600px (기본값)'],
['lg', '960px'],
['xl', '1140px'],
['full', '95vw (code-only)'],
] as const
).map(([size, desktopWidth]) => (
<Dialog key={size} size={size} isResponsive>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open {size}</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>size = {size}</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
데스크탑에서는 {desktopWidth}. 960px 미만에서는 이 prop 이 무시되고 다른 size 와 완전히 같은
시트로 뜹니다.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
))}
</div>
);
export default DialogResponsiveSizeExample;
ResponsiveEducoreButtonSize
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogResponsiveEducoreButtonSizeExample = () => (
<Dialog size="xs" isResponsive>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>educore 시트 버튼</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
모바일 폭에서 버튼 높이가 48(xl)이면 정상입니다. 36(md)이면 시트가 모달의 size 축을 다시 물려받은
것입니다.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogResponsiveEducoreButtonSizeExample;
NotDismissible
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
const DialogNotDismissibleExample = () => (
<Dialog isResponsive dismissible={false}>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>약관 동의</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Dialog.Description>
배경을 눌러도 닫히지 않습니다. 아래 버튼으로만 닫을 수 있어요.
</Dialog.Description>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="primary">동의합니다</Dialog.Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogNotDismissibleExample;
Default
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Text } from '@mildang/design-system/Text';
import { css } from '@mildang/styled-system/css';
import { splitCssProps } from '@mildang/styled-system/jsx';
function BlankBox(props: any) {
const { sx, ...other } = props;
const [cssProps] = splitCssProps(other);
const { css: _cssStyleProps, ...styleProps } = cssProps;
return (
<div className={css({ padding: '10', borderRadius: '4', backgroundColor: 'info.ghostBg.base', whiteSpace: 'pre-wrap', border: '1px dashed token(colors.info.border.highest)', color: 'info.text.base', w: '100%' }, styleProps, sx)}>
<Text className={css({ fontWeight: 'bold', fontSize: '10', lineHeight: '1' })}>Blank</Text>
<Text className={css({ fontSize: '10', lineHeight: '1.2', mt: '2', whiteSpace: 'pre-wrap' })}>{'Blank에는 어떤 아이템,' + '\n컴포넌트도 들어올 수 있습니다.'}</Text>
</div>
);
}
const DialogDefaultExample = () => {
return (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Modal Title</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<BlankBox />
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button>취소</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">확인</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
};
export default DialogDefaultExample;
ResponsiveLongContent
import { Dialog } from '@mildang/design-system/Dialog';
import { Button } from '@mildang/design-system/Button';
import { Grid } from '@mildang/styled-system/jsx';
import { Text } from '@mildang/design-system/Text';
import { css } from '@mildang/styled-system/css';
import { splitCssProps } from '@mildang/styled-system/jsx';
function BlankBox(props: any) {
const { sx, ...other } = props;
const [cssProps] = splitCssProps(other);
const { css: _cssStyleProps, ...styleProps } = cssProps;
return (
<div className={css({ padding: '10', borderRadius: '4', backgroundColor: 'info.ghostBg.base', whiteSpace: 'pre-wrap', border: '1px dashed token(colors.info.border.highest)', color: 'info.text.base', w: '100%' }, styleProps, sx)}>
<Text className={css({ fontWeight: 'bold', fontSize: '10', lineHeight: '1' })}>Blank</Text>
<Text className={css({ fontSize: '10', lineHeight: '1.2', mt: '2', whiteSpace: 'pre-wrap' })}>{'Blank에는 어떤 아이템,' + '\n컴포넌트도 들어올 수 있습니다.'}</Text>
</div>
);
}
const DialogResponsiveLongContentExample = () => (
<Dialog isResponsive>
<Dialog.Trigger asChild>
<Button variant="tertiary">약관 보기</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>이용약관</Dialog.Title>
<Dialog.CloseIconButton />
</Dialog.Header>
<Dialog.Body>
<Grid gap="16">
<Dialog.Description>
본 약관은 서비스 이용에 관한 조건과 절차, 이용자와 회사의 권리·의무를 규정합니다.
</Dialog.Description>
{Array.from({ length: 12 }, (_, index) => (
<BlankBox key={index} />
))}
</Grid>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild>
<Dialog.Button variant="tertiary">닫기</Dialog.Button>
</Dialog.Close>
<Dialog.Button variant="primary">동의</Dialog.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
export default DialogResponsiveLongContentExample;