Components

Modal

사용 예시

일정 추가

창 폭이 480px 이상이면 가운데에, 좁으면 아래에서 열려요. 푸터 버튼은 Dialog 모양에서는 오른쪽에 모이고, BottomSheet 모양에서는 같은 폭으로 나뉘어요.

tsx
<Modal>  <ModalTrigger render={<Button color="brand" emphasis="solid" />}>일정 추가</ModalTrigger>  <ModalContent>    <ModalHeader>      <ModalTitle>일정 추가</ModalTitle>      <ModalDescription>저장하면 캘린더에 바로 보여요.</ModalDescription>    </ModalHeader>    <ModalBody className="flex flex-col gap-stack">      <Field>        <FieldLabel>제목</FieldLabel>        <Input placeholder="주간 회의" />      </Field>      <Field>        <FieldLabel>날짜</FieldLabel>        <Input type="date" />      </Field>      <Field>        <FieldLabel>메모</FieldLabel>        <Textarea rows={3} placeholder="회의실, 준비물" />      </Field>    </ModalBody>    <ModalFooter>      <ModalClose render={<Button color="neutral" emphasis="outline" />}>그대로 두기</ModalClose>      <ModalClose render={<Button color="brand" emphasis="solid" />}>저장</ModalClose>    </ModalFooter>  </ModalContent></Modal>

모양에 따라 내용 바꾸기

useModalShape()로 지금 모양을 알 수 있어요. Dialog 모양에서는 헤더에 단계를 글자로, BottomSheet 모양에서는 본문 위에 Progress로 보여요. 화면 폭 조건을 따로 적지 않아도 Modal과 같은 기준을 써요.

tsx
function StepTrail() {  return useModalShape() === 'dialog' ? <span className="text-caption text-fg-subtle">1/4 단계</span> : null}function StepBar() {  if (useModalShape() !== 'sheet') return null  return <Progress value={25}><ProgressLabel>1/4 단계</ProgressLabel></Progress>}<Modal>  <ModalTrigger render={<Button color="neutral" emphasis="outline" />}>업무 추가</ModalTrigger>  <ModalContent>    <ModalHeader closable trail={<StepTrail />}>      <ModalTitle>업무 추가</ModalTitle>    </ModalHeader>    <ModalBody className="flex flex-col gap-stack">      <StepBar />      <Field>        <FieldLabel>업무 이름</FieldLabel>        <Input placeholder="출시 공지 작성" />      </Field>    </ModalBody>    <ModalFooter>      <Button color="brand" emphasis="solid">다음</Button>    </ModalFooter>  </ModalContent></Modal>

수정

저장처럼 누른 뒤 값을 반영하고 닫아야 하면 open을 넘기고 호출하는 쪽에서 닫아요. 열 때 입력값을 지금 이름으로 되돌려서 그대로 두기로 닫은 내용이 남지 않게 해요.

프로젝트: 신제품 출시

tsx
<Modal open={open} onOpenChange={change}>  <ModalTrigger render={<Button color="neutral" emphasis="outline" />}>이름 바꾸기</ModalTrigger>  <ModalContent size="sm">    <ModalHeader>      <ModalTitle>프로젝트 이름 바꾸기</ModalTitle>    </ModalHeader>    <Field>      <FieldLabel>이름</FieldLabel>      <Input value={draft} onChange={e => setDraft(e.target.value)} />    </Field>    <ModalFooter>      <ModalClose render={<Button color="neutral" emphasis="outline" />}>그대로 두기</ModalClose>      <Button color="brand" emphasis="solid" onClick={rename}>저장</Button>    </ModalFooter>  </ModalContent></Modal><p className="text-caption text-fg-muted">프로젝트: {name}</p>

한쪽 모양으로 고정

as를 dialog나 sheet로 주면 화면 폭과 상관없이 그 모양으로 열려요. 한 화면에서 같은 창이 늘 같은 모양이어야 할 때 써요.

tsx
<Modal as="dialog">  <ModalTrigger render={<Button color="neutral" emphasis="outline" />}>단축키</ModalTrigger>  <ModalContent>    <ModalHeader>      <ModalTitle>단축키</ModalTitle>      <ModalDescription>키보드를 쓰는 넓은 화면에서 주로 열어요.</ModalDescription>    </ModalHeader>    <ModalFooter>      <ModalClose render={<Button color="neutral" emphasis="outline" />}>닫기</ModalClose>    </ModalFooter>  </ModalContent></Modal>

기준 폭 바꾸기

size lg처럼 넓은 창은 breakpoint를 md로 올려서 768px 아래에서는 BottomSheet로 열어요.

tsx
<Modal breakpoint="md">  <ModalTrigger render={<Button color="neutral" emphasis="outline" />}>공지 작성</ModalTrigger>  <ModalContent size="lg">    <ModalHeader>      <ModalTitle>공지 작성</ModalTitle>    </ModalHeader>    <ModalBody className="flex flex-col gap-stack">      <Field>        <FieldLabel>제목</FieldLabel>        <Input placeholder="10월 휴무 안내" />      </Field>      <Field>        <FieldLabel>본문</FieldLabel>        <Textarea rows={6} placeholder="알릴 내용을 입력하세요" />      </Field>    </ModalBody>    <ModalFooter>      <ModalClose render={<Button color="neutral" emphasis="outline" />}>그대로 두기</ModalClose>      <ModalClose render={<Button color="brand" emphasis="solid" />}>게시</ModalClose>    </ModalFooter>  </ModalContent></Modal>

컨트롤

tsx
<Modal>  <ModalTrigger render={<Button color="neutral" emphasis="outline" />}>할 일 추가</ModalTrigger>  <ModalContent>    <ModalHeader>      <ModalTitle>할 일 추가</ModalTitle>      <ModalDescription>담당자에게 알림이 가요.</ModalDescription>    </ModalHeader>    <ModalFooter>      <ModalClose render={<Button color="neutral" emphasis="outline" />}>그대로 두기</ModalClose>      <ModalClose render={<Button color="brand" emphasis="solid" />}>추가</ModalClose>    </ModalFooter>  </ModalContent></Modal>

명세

이름
이름값기본
Modal
openboolean-
호출하는 쪽에서 열림 상태를 관리해요. 열린 상태로 breakpoint를 넘으면 모양만 바뀌고 계속 열려 있어요. 이때 안쪽을 다시 그려서 저장하지 않은 입력값은 없어져요.
defaultOpenbooleanfalse
처음에 열어 둘지 정해요.
onOpenChange(open: boolean, eventDetails) => void-
열리거나 닫히면 호출돼요. BottomSheet 모양에서 스와이프로 닫으면 eventDetails.reason이 swipe예요.
breakpointxs, sm, mdxs
이 폭 이상이면 Dialog, 아래면 BottomSheet로 열려요. xs는 480px, sm은 640px, md는 768px이에요.
asauto, dialog, sheetauto
auto는 breakpoint로 모양을 고르고, dialog와 sheet는 화면 폭과 상관없이 그 모양으로 열어요.
renderReactElement-
기본 요소 대신 그릴 요소예요. ModalTrigger와 ModalClose에 Button을 넘길 때 써요.
classNamestring-
Modal을 뺀 모든 하위 컴포넌트에 쓸 수 있어요.
ModalContent
sizesm, md, lgmd
Dialog 모양일 때의 최대 너비예요. sm은 448px, md는 480px, lg는 640px이에요. BottomSheet 모양에서는 쓰지 않아요.
ModalHeader
leadReactNode-
제목 앞에 보여요.
trailReactNode-
헤더 오른쪽 끝에 보여요.
closablebooleanfalse
헤더 오른쪽 끝에 닫기 버튼을 보여요. trail이 있으면 trail 다음에 와요.
closeLabelstring닫기
닫기 버튼의 접근성 이름이에요.
ModalBody
childrenReactNode-
본문이에요. 길면 헤더와 ModalFooter는 그대로 두고 이 안에서만 스크롤돼요.
ModalFooter
layoutend, between, fill, grow-last, { narrow, wide }모양에 따라
Dialog 모양이면 end로 오른쪽에 모으고, BottomSheet 모양이면 fill로 버튼이 같은 폭으로 나뉘어요. 값 하나를 넘기면 모양과 상관없이 그 배치를 쓰고, { narrow, wide }를 넘기면 Dialog 모양에서는 wide, BottomSheet 모양에서는 narrow를 써요.
useModalShape
반환값'dialog', 'sheet', null-
Modal 안에서 부르면 지금 모양을, 밖에서 부르면 null을 돌려줘요. breakpoint와 as가 정한 모양과 항상 같아요.