Drawer
하단에서 올라오는 모바일 최적화 Drawer 컴포넌트입니다. 드래그로 닫기, ESC 키, 외부 클릭 등 다양한 닫기 옵션을 지원하며, 높이와 너비를 자유롭게 설정할 수 있습니다. Portal을 사용하여 DOM 트리의 최상단에 렌더링됩니다.
미리보기
사용법
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function PreviewExample() {
6 return (
7 <Drawer.Root>
8 <Drawer.Trigger asChild>
9 <Button variant="primary">Open Drawer</Button>
10 </Drawer.Trigger>
11 <Drawer.Portal>
12 <Drawer.Overlay />
13 <Drawer.Content>
14 <Drawer.Handle />
15 <Drawer.Title>Drawer 제목</Drawer.Title>
16 <Drawer.Body>
17 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
18 Drawer 컴포넌트입니다. 아래로 드래그하거나, ESC 키를 누르거나, 외부를 클릭하여 닫을 수 있습니다.
19 </p>
20 </Drawer.Body>
21 </Drawer.Content>
22 </Drawer.Portal>
23 </Drawer.Root>
24 );
25}API 레퍼런스
Drawer.Root
Drawer의 상태를 관리하는 최상위 컨텍스트 프로바이더
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
open | boolean | - | Drawer 열림/닫힘 상태 (제어 모드) |
defaultOpen | boolean | false | Drawer 기본 열림 상태 (비제어 모드) |
onOpenChange | (open: boolean) => void | - | 상태가 변경될 때 실행되는 함수 |
closeOnBackdrop | boolean | object | true | 백드롭 인터랙션으로 닫기 허용 (boolean 또는 escapeKey, clickOutside 속성을 가진 객체) |
closeOnDrag | boolean | true | 드래그로 닫기 허용 |
maxHeight | string | "70dvh" | Drawer 최대 높이 |
width | string | "480px" | Drawer 너비 (데스크톱) |
maxWidth | string | - | Drawer 최대 컨테이너 너비 (데스크톱 전용) |
zIndex | number | 9999 | z-index 값 |
Drawer.Trigger
Drawer를 여는 트리거 버튼
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
asChild | boolean | false | 래퍼 없이 자식 요소만 렌더링 |
기본 <code>button</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Portal
Drawer를 document.body나 지정된 컨테이너에 렌더링
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
container | HTMLElement | document.body | Drawer를 렌더링할 DOM 컨테이너 |
Drawer.Overlay
Drawer 배경 오버레이
기본 <code>div</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Content
Drawer 메인 컨테이너
기본 <code>div</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Handle
드래그 가능한 핸들 영역
기본 <code>div</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Title
Drawer 제목
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
asChild | boolean | false | 래퍼 없이 자식 요소만 렌더링 |
기본 <code>h2</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Body
스크롤 가능한 콘텐츠 영역
기본 <code>div</code> HTML 속성을 모두 사용할 수 있습니다.
Drawer.Close
Drawer를 닫는 버튼
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
asChild | boolean | false | 래퍼 없이 자식 요소만 렌더링 |
기본 <code>button</code> HTML 속성을 모두 사용할 수 있습니다.
예제
ESC 키로만 닫기
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function EscKeyOnlyExample() {
6 return (
7 <Drawer.Root closeOnBackdrop={{ escapeKey: true, clickOutside: false }} closeOnDrag={false}>
8 <Drawer.Trigger asChild>
9 <Button variant="secondary" size="small">
10 ESC Key Only
11 </Button>
12 </Drawer.Trigger>
13 <Drawer.Portal>
14 <Drawer.Overlay />
15 <Drawer.Content>
16 <Drawer.Handle />
17 <Drawer.Title>ESC Key Only</Drawer.Title>
18 <Drawer.Body>
19 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
20 이 Drawer는 ESC 키로만 닫을 수 있습니다. 외부 클릭은 작동하지 않습니다.
21 </p>
22 </Drawer.Body>
23 </Drawer.Content>
24 </Drawer.Portal>
25 </Drawer.Root>
26 );
27}외부 클릭으로만 닫기
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function ClickOutsideOnlyExample() {
6 return (
7 <Drawer.Root closeOnBackdrop={{ escapeKey: false, clickOutside: true }} closeOnDrag={false}>
8 <Drawer.Trigger asChild>
9 <Button variant="secondary" size="small">
10 Click Outside Only
11 </Button>
12 </Drawer.Trigger>
13 <Drawer.Portal>
14 <Drawer.Overlay />
15 <Drawer.Content>
16 <Drawer.Handle />
17 <Drawer.Title>Click Outside Only</Drawer.Title>
18 <Drawer.Body>
19 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
20 이 Drawer는 외부 클릭으로만 닫을 수 있습니다. ESC 키는 작동하지 않습니다.
21 </p>
22 </Drawer.Body>
23 </Drawer.Content>
24 </Drawer.Portal>
25 </Drawer.Root>
26 );
27}백드롭으로 닫기 비활성화
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function NoBackdropCloseExample() {
6 return (
7 <Drawer.Root closeOnBackdrop={false}>
8 <Drawer.Trigger asChild>
9 <Button variant="secondary" size="small">
10 No Backdrop Close
11 </Button>
12 </Drawer.Trigger>
13 <Drawer.Portal>
14 <Drawer.Overlay />
15 <Drawer.Content>
16 <Drawer.Handle />
17 <Drawer.Title>No Backdrop Close</Drawer.Title>
18 <Drawer.Body>
19 <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
20 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
21 이 Drawer는 드래그로만 닫을 수 있습니다. ESC 키와 외부 클릭은 비활성화되어 있습니다.
22 </p>
23 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6, fontSize: "14px" }}>
24 핸들을 아래로 드래그하여 닫아보세요.
25 </p>
26 </div>
27 </Drawer.Body>
28 </Drawer.Content>
29 </Drawer.Portal>
30 </Drawer.Root>
31 );
32}큰 높이
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function LargeHeightExample() {
6 return (
7 <Drawer.Root maxHeight="90dvh">
8 <Drawer.Trigger asChild>
9 <Button variant="secondary" size="small">
10 Large (90dvh)
11 </Button>
12 </Drawer.Trigger>
13 <Drawer.Portal>
14 <Drawer.Overlay />
15 <Drawer.Content>
16 <Drawer.Handle />
17 <Drawer.Title>Large Drawer</Drawer.Title>
18 <Drawer.Body>
19 <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
20 <section>
21 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
22 최대 높이
23 </h3>
24 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
25 이 Drawer는 최대 높이가 더 큽니다 (90dvh). 뷰포트의 대부분을 차지할 수 있어 콘텐츠가 많은 섹션에 유용합니다.
26 </p>
27 </section>
28
29 <section>
30 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
31 스크롤 가능한 콘텐츠
32 </h3>
33 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
34 콘텐츠가 최대 높이를 초과하면 본문 영역이 스크롤 가능해집니다. 핸들은 상단에 고정되어 쉽게 닫을 수 있습니다.
35 </p>
36 </section>
37
38 <section>
39 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
40 사용 사례
41 </h3>
42 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
43 큰 Drawer는 상세한 폼, 긴 목록, 설정 패널 또는 더 많은 수직 공간이 필요한 콘텐츠에 이상적입니다.
44 </p>
45 </section>
46
47 <section>
48 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
49 반응형 디자인
50 </h3>
51 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
52 Drawer는 설정한 최대 높이 제약을 준수하면서 다양한 화면 크기에 자동으로 적응합니다.
53 </p>
54 </section>
55
56 <section>
57 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
58 접근성
59 </h3>
60 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
61 큰 Drawer는 키보드 내비게이션, 스크린 리더 지원, 적절한 포커스 관리를 포함한 모든 접근성 기능을 유지합니다.
62 </p>
63 </section>
64
65 <section>
66 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
67 추가 콘텐츠
68 </h3>
69 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
70 필요한 만큼 콘텐츠를 추가할 수 있습니다. Drawer는 핸들 영역에서 드래그로 닫기 기능을 유지하면서 부드럽게 스크롤됩니다.
71 </p>
72 </section>
73 </div>
74 </Drawer.Body>
75 </Drawer.Content>
76 </Drawer.Portal>
77 </Drawer.Root>
78 );
79}커스텀 너비
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function CustomWidthExample() {
6 return (
7 <Drawer.Root width="600px">
8 <Drawer.Trigger asChild>
9 <Button variant="secondary" size="small">
10 Wide (600px)
11 </Button>
12 </Drawer.Trigger>
13 <Drawer.Portal>
14 <Drawer.Overlay />
15 <Drawer.Content>
16 <Drawer.Handle />
17 <Drawer.Title>Wide Drawer</Drawer.Title>
18 <Drawer.Body>
19 <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
20 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
21 이 Drawer는 데스크톱에서 사용자 정의 너비(600px)를 가지며, 폼이나 나란히 배치된 레이아웃과 같은 콘텐츠에 더 많은 가로 공간을 제공합니다.
22 </p>
23 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
24 모바일 기기에서는 자동으로 전체 너비로 조정되어 작은 화면에서 최적의 사용성을 보장합니다.
25 </p>
26 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
27 이러한 유연성은 모든 기기 크기에서 작동해야 하는 반응형 디자인에 완벽합니다.
28 </p>
29 </div>
30 </Drawer.Body>
31 </Drawer.Content>
32 </Drawer.Portal>
33 </Drawer.Root>
34 );
35}닫기 버튼 사용
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function WithCloseButtonExample() {
6 return (
7 <Drawer.Root>
8 <Drawer.Trigger asChild>
9 <Button variant="secondary">With Close Button</Button>
10 </Drawer.Trigger>
11 <Drawer.Portal>
12 <Drawer.Overlay />
13 <Drawer.Content>
14 <Drawer.Handle />
15 <Drawer.Title>Confirm Action</Drawer.Title>
16 <Drawer.Body>
17 <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
18 <p style={{ margin: 0, color: "#6b7280", lineHeight: 1.6 }}>
19 이 작업을 계속하시겠습니까? 이 작업은 취소할 수 없습니다.
20 </p>
21 <div style={{ display: "flex", gap: "8px" }}>
22 <Drawer.Close asChild>
23 <Button variant="primary">확인</Button>
24 </Drawer.Close>
25 <Drawer.Close asChild>
26 <Button variant="ghost">취소</Button>
27 </Drawer.Close>
28 </div>
29 </div>
30 </Drawer.Body>
31 </Drawer.Content>
32 </Drawer.Portal>
33 </Drawer.Root>
34 );
35}긴 스크롤 콘텐츠
1"use client";
2
3import { Button, Drawer } from "motile-ui";
4
5export default function LongContentExample() {
6 return (
7 <Drawer.Root>
8 <Drawer.Trigger asChild>
9 <Button variant="secondary">Long Scrollable Content</Button>
10 </Drawer.Trigger>
11 <Drawer.Portal>
12 <Drawer.Overlay />
13 <Drawer.Content>
14 <Drawer.Handle />
15 <Drawer.Title>Terms and Conditions</Drawer.Title>
16 <Drawer.Body>
17 <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
18 <section>
19 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
20 1. 소개
21 </h3>
22 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
23 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
24 </p>
25 </section>
26
27 <section>
28 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
29 2. 사용자 책임
30 </h3>
31 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
32 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
33 </p>
34 </section>
35
36 <section>
37 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
38 3. 개인정보 보호정책
39 </h3>
40 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
41 Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
42 </p>
43 </section>
44
45 <section>
46 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
47 4. 데이터 수집
48 </h3>
49 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
50 Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
51 </p>
52 </section>
53
54 <section>
55 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
56 5. 쿠키
57 </h3>
58 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
59 Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
60 </p>
61 </section>
62
63 <section>
64 <h3 style={{ margin: "0 0 8px", fontSize: "14px", fontWeight: 600 }}>
65 6. 해지
66 </h3>
67 <p style={{ margin: 0, color: "#6b7280", fontSize: "14px", lineHeight: 1.6 }}>
68 Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur.
69 </p>
70 </section>
71
72 <div style={{ paddingTop: "8px" }}>
73 <Drawer.Close asChild>
74 <Button variant="primary" size="large">
75 동의합니다
76 </Button>
77 </Drawer.Close>
78 </div>
79 </div>
80 </Drawer.Body>
81 </Drawer.Content>
82 </Drawer.Portal>
83 </Drawer.Root>
84 );
85}