Appearance
NavigationBar
좌측 수직 네비게이션 사이드바 컴포넌트
개요
NavigationBar는 APM 애플리케이션의 메인 네비게이션 인터페이스를 제공하는 좌측 수직 사이드바 컴포넌트입니다. 메뉴 클릭 시 슬라이드 패널이 열리며, 대시보드, 분석, 통계 등의 주요 기능에 접근할 수 있습니다.
주요 기능
- 📱 수직 사이드바: 72px 고정 너비의 좌측 네비게이션 바
- 🎯 슬라이드 패널: 메뉴 클릭 시 256px (기본값) 너비의 패널이 슬라이드로 열림
- 🔄 토글 동작: 같은 메뉴 재클릭 시 패널 닫기, 다른 메뉴 클릭 시 패널 전환
- 🌐 Teleport 지원: Portal을 통한 유연한 DOM 배치
- 🏷️ 배지 표시: 알림 개수 등을 표시하는 배지 지원
- 🎨 커스터마이징: 로고, 메뉴 아이템, 패널 콘텐츠 완전 커스터마이징
- 🔌 독립 패널: 일반 패널과 별도로 작동하는 커스텀 패널 지원
- ♿ 접근성: 외부 클릭 감지, 키보드 네비게이션
Props
NavigationBar Props
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
headMenuItems | MenuItem[] | [] | 상단 메뉴 아이템 목록 |
tailMenuItems | MenuItem[] | [] | 하단 메뉴 아이템 목록 |
logoSrc | string | - | 로고 이미지 소스 (없으면 #logo 슬롯 사용) |
logoHref | string | '/' | 로고 클릭 시 이동할 URL |
selectedPanel | string | null | null | 현재 선택된 패널 키 (v-model) |
panelWidth | number | 256 | 패널 너비(px) - 일반 패널과 커스텀 패널 모두에 적용 |
teleportTo | string | null | 'body' | Teleport 대상 선택자 |
enableCustomPanel | boolean | false | 커스텀 패널 활성화 여부 |
customPanelKey | string | 'custom' | 커스텀 패널 식별 키 |
class | string | - | 추가 CSS 클래스 |
MenuItem Interface
typescript
interface MenuItem {
/** 메뉴 식별자 */
key: string;
/** 아이콘 (ICON_TYPE) */
icon: IconTypes;
/** 표시 이름 */
displayName: string;
/** 메뉴 타입 */
type: 'panel' | 'event'; // panel: 패널 열림, event: 이벤트만 발생
/** 선택 상태 */
selected?: boolean;
/** 눌림 상태 (패널이 열려있을 때 시각적 피드백) */
pressed?: boolean;
/** 비활성화 상태 */
disabled?: boolean;
/** 배지 (알림 개수 등) */
badge?: number | string;
/** 툴팁 텍스트 */
tooltip?: string;
}SubMenuItem Interface
typescript
interface SubMenuItem {
/** 메뉴 식별자 */
key: string;
/** 아이콘 (thumbnail이 있으면 선택사항) */
icon?: IconTypes;
/** 표시 이름 */
displayName: string;
/** 선택 상태 */
selected?: boolean;
/** 눌림 상태 (클릭 시 임시 시각적 피드백) */
pressed?: boolean;
/** 비활성화 상태 */
disabled?: boolean;
/** 검색 하이라이트 인덱스 */
indices?: number[];
/** 썸네일 이미지 경로 (아이콘 대신 사용) */
thumbnail?: string;
/** 툴팁 텍스트 */
tooltip?: string;
}Events
| 이벤트 | 파라미터 | 설명 |
|---|---|---|
menu-click | item: MenuItem | 메뉴 클릭 시 발생 |
panel-open | panelKey: string, item?: MenuItem | 패널 열림 시 발생 |
panel-close | panelKey: string, item?: MenuItem | 패널 닫힘 시 발생 |
logo-click | - | 로고 클릭 시 발생 |
update:selectedPanel | value: string | null | v-model 업데이트 |
Slots
#logo
로고 영역 슬롯. logoSrc prop이 없을 때 사용됩니다.
vue
<NavigationBar>
<template #logo>
<div class="custom-logo">MY APP</div>
</template>
</NavigationBar>#panel-
각 패널의 전체 콘텐츠를 정의하는 슬롯. {key}는 MenuItem.key 값입니다.
vue
<NavigationBar>
<template #panel-dashboard>
<NavHeader title="대시보드" closeable @close="closePanel" />
<div class="panel-content">
<!-- 패널 콘텐츠 -->
</div>
</template>
</NavigationBar>#custom-panel
독립적인 커스텀 패널 슬롯. enableCustomPanel이 true일 때 표시됩니다.
vue
<NavigationBar :enable-custom-panel="showCustom">
<template #custom-panel>
<NavHeader title="커스텀" closeable @close="closeCustomPanel" />
<div class="panel-content">
<!-- 커스텀 패널 콘텐츠 -->
</div>
</template>
</NavigationBar>TypeScript 타입
typescript
import type {
MenuItem,
SubMenuItem,
NavigationBarProps,
NavigationBarEmits
} from '@jennifersoft/apm-components';관련 컴포넌트
NavigationBar와 함께 사용되는 주요 컴포넌트들입니다.
NavHeader
패널 상단에 표시되는 헤더 컴포넌트입니다.
Props
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title | string | - | 헤더 타이틀 텍스트 |
tailIcon | IconTypes | - | 우측 커스텀 아이콘 (closeable이 false일 때) |
closeable | boolean | false | 닫기 아이콘 표시 및 close 이벤트 발생 여부 |
clickable | boolean | false | 헤더 클릭 가능 여부 |
Events
| 이벤트 | 파라미터 | 설명 |
|---|---|---|
click | event: MouseEvent | 헤더 클릭 시 발생 (clickable이 true일 때) |
close | - | 닫기 아이콘 클릭 시 발생 (closeable이 true일 때) |
사용 예시
vue
<NavHeader
title="대시보드"
closeable
@close="closePanel"
/>SubNavItem
패널 내부의 서브메뉴 아이템 컴포넌트입니다.
Props
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
icon | IconTypes | - | 아이콘 (thumbnail이 없을 때 필수) |
name | string | - | 표시 이름 |
selected | boolean | false | 선택 상태 (보라색 하이라이트) |
pressed | boolean | false | 눌림 상태 (회색 오버레이) |
disabled | boolean | false | 비활성화 상태 |
indices | number[] | - | 검색어 하이라이트 문자 인덱스 배열 |
thumbnail | string | - | 썸네일 이미지 URL (67×48px, 아이콘 대신 표시) |
tooltip | string | - | 툴팁 텍스트 |
Events
| 이벤트 | 파라미터 | 설명 |
|---|---|---|
click | event: MouseEvent | 아이템 클릭 시 발생 |
사용 예시
vue
<SubNavItem
v-for="item in dashboardItems"
:key="item.key"
:icon="item.icon"
:name="item.displayName"
:selected="item.selected"
@click="navigateTo(item.key)"
/>SubHeader
패널 내부의 섹션 구분 헤더입니다.
Props
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title | string | - | 섹션 타이틀 |
사용 예시
vue
<SubHeader title="최근 대시보드" />
<SubNavItem v-for="item in recentItems" ... />기본 사용법
vue
<script setup lang="ts">
import { ref } from 'vue';
import {
NavigationBar,
NavHeader,
SubNavItem,
type MenuItem
} from '@jennifersoft/apm-components';
import { ICON_TYPE } from '@jennifersoft/vue-components-v2';
const selectedPanel = ref<string | null>(null);
const headMenuItems = ref<MenuItem[]>([
{
key: 'dashboard',
icon: ICON_TYPE.dashboard,
displayName: '대시보드',
type: 'panel'
},
{
key: 'analysis',
icon: ICON_TYPE.analysis,
displayName: '분석',
type: 'panel'
}
]);
const tailMenuItems = ref<MenuItem[]>([
{
key: 'settings',
icon: ICON_TYPE.settings,
displayName: '설정',
type: 'panel'
},
{
key: 'user',
icon: ICON_TYPE.userStroke,
displayName: '사용자',
type: 'panel'
}
]);
const closePanel = () => {
selectedPanel.value = null;
};
</script>
<template>
<NavigationBar
v-model:selected-panel="selectedPanel"
:head-menu-items="headMenuItems"
:tail-menu-items="tailMenuItems"
logo-src="/assets/logo.png"
>
<!-- Dashboard Panel -->
<template #panel-dashboard>
<NavHeader title="대시보드" closeable @close="closePanel" />
<div class="panel-content">
<p>대시보드 콘텐츠</p>
</div>
</template>
<!-- Analysis Panel -->
<template #panel-analysis>
<NavHeader title="분석" closeable @close="closePanel" />
<div class="panel-content">
<p>분석 콘텐츠</p>
</div>
</template>
<!-- Settings Panel -->
<template #panel-settings>
<NavHeader title="설정" closeable @close="closePanel" />
<div class="panel-content">
<p>설정 콘텐츠</p>
</div>
</template>
<!-- User Panel -->
<template #panel-user>
<NavHeader title="사용자" closeable @close="closePanel" />
<div class="panel-content">
<p>사용자 정보</p>
</div>
</template>
</NavigationBar>
</template>인터랙티브 데모
주요 기능 상세
메뉴 타입 동작
MenuItem의 type 속성으로 메뉴 클릭 시 동작을 제어합니다.
type: 'panel': 메뉴 클릭 시 연결된 패널이 열리거나 닫힙니다type: 'event': 패널을 열지 않고menu-click이벤트만 발생시킵니다
vue
<script setup>
const menuItems = ref([
{
key: 'dashboard',
type: 'panel', // 패널 토글
// ... 기타 속성
},
{
key: 'external-link',
type: 'event', // 이벤트만 발생
// ... 기타 속성
}
]);
const handleMenuClick = (item) => {
if (item.type === 'event') {
// 외부 링크 이동, 다운로드 등의 액션 수행
window.open('https://example.com', '_blank');
}
};
</script>
<template>
<NavigationBar
:head-menu-items="menuItems"
@menu-click="handleMenuClick"
/>
</template>스크롤 위치 유지
NavigationBar는 패널별 스크롤 위치를 자동으로 저장하고 복원합니다.
동작 방식:
- 각 패널의 스크롤 위치가 Map에 저장됩니다
- 다른 패널로 전환 시 현재 스크롤 위치가 저장됩니다
- 이전에 열었던 패널로 돌아가면 스크롤 위치가 복원됩니다
- 성능을 위해 throttle(200ms) 적용된 스크롤 핸들러 사용
- 일반 패널과 커스텀 패널의 스크롤 위치가 독립적으로 관리됩니다
vue
<!-- 스크롤 위치는 자동으로 관리되므로 별도 설정 불필요 -->
<NavigationBar>
<template #panel-dashboard>
<NavHeader title="대시보드" />
<div class="panel-content">
<!-- 긴 콘텐츠... -->
<!-- 스크롤 후 다른 패널로 이동했다가 돌아와도 -->
<!-- 스크롤 위치가 자동으로 복원됩니다 -->
</div>
</template>
</NavigationBar>패널 너비 설정
panelWidth prop은 일반 패널과 커스텀 패널 모두에 적용됩니다.
vue
<script setup>
const panelWidth = ref(256); // 기본값
// 필요에 따라 동적으로 변경 가능
const expandPanel = () => {
panelWidth.value = 400;
};
</script>
<template>
<NavigationBar :panel-width="panelWidth" />
</template>참고: 각 패널마다 다른 너비가 필요한 경우, CSS로 개별 패널 콘텐츠의 너비를 조정하세요.
커스텀 패널 동작
커스텀 패널은 일반 패널과 독립적으로 작동하는 특수 패널입니다.
특징:
enableCustomPanelprop으로 표시/숨김 제어- 메뉴 선택과 무관하게 독립적으로 표시 가능
- 일반 패널이 열리면 자동으로 닫힙니다
- 외부 클릭 시 자동으로 닫힙니다
- 별도의 스크롤 위치 저장
vue
<script setup>
const showCustomPanel = ref(false);
const openHelp = () => {
showCustomPanel.value = true;
};
const closeCustomPanel = () => {
showCustomPanel.value = false;
};
</script>
<template>
<NavigationBar :enable-custom-panel="showCustomPanel">
<template #custom-panel>
<NavHeader title="도움말" closeable @close="closeCustomPanel" />
<div class="panel-content">
<p>도움말 콘텐츠...</p>
</div>
</template>
</NavigationBar>
<!-- 외부에서 커스텀 패널 열기 -->
<button @click="openHelp">도움말 보기</button>
</template>Z-Index 레이어 구조
NavigationBar와 패널들의 z-index 구조를 이해하면 레이아웃 문제를 방지할 수 있습니다.
Navigation Bar: z-index 20 (최상단)
├─ General Panel: z-index 10 (중간)
└─ Custom Panel: z-index 9 (하단)주의사항:
- 일반 패널이 열린 상태에서 커스텀 패널을 열면, 일반 패널이 위에 표시됩니다
- 다른 UI 요소와 z-index 충돌을 피하려면 이 구조를 고려하세요
- Teleport를 사용하므로 부모 컨테이너의 z-index는 영향을 주지 않습니다
고급 사용법
1. 배지 표시
알림 개수 등을 배지로 표시할 수 있습니다.
vue
<script setup>
const menuItems = ref([
{
key: 'notification',
icon: ICON_TYPE.bell2,
displayName: '알림',
badge: 3, // 숫자 배지
type: 'panel'
},
{
key: 'messages',
icon: ICON_TYPE.message,
displayName: '메시지',
badge: 'NEW', // 텍스트 배지
type: 'panel'
}
]);
</script>2. 이벤트 타입 메뉴
패널을 열지 않고 이벤트만 발생시키는 메뉴입니다.
vue
<script setup>
const menuItems = ref([
{
key: 'report',
icon: ICON_TYPE.report2,
displayName: '리포트',
type: 'event' // 패널을 열지 않음
}
]);
const handleMenuClick = (item) => {
if (item.type === 'event') {
// 페이지 이동 또는 다른 액션 수행
router.push(`/${item.key}`);
}
};
</script>
<template>
<NavigationBar
:head-menu-items="menuItems"
@menu-click="handleMenuClick"
/>
</template>3. 서브메뉴 with 썸네일
서브메뉴 아이템에 썸네일 이미지를 표시할 수 있습니다.
vue
<template>
<NavigationBar>
<template #panel-dashboard>
<NavHeader title="대시보드" closeable @close="closePanel" />
<div class="panel-content">
<SubNavItem
v-for="item in dashboardItems"
:key="item.key"
:icon="item.icon"
:name="item.displayName"
:thumbnail="item.thumbnail"
@click="navigateTo(item.key)"
/>
</div>
</template>
</NavigationBar>
</template>4. 커스텀 패널
일반 패널과 독립적으로 작동하는 커스텀 패널을 사용할 수 있습니다.
vue
<script setup>
const showCustomPanel = ref(false);
const openCustomPanel = () => {
showCustomPanel.value = true;
};
const closeCustomPanel = () => {
showCustomPanel.value = false;
};
</script>
<template>
<NavigationBar :enable-custom-panel="showCustomPanel">
<template #custom-panel>
<NavHeader title="도움말" closeable @close="closeCustomPanel" />
<div class="panel-content">
<p>도움말 콘텐츠...</p>
</div>
</template>
</NavigationBar>
<button @click="openCustomPanel">도움말 열기</button>
</template>5. 동적 패널 너비
패널 너비를 동적으로 조정할 수 있습니다.
vue
<script setup>
const panelWidth = ref(256);
const togglePanelWidth = () => {
panelWidth.value = panelWidth.value === 256 ? 400 : 256;
};
</script>
<template>
<NavigationBar :panel-width="panelWidth" />
</template>의존성
내부 컴포넌트
- NavHeader: 패널 헤더 컴포넌트
- SubNavItem: 서브메뉴 아이템 컴포넌트
- SubHeader: 서브메뉴 섹션 헤더
- Divider: 구분선 컴포넌트
- WrappedNavItem: 메뉴 아이템 래퍼
외부 의존성
@vueuse/components: vOnClickOutside 디렉티브@jennifersoft/vue-components-v2: ICON_TYPE
스타일링
기본 구조
- 네비게이션 바: 72px 고정 너비, 좌측 고정
- 패널: 기본 256px 너비, 슬라이드 애니메이션
- z-index: 네비게이션 바(20), 일반 패널(10), 커스텀 패널(9)
CSS 클래스
scss
.navigation-bar {
width: 72px;
height: 100vh;
position: fixed;
left: 0;
top: 0;
}
.navigation-panel {
position: fixed;
left: 72px;
top: 0;
height: 100vh;
background: white;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}주의사항
Teleport 사용: 기본적으로
body에 Teleport되므로 전역 스타일 충돌에 주의하세요.패널 외부 클릭: 패널은 외부 클릭 시 자동으로 닫힙니다.
z-index 관리: 커스텀 패널과 일반 패널은 별도의 z-index를 가집니다.
v-model 사용:
selectedPanel은 v-model로 양방향 바인딩됩니다.슬롯 네이밍: 패널 슬롯은
#panel-{key}형식으로 명명해야 합니다.
브라우저 지원
- Chrome (최신 2개 버전)
- Firefox (최신 2개 버전)
- Safari (최신 2개 버전)
- Edge (최신 2개 버전)