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

breakpoint

"xs" | "sm" | "md" | "lg" | "xl"

md

dismissible

boolean

true

isResponsive

boolean

false

scrollType

"body" | "overlay"

body

size

"xs" | "sm" | "md" | "lg" | "xl" | "full"

md

같은 패밀리

@mildang/design-system/Dialog 에서 같이 내보내는 컴포넌트다.

폭 선택 — size

Figma dialog 정본의 폭 5단계다. 테마 공통이며 전부 maxWidth: calc(100vw - 48px) 로 캡된다 — Figma 모달 프레임의 좌우 padding 24다.

size너비용도
xs240px한 줄 확인. header/footer padding 도 한 단계 좁아진다
sm312px짧은 확인·간단 선택. 모바일 폭에 가까운 기본 확인창
md(기본)600px필드 몇 개짜리 폼, 목록 선택
lg960px2단 레이아웃, 표가 들어가는 편집 화면
xl1140px가장 넓은 단계. 이보다 커지면 Drawer·페이지를 검토한다
full95vw × 90vhFigma 에 없는 code-only escape hatch. 신규 사용은 지양한다

size 는 header/body/footer 의 padding, 타이틀 타이포그래피, Dialog.Button 의 기본 size까지 함께 결정한다 — 값은 테마(mildang/educore)별로 다르다.

size 는 모달 뷰 전용이다. isResponsive 로 바텀시트가 떠 있는 동안에는 전폭 1종으로 고정되고 이 prop 은 무시된다.

반응형 — 하나의 마크업, 두 개의 셸

isResponsive 를 켜면 마크업을 그대로 둔 채 좁은 화면에서 중앙 모달 대신 BottomSheet 가 올라온다(Figma _dialog_wrapperis_responsive 대응). 과거 별도 컴포넌트였던 ResponsiveDialog 는 이 prop 으로 흡수됐다.

Prop기본값설명
isResponsivefalsebreakpoint 미만에서 BottomSheet 로 전환한다. Figma 기본값은 true 지만, 기존 소비처의 모바일 동작이 한 번에 바뀌는 것을 막기 위해 코드 기본값은 false 다. 신규 화면은 명시적으로 켠다
breakpoint'md'전환 기준점. xs 0 / sm 600 / md 960 / lg 1280 / xl 1920(px)
dismissibletruefalse 면 scrim 클릭·ESC 로 닫히지 않는다. 모달·시트 양쪽에 동일하게 적용된다

시트로 전환되면 셸만 바뀐다 — 콘텐츠 스타일(header/title/body/footer)은 Dialog 가 계속 소유하고, BottomSheet 는 위치·상단 radius·overlay·드래그만 담당한다. 시트에서는 아래 모서리를 둥글리지 않고 footer 가 하단에 붙는다.

스크롤 — scrollType

scrollType동작
body(기본)content 높이를 85vh 로 캡하고 Body 만 스크롤한다. Header·Footer 는 항상 보인다
overlaycontent 는 콘텐츠 높이만큼 자라고 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 &gt; secondary &gt; tertiary &gt; 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;

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;