Skip to content
API 风格:

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>
지정한 컨테이너에 마운트
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>

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메뉴 항목 클릭 후 자동으로 닫을지 여부booleantrue
teleportTargetTeleport 대상(선택자 또는 요소)string | HTMLElement'body'
popperClass팝업 레이어 커스텀 클래스 이름string—
maxHeight메뉴 최대 높이(숫자는 px)string | number—
themeVars컴포넌트 수준 테마 변수{ [x: string]: string; }—
bgPattern컴포넌트 수준 배경 문양, ConfigProvider 설정을 덮어씀string—
sxsx 빠른 레이아웃 속성(트리거 루트 요소에 적용)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프로그래밍 방식 전환

Props ​

속성설명타입기본값
command명령 식별자, 클릭 시 command 이벤트와 함께 전달string | number—
disabled비활성화 여부booleanfalse
divided위쪽에 구분선을 표시할지 여부booleanfalse

접근성 ​

  • 트리거는 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'

국풍의 아름다움으로 코드의 토대를 세웁니다