Dropdown 드롭다운
아래로 펼쳐지는 명령 메뉴입니다. 트리거는 어떤 요소든 될 수 있고, 메뉴 항목은 command로 식별하며 클릭 시 command 이벤트로 전달됩니다. WAI-ARIA menu 패턴으로 구현되었습니다: 방향키 순환, Home/End로 처음과 끝, Enter/Space로 실행, Esc로 닫고 초점을 돌려줍니다.
기본 사용법
click 트리거 + command 이벤트
최근 명령: —
vue
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || '추가 조작' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">새로 만들기</LhDropdownItem>
<LhDropdownItem command="edit">편집</LhDropdownItem>
<LhDropdownItem command="delete">삭제</LhDropdownItem>
</template>
</LhDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const lastCommand = ref('')
// 트리거 텍스트는 사용자가 관리합니다: command 이벤트는 명령 값만 전달하고, 라벨을 어떻게 표시할지는 외부가 결정합니다
const labelMap: Record<string, string> = { new: '새로 만들기', edit: '편집', delete: '삭제' }
const selectedLabel = ref('')
function onCommand(command: string | number | undefined) {
lastCommand.value = String(command)
selectedLabel.value = labelMap[String(command)] ?? String(command)
}
</script>vue
<script>
export default {
data() {
return {
lastCommand: '',
// 트리거 텍스트는 사용자가 관리합니다: command 이벤트는 명령 값만 전달하고, 라벨을 어떻게 표시할지는 외부가 결정합니다
labelMap: { new: '새로 만들기', edit: '편집', delete: '삭제' },
selectedLabel: '',
}
},
methods: {
onCommand(command) {
this.lastCommand = String(command)
this.selectedLabel = this.labelMap[String(command)] ?? String(command)
},
},
}
</script>
<template>
<LhDropdown @command="onCommand">
<LhButton>{{ selectedLabel || '추가 조작' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="new">새로 만들기</LhDropdownItem>
<LhDropdownItem command="edit">편집</LhDropdownItem>
<LhDropdownItem command="delete">삭제</LhDropdownItem>
</template>
</LhDropdown>
</template>트리거 방식
기본값은 click이며, 호버나 오른쪽 클릭으로 펼치도록 바꿀 수도 있습니다.
hover / contextmenu
vue
<script setup lang="ts">
import { ref } from 'vue'
const hoverLabel = ref('')
function onHoverCommand(command: string | number | undefined) {
hoverLabel.value = String(command)
}
const contextLabel = ref('')
function onContextCommand(command: string | number | undefined) {
contextLabel.value = String(command)
}
</script>
<template>
<!-- 호버 트리거 -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || '호버 트리거' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">옵션 1</LhDropdownItem>
<LhDropdownItem command="hover-2">옵션 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- 오른쪽 클릭 트리거 -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || '오른쪽 클릭 트리거' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">새로고침</LhDropdownItem>
<LhDropdownItem command="ctx-2">복사</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { hoverLabel: '', contextLabel: '' }
},
methods: {
onHoverCommand(command) {
this.hoverLabel = String(command)
},
onContextCommand(command) {
this.contextLabel = String(command)
},
},
}
</script>
<template>
<!-- 호버 트리거 -->
<LhDropdown trigger="hover" @command="onHoverCommand">
<LhButton>{{ hoverLabel || '호버 트리거' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="hover-1">옵션 1</LhDropdownItem>
<LhDropdownItem command="hover-2">옵션 2</LhDropdownItem>
</template>
</LhDropdown>
<!-- 오른쪽 클릭 트리거 -->
<LhDropdown trigger="contextmenu" @command="onContextCommand">
<LhButton>{{ contextLabel || '오른쪽 클릭 트리거' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="ctx-1">새로고침</LhDropdownItem>
<LhDropdownItem command="ctx-2">복사</LhDropdownItem>
</template>
</LhDropdown>
</template>
contextmenu트리거는 브라우저의 기본 컨텍스트 메뉴를 막습니다.
비활성화와 구분선
disabled / divided
vue
<script setup lang="ts">
import { ref } from 'vue'
const actionLabel = ref('')
function onActionCommand(command: string | number | undefined) {
actionLabel.value = String(command)
}
</script>
<template>
<!-- 비활성화 항목과 구분선이 있는 조작 메뉴 -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || '조작' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">내보내기</LhDropdownItem>
<LhDropdownItem command="import">가져오기</LhDropdownItem>
<LhDropdownItem divided command="audit">감사 로그</LhDropdownItem>
<LhDropdownItem disabled command="purge">데이터 비우기</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { actionLabel: '' }
},
methods: {
onActionCommand(command) {
this.actionLabel = String(command)
},
},
}
</script>
<template>
<!-- 비활성화 항목과 구분선이 있는 조작 메뉴 -->
<LhDropdown @command="onActionCommand">
<LhButton>{{ actionLabel || '조작' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="export">내보내기</LhDropdownItem>
<LhDropdownItem command="import">가져오기</LhDropdownItem>
<LhDropdownItem divided command="audit">감사 로그</LhDropdownItem>
<LhDropdownItem disabled command="purge">데이터 비우기</LhDropdownItem>
</template>
</LhDropdown>
</template>비활성화 항목도 여전히 포커스를 받을 수 있어(
aria-disabled="true"), 스크린 리더가 해당 항목을 읽을 수 있습니다. 단, 방향키는 이 항목을 건너뛰고, 클릭과 Enter/Space 모두command를 발생시키지 않습니다.
위치와 최대 높이
placement과 maxHeight
vue
<script setup lang="ts">
import { ref } from 'vue'
const itemLabel = ref('')
function onItemCommand(command: string | number | undefined) {
itemLabel.value = `${command}번 항목`
}
</script>
<template>
<!-- 하단 우측 정렬하고 최대 높이 제한 -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || '하단 우측 정렬' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">{{ i }}번 항목</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { itemLabel: '' }
},
methods: {
onItemCommand(command) {
this.itemLabel = `${command}번 항목`
},
},
}
</script>
<template>
<!-- 하단 우측 정렬하고 최대 높이 제한 -->
<LhDropdown placement="bottom-end" :max-height="160" @command="onItemCommand">
<LhButton>{{ itemLabel || '하단 우측 정렬' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem v-for="i in 12" :key="i" :command="i">{{ i }}번 항목</LhDropdownItem>
</template>
</LhDropdown>
</template>제어형 표시/숨김과 클릭 동작
v-model:visible으로 표시/숨김을 수동으로 제어합니다. hide-on-click은 선택 후 자동으로 접을지 여부를 제어하고, popper-class는 팝업 레이어에 커스텀 클래스를 추가하며, teleport-target은 마운트 컨테이너를 지정합니다.
제어형 표시/숨김
vue
<script setup lang="ts">
import { ref } from 'vue'
const menuVisible = ref(false)
</script>
<template>
<!-- 제어형 표시/숨김: 선택 후에도 접히지 않음 -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? '접기' : '펼치기' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">복사(선택 후에도 접히지 않음)</LhDropdownItem>
<LhDropdownItem command="paste">붙여넣기</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {
data() {
return { menuVisible: false }
},
}
</script>
<template>
<!-- 제어형 표시/숨김: 선택 후에도 접히지 않음 -->
<LhDropdown
v-model:visible="menuVisible"
:hide-on-click="false"
popper-class="demo-dropdown-popper"
>
<LhButton>{{ menuVisible ? '접기' : '펼치기' }} ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="copy">복사(선택 후에도 접히지 않음)</LhDropdownItem>
<LhDropdownItem command="paste">붙여넣기</LhDropdownItem>
</template>
</LhDropdown>
</template>지정한 컨테이너에 마운트
Teleport 컨테이너(#dropdown-zone)
vue
<script setup lang="ts">
</script>
<template>
<!-- 지정한 컨테이너에 마운트 -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>더 보기 ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">옵션</LhDropdownItem>
</template>
</LhDropdown>
</template>vue
<script>
export default {}
</script>
<template>
<!-- 지정한 컨테이너에 마운트 -->
<LhDropdown teleport-target="#dropdown-zone">
<LhButton>더 보기 ▾</LhButton>
<template #dropdown>
<LhDropdownItem command="a">옵션</LhDropdownItem>
</template>
</LhDropdown>
</template>Dropdown API
Props
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| trigger | 트리거 방식 | "hover" | "click" | "contextmenu" | 'click' |
| placement | 표시 위치(12가지, Tooltip과 동일) | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'bottom-start' |
| visible | 표시/숨김 상태(v-model:visible) | boolean | — |
| disabled | 트리거 비활성화 여부 | boolean | — |
| hideOnClick | 메뉴 항목 클릭 후 자동으로 닫을지 여부 | boolean | true |
| teleportTarget | Teleport 대상(선택자 또는 요소) | string | HTMLElement | 'body' |
| popperClass | 팝업 레이어 커스텀 클래스 이름 | string | — |
| maxHeight | 메뉴 최대 높이(숫자는 px) | string | number | — |
| themeVars | 컴포넌트 수준 테마 변수 | { [x: string]: string; } | — |
| bgPattern | 컴포넌트 수준 배경 문양, ConfigProvider 설정을 덮어씀 | string | — |
| sx | sx 빠른 레이아웃 속성(트리거 루트 요소에 적용) | LhSxProps | — |
Events
| 이벤트 | 설명 | 매개변수 |
|---|---|---|
| command | 메뉴 항목을 클릭할 때 발생 | (command: string | number | undefined) |
| click | 메뉴 항목(LhDropdownItem)이 클릭될 때 발생 | (command: string | number | undefined) |
| visible-change | 표시/숨김 변화 | (value: boolean) |
| update:visible | 표시/숨김 변화(v-model) | (value: boolean) |
Slots
| 슬롯 | 설명 |
|---|---|
| default | 트리거 내용 |
| dropdown | 메뉴 내용(LhDropdownItem 목록) |
Methods
| 메서드 | 설명 |
|---|---|
| show | 프로그래밍 방식 펼치기 |
| hide | 프로그래밍 방식 접기 |
| toggle | 프로그래밍 방식 전환 |
DropdownItem API
Props
| 속성 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| command | 명령 식별자, 클릭 시 command 이벤트와 함께 전달 | string | number | — |
| disabled | 비활성화 여부 | boolean | false |
| divided | 위쪽에 구분선을 표시할지 여부 | boolean | false |
접근성
- 트리거는
aria-haspopup="menu"와aria-expanded를 가지며, 메뉴는role="menu", 항목은role="menuitem"입니다 - 키보드는 APG menu 패턴을 따릅니다: ↑/↓ 순환(비활성화 항목은 건너뜀), Home/End로 처음과 끝, Enter/Space로
command실행 후 닫기, Esc로 닫고 초점을 돌려줍니다 - 비활성화 항목은 초점 트리에서 제거하는 대신
aria-disabled를 사용하며, 방향키와 Enter 모두 이 항목을 건너뜁니다 - 메뉴가 펼쳐졌을 때 주축 방향으로 넘치면 자동으로 방향을 뒤집고, 교차축으로 넘치면 뷰포트 안으로 밀어 넣습니다
타입 정의
이 컴포넌트가 외부로 내보내는 TypeScript 타입 구조는 다음과 같습니다(packages/components/dropdown/src/types.ts와 @lh-ui/share/core/* 소스에서 자동 추출, 각 항목을 펼쳐 전체 정의를 확인하세요. 속성 설명은 위쪽 Props 표를 참고하세요).
LhDropdownProps
ts
/**
* Dropdown 컴포넌트 Props
*/
export interface LhDropdownProps {
/** 트리거 방식 */
trigger?: LhDropdownTrigger
/** 표시 위치 */
placement?: LhDropdownPlacement
/** 수동으로 제어하는 표시 상태(v-model:visible) */
visible?: boolean
/** 비활성화 여부 */
disabled?: boolean
/** 메뉴 항목 클릭 후 자동으로 닫을지 여부 */
hideOnClick?: boolean
/** Teleport 대상(선택자 또는 요소, 기본값 body) */
teleportTarget?: string | HTMLElement
/** 팝업 레이어 커스텀 클래스 이름 */
popperClass?: string
/** 메뉴 최대 높이 */
maxHeight?: string | number
/** 컴포넌트 수준 테마 변수, ConfigProvider 설정을 덮어씀 */
themeVars?: Record<string, string>
/** 컴포넌트 수준 배경 문양, ConfigProvider 설정을 덮어씀 */
bgPattern?: string
/** sx 빠른 레이아웃 속성(트리거 루트 요소에 적용) */
sx?: LhSxProps
}LhDropdownItemProps
ts
/**
* DropdownItem 컴포넌트 Props
*/
export interface LhDropdownItemProps {
/** 명령 식별자, 클릭 시 command 이벤트와 함께 전달 */
command?: string | number
/** 비활성화 여부(접근성 트리에 들어갈 수 있도록 포커스 가능성 유지) */
disabled?: boolean
/** 구분선 표시 여부 */
divided?: boolean
}LhDropdownTrigger (@lh-ui/share/core/dropdown (logic.ts)에서 가져옴)
ts
/** 트리거 방식 */
export type LhDropdownTrigger = 'hover' | 'click' | 'contextmenu'