Menu 내비게이션 메뉴
페이지 내비게이션 컴포넌트. 가로/세로 레이아웃, 중첩 하위 메뉴, 접기 상태, 아코디언을 지원하며 키보드 조작은 APG menu pattern에 맞춥니다.
세로 레이아웃(인라인 하위 메뉴)
세로 인라인 모드는 키보드 탐색을 지원합니다: 하위 메뉴를 펼친 후 ↑/↓로 '리프 항목 ↔ 하위 메뉴 제목 ↔ 패널 내 항목' 경계를 넘어 이동할 수 있으며(Home/End는 양끝으로), 비활성화 항목과 접혀 있는 패널 내 항목은 자동으로 건너뜁니다.
세로 레이아웃
- 홈
- 설정▾
- 기본 설정
- 보안 설정
- 비활성화 항목
vue
<script setup>
import { ref } from 'vue'
const active = ref('home')
const active2 = ref('settings2-basic')
</script>
<template>
<!-- 세로 레이아웃 -->
<LhMenu v-model="active">
<LhMenuItem index="home">홈</LhMenuItem>
<LhSubMenu index="settings" title="설정">
<LhMenuItem index="settings-basic">기본 설정</LhMenuItem>
<LhMenuItem index="settings-security">보안 설정</LhMenuItem>
</LhSubMenu>
<LhMenuItem index="docs" disabled>비활성화 항목</LhMenuItem>
</LhMenu>
</template>vue
<script>
export default {
data() {
return {
active: 'home',
active2: 'settings2-basic',
}
},
}
</script>
<template>
<!-- 세로 레이아웃 -->
<LhMenu v-model="active">
<LhMenuItem index="home">홈</LhMenuItem>
<LhSubMenu index="settings" title="설정">
<LhMenuItem index="settings-basic">기본 설정</LhMenuItem>
<LhMenuItem index="settings-security">보안 설정</LhMenuItem>
</LhSubMenu>
<LhMenuItem index="docs" disabled>비활성화 항목</LhMenuItem>
</LhMenu>
</template>펼침 제어
default-openeds는 처음에 펼쳐지는 하위 메뉴, openeds(v-model:openeds와 함께)는 제어되는 펼침 집합, unique-opened는 동시에 하나의 하위 메뉴만 펼치도록 합니다. LhSubMenu도 disabled로 전체를 비활성화할 수 있습니다.
펼침 제어
- 설정(기본 펼침)▾
- 기본 설정
- 보안 설정
- 관리(비활성화)▾
- 사용자 관리
- 정보
vue
<script setup>
import { ref } from 'vue'
const active2 = ref('settings2-basic')
</script>
<template>
<!-- 펼침 제어 -->
<LhMenu
v-model="active2"
:default-openeds="['settings2']"
unique-opened
style="max-width: 240px"
>
<LhSubMenu index="settings2" title="설정(기본 펼침)">
<LhMenuItem index="settings2-basic">기본 설정</LhMenuItem>
<LhMenuItem index="settings2-security">보안 설정</LhMenuItem>
</LhSubMenu>
<LhSubMenu index="manage" title="관리(비활성화)" disabled>
<LhMenuItem index="manage-users">사용자 관리</LhMenuItem>
</LhSubMenu>
<LhMenuItem index="about2">정보</LhMenuItem>
</LhMenu>
</template>vue
<script>
export default {
data() {
return { active2: 'settings2-basic' }
},
}
</script>
<template>
<!-- 펼침 제어 -->
<LhMenu
v-model="active2"
:default-openeds="['settings2']"
unique-opened
style="max-width: 240px"
>
<LhSubMenu index="settings2" title="설정(기본 펼침)">
<LhMenuItem index="settings2-basic">기본 설정</LhMenuItem>
<LhMenuItem index="settings2-security">보안 설정</LhMenuItem>
</LhSubMenu>
<LhSubMenu index="manage" title="관리(비활성화)" disabled>
<LhMenuItem index="manage-users">사용자 관리</LhMenuItem>
</LhSubMenu>
<LhMenuItem index="about2">정보</LhMenuItem>
</LhMenu>
</template>제어되는 펼침 집합
- 설정▾
- 기본 설정
vue
<script setup lang="ts">
import { ref } from 'vue'
const active2 = ref('settings2-basic')
</script>
<template>
<!-- 제어되는 펼침 집합 -->
<LhMenu v-model="active2" :openeds="['settings2']">
<LhSubMenu index="settings2" title="설정">
<LhMenuItem index="settings2-basic">기본 설정</LhMenuItem>
</LhSubMenu>
</LhMenu>
</template>vue
<script>
export default {
data() {
return { active2: 'settings2-basic' }
},
}
</script>
<template>
<!-- 제어되는 펼침 집합 -->
<LhMenu v-model="active2" :openeds="['settings2']">
<LhSubMenu index="settings2" title="설정">
<LhMenuItem index="settings2-basic">기본 설정</LhMenuItem>
</LhSubMenu>
</LhMenu>
</template>가로 레이아웃(플라이아웃 하위 메뉴)
가로 레이아웃
- 홈
- 설정▾
vue
<script setup lang="ts">
import { ref } from 'vue'
const active = ref('home')
</script>
<template>
<!-- 가로 레이아웃 -->
<LhMenu v-model="active" mode="horizontal">
<LhMenuItem index="home">홈</LhMenuItem>
<LhSubMenu index="settings" title="설정">
<LhMenuItem index="settings-basic">기본 설정</LhMenuItem>
</LhSubMenu>
</LhMenu>
</template>vue
<script>
export default {
data() {
return { active: 'home' }
},
}
</script>
<template>
<!-- 가로 레이아웃 -->
<LhMenu v-model="active" mode="horizontal">
<LhMenuItem index="home">홈</LhMenuItem>
<LhSubMenu index="settings" title="설정">
<LhMenuItem index="settings-basic">기본 설정</LhMenuItem>
</LhSubMenu>
</LhMenu>
</template>접기 상태와 그룹
접기 + 그룹
- 콘텐츠
- 홈
vue
<script setup lang="ts">
import { ref } from 'vue'
const active = ref('home')
</script>
<template>
<!-- 접기 + 그룹 -->
<LhMenu v-model="active" collapse>
<LhMenuItemGroup title="콘텐츠">
<LhMenuItem index="home">홈</LhMenuItem>
</LhMenuItemGroup>
</LhMenu>
</template>vue
<script>
export default {
data() {
return { active: 'home' }
},
}
</script>
<template>
<!-- 접기 + 그룹 -->
<LhMenu v-model="active" collapse>
<LhMenuItemGroup title="콘텐츠">
<LhMenuItem index="home">홈</LhMenuItem>
</LhMenuItemGroup>
</LhMenu>
</template>API
LhMenu Props
| 프로퍼티 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| modelValue | 현재 활성 항목(제어되는 v-model; 넘기지 않으면 내부에서 관리) | string | — |
| mode | 레이아웃 모드 | "horizontal" | "vertical" | 'vertical' |
| collapse | 세로 모드에서 아이콘 막대로 접기(하위 메뉴는 플라이아웃 패널로 전환) | boolean | false |
| defaultOpeneds | 기본적으로 펼쳐지는 하위 메뉴 키(비제어) | string[] | () => [] |
| openeds | 제어되는 펼침 하위 메뉴 키 | string[] | — |
| uniqueOpened | 아코디언: 동시에 하나의 하위 메뉴만 펼침 | boolean | false |
| sx | sx 빠른 레이아웃 속성 | LhSxProps | — |
Events
| 이벤트 | 설명 | 콜백 매개변수 |
|---|---|---|
| update:modelValue | 활성 항목이 변경될 때 발생(v-model) | (value: string) |
| update:openeds | 펼침 하위 메뉴 집합이 변경될 때 발생(v-model:openeds) | (value: string[]) |
| select | 메뉴 항목이 선택될 때 발생 | (index: string, indexPath: string[]) |
| open | 하위 메뉴가 펼쳐질 때 발생 | (index: string, indexPath: string[]) |
| close | 하위 메뉴가 접힐 때 발생 | (index: string, indexPath: string[]) |
LhMenuItem Props
| 프로퍼티 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| index | 고유 식별자(필수) | string | — |
| disabled | 비활성화 | boolean | false |
| sx | sx 빠른 레이아웃 속성 | LhSxProps | — |
이벤트: click(index); 슬롯: default.
LhSubMenu Props
| 프로퍼티 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| index | 고유 식별자(필수) | string | — |
| title | 제목(title 슬롯으로도 가능) | string | — |
| disabled | 비활성화 | boolean | false |
| sx | sx 빠른 레이아웃 속성 | LhSxProps | — |
이벤트: select(index, indexPath)(버블링); 슬롯: title, default(LhSubMenu를 다시 중첩 가능).
LhMenuItemGroup Props
| 프로퍼티 | 설명 | 타입 | 기본값 |
|---|---|---|---|
| title | 그룹 제목(title 슬롯으로도 가능) | string | — |
| sx | sx 빠른 레이아웃 속성 | LhSxProps | — |
슬롯: title, default.
접근성
- 가로는
role="menubar", 세로는role="menu"; 항목은role="menuitem", 그룹은role="group"(컨테이너는role="presentation") - 하위 메뉴 제목은
aria-haspopup+aria-expanded+aria-controls로 패널과 연결 - 키보드: Enter/Space/→로 펼치고 첫 항목에 포커스, ←로 접기, Esc로 닫기; 세로 인라인 모드에서는 ↑/↓/Home/End로 보이는 항목(리프 항목, 하위 메뉴 제목, 펼쳐진 패널 내 항목) 사이를 경계 넘어 순회하며 순환하고, 비활성화 항목과 접혀 있는 패널 내 항목은 자동으로 건너뜀
- 인라인 펼침 높이에는 CSS
grid-template-rows: 0fr/1fr을 사용(측정 없는 JS 제로); 플라이아웃 패널은 popper 엔진이 위치시킴(flip/shift/뷰포트 밖 접기)
타입 정의
이 컴포넌트가 외부로 노출하는 TypeScript 타입 구조는 다음과 같습니다(packages/components/menu/src/types.ts와 @lh-ui/share/core/* 소스에서 자동 추출, 각 항목을 클릭하여 전체 정의를 펼쳐 볼 수 있습니다; 프로퍼티 설명은 위쪽 Props 표를 참고하세요):
LhMenuProps
ts
/** 메뉴 props */
export interface LhMenuProps {
/** 현재 활성 항목(제어되는 v-model; 넘기지 않으면 컴포넌트 내부에서 관리) */
modelValue?: string
/** 레이아웃 모드 */
mode?: LhMenuMode
/** 세로 모드에서 아이콘 막대로 접기(하위 메뉴는 플라이아웃 패널로 전환) */
collapse?: boolean
/** 기본적으로 펼쳐지는 하위 메뉴 키(비제어) */
defaultOpeneds?: string[]
/** 제어되는 펼침 하위 메뉴 키 */
openeds?: string[]
/** 아코디언: 동시에 하나의 하위 메뉴만 펼침 */
uniqueOpened?: boolean
/** 빠른 레이아웃 속성 */
sx?: LhSxProps
}LhMenuItemProps
ts
/** 리프 항목 props */
export interface LhMenuItemProps {
/** 고유 식별자(필수) */
index: string
disabled?: boolean
/** 빠른 레이아웃 속성 */
sx?: LhSxProps
}LhSubMenuProps
ts
/** 하위 메뉴 props(재귀 중첩 가능) */
export interface LhSubMenuProps {
/** 고유 식별자(필수) */
index: string
/** 제목(title 슬롯으로도 가능) */
title?: string
disabled?: boolean
/** 빠른 레이아웃 속성 */
sx?: LhSxProps
}LhMenuItemGroupProps
ts
/** 그룹 props */
export interface LhMenuItemGroupProps {
/** 그룹 제목(title 슬롯으로도 가능) */
title?: string
/** 빠른 레이아웃 속성 */
sx?: LhSxProps
}LhMenuMode (출처: @lh-ui/share/core/menu)
ts
/** 메뉴 레이아웃 모드 */
export type LhMenuMode = 'horizontal' | 'vertical'