사이드 메뉴 Side navigation
사이드 메뉴는 서브 화면 내에서의 이동을 위해 사용하는 메뉴이다. 일반적으로 본문 영역의 좌측에 사이드바 형태로 제공된다. 메인 메뉴보다 훨씬 좁고 깊은 페이지 구조 탐색에 사용되기 때문에 링크의 개수가 많고 복잡하게 표현되기 쉽다.
기본
<nav>에 krdsSideNavigation을 붙이고, 메뉴 제목은 heading으로, 메뉴 이름은 aria-label로 줍니다. 메뉴 항목 배열은 [(items)]로 주고받습니다. 2Depth의 subItems는 펼침 목록으로, 3Depth의 subItems는 4Depth 팝업으로 렌더링됩니다. 펼침 상태는 각 항목의 expanded로, 처음 값을 주면 그대로 펼쳐지고 사용자가 펼치고 접으면 바뀐 배열이 items로 돌아옵니다. 2Depth를 접으면 그 안의 팝업도 닫힙니다.
4Depth 팝업을 열면 팝업 제목으로 초점이 옮겨 가고, 제목을 눌러 닫으면 팝업을 연 버튼으로 초점이 돌아옵니다. Tab으로 팝업을 벗어나거나 팝업 밖을 누르면 팝업이 닫히고, 초점은 옮겨 간 곳에 그대로 둡니다.
import { Component, signal } from '@angular/core'
import { KrdsSideNavigation, type KrdsSideNavItem } from '@krds.ui/angular'
const menu = (expanded: boolean): KrdsSideNavItem => ({
text: '2Depth-menu',
expanded,
subItems: [
{
text: '3Depth-menu',
popupTitle: '3Depth-title',
subItems: [
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' }
]
},
{ text: '3Depth-link', href: '#' },
{ text: '3Depth-link', href: '#' }
]
})
@Component({
selector: 'demo-side-navigation-basic',
imports: [KrdsSideNavigation],
template: ` <nav krdsSideNavigation heading="1Depth-title" aria-label="사이드 메뉴" [(items)]="items"></nav> `
})
export default class SideNavigationBasic {
readonly items = signal([menu(true), menu(false), menu(false)])
}2Depth 링크
subItems가 없는 2Depth 항목은 링크로 렌더링됩니다. 링크를 누르면 (itemClick)으로 항목과 클릭 이벤트를 보내므로, 라우터로 이동하거나 다른 동작을 하려면 event.preventDefault()로 기본 이동을 막습니다. 3Depth·4Depth 링크도 같습니다.
import { Component, signal } from '@angular/core'
import { KrdsSideNavigation, type KrdsSideNavClick, type KrdsSideNavItem } from '@krds.ui/angular'
@Component({
selector: 'demo-side-navigation-two-depth-link',
imports: [KrdsSideNavigation],
template: `
<nav
krdsSideNavigation
heading="1Depth-title"
aria-label="링크가 있는 사이드 메뉴"
[(items)]="items"
(itemClick)="onItemClick($event)"
></nav>
<p>누른 링크: {{ clicked() || '없음' }}</p>
`
})
export default class SideNavigationTwoDepthLink {
readonly items = signal<KrdsSideNavItem[]>([
{ text: '2Depth-link', href: '#lnb-link' },
{ text: '2Depth-action' },
{
text: '2Depth-menu',
subItems: [
{
text: '3Depth-menu',
popupTitle: '3Depth-title',
subItems: [
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' }
]
},
{ text: '3Depth-link', href: '#' },
{ text: '3Depth-link', href: '#' }
]
}
])
readonly clicked = signal('')
onItemClick({ item, event }: KrdsSideNavClick) {
// 라우터로 이동하거나 다른 동작을 할 때는 기본 이동을 막는다
event.preventDefault()
this.clicked.set(item.text)
}
}여러 개 배치
한 페이지에 여러 개를 두어도 내부 id는 겹치지 않습니다. aria-label로 각 메뉴를 구분합니다.
import { Component, signal } from '@angular/core'
import { KrdsSideNavigation, type KrdsSideNavItem } from '@krds.ui/angular'
const createItems = (): KrdsSideNavItem[] =>
[true, false, false].map(expanded => ({
text: '2Depth-menu',
expanded,
subItems: [
{
text: '3Depth-menu',
popupTitle: '3Depth-title',
subItems: [
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' },
{ text: '4Depth', href: '#' }
]
},
{ text: '3Depth-link', href: '#' },
{ text: '3Depth-link', href: '#' }
]
}))
@Component({
selector: 'demo-side-navigation-multiple',
imports: [KrdsSideNavigation],
template: `
<div style="display: flex; gap: 2rem">
<nav krdsSideNavigation heading="1Depth-title" aria-label="첫 번째 사이드 메뉴" style="flex: 1" [(items)]="first"></nav>
<nav krdsSideNavigation heading="1Depth-title" aria-label="두 번째 사이드 메뉴" style="flex: 1" [(items)]="second"></nav>
</div>
`
})
export default class SideNavigationMultiple {
readonly first = signal(createItems())
readonly second = signal(createItems())
}KrdsSideNavigation
선택자 nav[krdsSideNavigation]
Inputs
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
heading (필수) | string | - | 메뉴 제목 (title 속성은 기본 툴팁이 되므로 heading으로 받음) |
[(items)] | KrdsSideNavItem[] | [] | 메뉴 항목 (expanded로 펼침 상태) |
Outputs
| 이름 | 값 | 설명 |
|---|---|---|
itemClick | KrdsSideNavClick | 링크를 클릭했을 때 |
타입
/** 사이드 메뉴 2단계 항목 (subItems가 있으면 펼침 버튼) */
export interface KrdsSideNavItem {
text: string
href?: string
/** 펼침 */
expanded?: boolean
subItems?: KrdsSideNavSubItem[]
}
/** 사이드 메뉴 링크 클릭 이벤트 (라우터로 이동하려면 event.preventDefault()) */
export interface KrdsSideNavClick {
item: KrdsSideNavItem | KrdsSideNavSubItem | KrdsSideNavPopupItem
event: MouseEvent
}
/** 사이드 메뉴 3단계 항목 (subItems가 있으면 팝업을 여는 버튼) */
export interface KrdsSideNavSubItem {
text: string
href?: string
/** 팝업 열림 */
expanded?: boolean
/** 팝업 제목 (기본: text) */
popupTitle?: string
subItems?: KrdsSideNavPopupItem[]
}
/** 사이드 메뉴 4단계(팝업 안) 항목 */
export interface KrdsSideNavPopupItem {
text: string
href?: string
}