Drawer 서랍
화면 가장자리에서 미끄러져 들어오는 측면 패널로, Dialog보다 가장자리에 가까운 일시적인 조작(상세, 필터, 마법사 단계)에 적합합니다. Dialog와 마찬가지로 role="dialog" + aria-modal="true"를 사용하며, 초점이 패널 안에 머물고 Esc와 오버레이 클릭으로 닫을 수 있습니다.
기본 사용법
오른쪽 서랍
vue
<template>
<LhButton @click="basic = true">서랍 열기</LhButton>
<LhDrawer v-model="basic" title="주문 상세">
<p>여기에 서랍 본문 내용을 넣습니다.</p>
</LhDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const basic = ref(false)
</script>vue
<script>
export default {
data() {
return { basic: false }
},
}
</script>
<template>
<LhButton @click="basic = true">서랍 열기</LhButton>
<LhDrawer v-model="basic" title="주문 상세">
<p>여기에 서랍 본문 내용을 넣습니다.</p>
</LhDrawer>
</template>네 방향
right / left / top / bottom
vue
<script setup lang="ts">
import { ref } from 'vue'
const dirVisible = ref(false)
const direction = ref<'right' | 'left' | 'top' | 'bottom'>('right')
const size = ref('40%')
function openDir(d: 'right' | 'left' | 'top' | 'bottom') {
direction.value = d
dirVisible.value = true
}
</script>
<template>
<!-- 오른쪽 서랍 열기 -->
<LhButton @click="openDir('right')">right</LhButton>
<!-- 왼쪽 서랍 열기 -->
<LhButton @click="openDir('left')">left</LhButton>
<!-- 상단 서랍 열기 -->
<LhButton @click="openDir('top')">top</LhButton>
<!-- 하단 서랍 열기 -->
<LhButton @click="openDir('bottom')">bottom</LhButton>
<!-- 지정한 방향으로 미끄러져 나옴 -->
<LhDrawer v-model="dirVisible" :title="`${direction} 서랍`" :direction="direction" :size="size">
<p>현재 방향: {{ direction }}</p>
</LhDrawer>
</template>vue
<script>
export default {
data() {
return { dirVisible: false, direction: 'right', size: '40%' }
},
methods: {
openDir(d) {
this.direction = d
this.dirVisible = true
},
},
}
</script>
<template>
<!-- 오른쪽 서랍 열기 -->
<LhButton @click="openDir('right')">right</LhButton>
<!-- 왼쪽 서랍 열기 -->
<LhButton @click="openDir('left')">left</LhButton>
<!-- 상단 서랍 열기 -->
<LhButton @click="openDir('top')">top</LhButton>
<!-- 하단 서랍 열기 -->
<LhButton @click="openDir('bottom')">bottom</LhButton>
<!-- 지정한 방향으로 미끄러져 나옴 -->
<LhDrawer v-model="dirVisible" :title="`${direction} 서랍`" :direction="direction" :size="size">
<p>현재 방향: {{ direction }}</p>
</LhDrawer>
</template>크기와 하단 커스터마이징
size와 footer 슬롯
vue
<script setup lang="ts">
import { ref } from 'vue'
const sized = ref(false)
</script>
<template>
<!-- 480px 너비 서랍 열기 -->
<LhButton @click="sized = true">480px 너비 서랍</LhButton>
<!-- 고정 크기 서랍, 하단은 조작 영역 -->
<LhDrawer v-model="sized" title="필터 조건" :size="480">
<LhInput placeholder="이름으로 필터" />
<LhInput placeholder="태그로 필터" />
<template #footer>
<LhButton @click="sized = false">취소</LhButton>
<LhButton type="primary" @click="sized = false">필터 적용</LhButton>
</template>
</LhDrawer>
</template>vue
<script>
export default {
data() {
return { sized: false }
},
}
</script>
<template>
<!-- 480px 너비 서랍 열기 -->
<LhButton @click="sized = true">480px 너비 서랍</LhButton>
<!-- 고정 크기 서랍, 하단은 조작 영역 -->
<LhDrawer v-model="sized" title="필터 조건" :size="480">
<LhInput placeholder="이름으로 필터" />
<LhInput placeholder="태그로 필터" />
<template #footer>
<LhButton @click="sized = false">취소</LhButton>
<LhButton type="primary" @click="sized = false">필터 적용</LhButton>
</template>
</LhDrawer>
</template>
size에 숫자를 전달하면 px이고, 문자열은 백분율(예:'40%')을 지원합니다. 좌우 방향은 너비에, 상하 방향은 높이에 적용됩니다.
동작 스위치
mask-closable로 오버레이 클릭 닫기를 금지, show-close로 오른쪽 위 닫기 버튼을 숨김, close-on-esc로 Esc 닫기를 비활성화, lock-scroll로 열렸을 때 페이지 스크롤을 잠그지 않음, teleport-target으로 마운트 컨테이너를 지정합니다.
동작 스위치
vue
<script setup lang="ts">
import { ref } from 'vue'
const adv1 = ref(false)
const adv2 = ref(false)
const adv3 = ref(false)
</script>
<template>
<!-- '버튼으로만 닫기' 서랍 열기 -->
<LhButton @click="adv1 = true">버튼으로만 닫기</LhButton>
<!-- '스크롤 잠금 안 함' 서랍 열기 -->
<LhButton @click="adv2 = true">스크롤 잠금 안 함</LhButton>
<!-- 오버레이 클릭과 Esc로는 닫히지 않음 -->
<LhDrawer
v-model="adv1"
title="버튼으로만 닫기"
:mask-closable="false"
:show-close="false"
:close-on-esc="false"
>
<p>오버레이 클릭과 Esc로는 닫히지 않고, 버튼으로만 닫을 수 있습니다.</p>
<template #footer>
<LhButton type="primary" @click="adv1 = false">닫기</LhButton>
</template>
</LhDrawer>
<!-- 열려 있을 때도 페이지를 스크롤할 수 있음 -->
<LhDrawer v-model="adv2" title="스크롤 잠금 안 함" :lock-scroll="false">
<p>서랍이 열려 있을 때도 페이지를 스크롤할 수 있습니다.</p>
</LhDrawer>
<!-- 지정한 컨테이너에 마운트 -->
<LhDrawer v-model="adv3" title="마운트 컨테이너 지정" teleport-target="#drawer-host">
<p>패널이 id가 drawer-host인 컨테이너 안으로 전송됩니다.</p>
</LhDrawer>
</template>vue
<script>
export default {
data() {
return { adv1: false, adv2: false, adv3: false }
},
}
</script>
<template>
<!-- '버튼으로만 닫기' 서랍 열기 -->
<LhButton @click="adv1 = true">버튼으로만 닫기</LhButton>
<!-- '스크롤 잠금 안 함' 서랍 열기 -->
<LhButton @click="adv2 = true">스크롤 잠금 안 함</LhButton>
<!-- 오버레이 클릭과 Esc로는 닫히지 않음 -->
<LhDrawer
v-model="adv1"
title="버튼으로만 닫기"
:mask-closable="false"
:show-close="false"
:close-on-esc="false"
>
<p>오버레이 클릭과 Esc로는 닫히지 않고, 버튼으로만 닫을 수 있습니다.</p>
<template #footer>
<LhButton type="primary" @click="adv1 = false">닫기</LhButton>
</template>
</LhDrawer>
<!-- 열려 있을 때도 페이지를 스크롤할 수 있음 -->
<LhDrawer v-model="adv2" title="스크롤 잠금 안 함" :lock-scroll="false">
<p>서랍이 열려 있을 때도 페이지를 스크롤할 수 있습니다.</p>
</LhDrawer>
<!-- 지정한 컨테이너에 마운트 -->
<LhDrawer v-model="adv3" title="마운트 컨테이너 지정" teleport-target="#drawer-host">
<p>패널이 id가 drawer-host인 컨테이너 안으로 전송됩니다.</p>
</LhDrawer>
</template>Drawer API
Props
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| modelValue | 표시 여부(v-model) | boolean | — |
| title | 제목(header 슬롯으로도 사용 가능) | string | — |
| direction | 방향 | "right" | "left" | "top" | "bottom" | 'right' |
| size | 크기: 숫자는 px, 문자열은 백분율 지원 | string | number | '30%' |
| maskClosable | 오버레이 클릭 시 닫기 여부 | boolean | true |
| showClose | 닫기 버튼 표시 여부 | boolean | true |
| teleportTarget | Teleport 대상(선택자 또는 요소, 기본값 body) | string | HTMLElement | 'body' |
| lockScroll | 페이지 스크롤 잠금 여부 | boolean | true |
| closeOnEsc | Esc 닫기 응답 여부 | boolean | true |
| themeVars | 컴포넌트 수준 테마 변수, ConfigProvider 설정을 덮어씀 | { [x: string]: string; } | — |
| bgPattern | 컴포넌트 수준 배경 문양, ConfigProvider 설정을 덮어씀 | string | — |
| sx | sx 빠른 레이아웃 속성(패널에 적용) | LhSxProps | — |
Events
| 이벤트 | 설명 | 매개변수 |
|---|---|---|
| update:model-value | 표시/숨김 변화 | (value: boolean) |
| open | 열린 후 발생 | — |
| close | 닫힌 후 발생 | — |
Slots
| 슬롯 | 설명 |
|---|---|
| default | 본문 내용 |
| header | 커스텀 머리글(제목 영역 포함) |
| footer | 커스텀 하단 조작 영역 |
Methods
| 메서드 | 설명 |
|---|---|
| open | 프로그래밍 방식 열기 |
| close | 프로그래밍 방식 닫기 |
접근성
role="dialog"+aria-modal="true". 제목이 있으면aria-labelledby로 연결하고, 없으면 언어 팩drawer.title을 aria-label로 사용합니다- 열릴 때 초점이 패널의 첫 번째 포커스 가능 요소로 이동하며, Tab은 패널 안에서 순환하고 닫힌 후 트리거로 초점을 돌려줍니다
- Esc로 닫기(
close-on-esc로 비활성화 가능). 오버레이는 장식용(aria-hidden)입니다 - 닫기 버튼은 실제
<button>이며, aria-label은 언어 팩drawer.close를 사용합니다 - 미끄러짐 애니메이션은
prefers-reduced-motion에서 페이드 인/아웃으로 낮아집니다
타입 정의
이 컴포넌트가 외부로 내보내는 TypeScript 타입 구조는 다음과 같습니다(packages/components/drawer/src/types.ts와 @lh-ui/share/core/* 소스에서 자동 추출, 각 항목을 펼쳐 전체 정의를 확인하세요. 속성 설명은 위쪽 Props 표를 참고하세요).
LhDrawerProps
ts
/**
* Drawer 컴포넌트 Props
*/
export interface LhDrawerProps {
/** 표시 여부(v-model) */
modelValue?: boolean
/** 제목 */
title?: string
/** 방향 */
direction?: LhDrawerDirection
/** 크기: 숫자는 px, 문자열은 백분율 지원 */
size?: string | number
/** 오버레이 클릭 시 닫기 여부 */
maskClosable?: boolean
/** 닫기 버튼 표시 여부 */
showClose?: boolean
/** Teleport 대상(선택자 또는 요소, 기본값 body) */
teleportTarget?: string | HTMLElement
/** 페이지 스크롤 잠금 여부 */
lockScroll?: boolean
/** Esc 닫기 응답 여부 */
closeOnEsc?: boolean
/** 컴포넌트 수준 테마 변수, ConfigProvider 설정을 덮어씀 */
themeVars?: Record<string, string>
/** 컴포넌트 수준 배경 문양, ConfigProvider 설정을 덮어씀 */
bgPattern?: string
/** sx 빠른 레이아웃 속성(패널에 적용) */
sx?: LhSxProps
}LhDrawerDirection (@lh-ui/share/core/drawer (logic.ts)에서 가져옴)
ts
/**
* Drawer 순수 함수 로직(프레임워크 의존 없음)
*/
/** 서랍 방향 */
export type LhDrawerDirection = 'right' | 'left' | 'top' | 'bottom'