LnbSidebar
진행 중 (LnbSidebar component — work in progress)
- 좌측 네비게이션(LNB) 메뉴 컴포넌트. config 기반으로 섹션·메뉴·서브메뉴를 렌더링.
- 다크 배경(neutral-900), 글래스 톤 (
--ds-glass-*) 위주의 색 토큰 사용. - 토큰 fix 완료. 단, transition 120ms / 150ms, font-weight, 일부 비표준 rgba, 1px hairline 정책 결정 대기 — 검토 필요 사항 참고.
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다
LNB 강제 규칙. 상세·미리보기는 아래 본문.
마크업 / 구성
- 템플릿은 빌드타임 include(
<!-- include "../../packages/components/html/lnb-sidebar.html" -->)로 넣습니다..ds-lnb__menu마크업을 손으로 짓지 마세요. - 루트
<aside class="ds-lnb">는ds-lnb--expanded/ds-lnb--collapsed중 정확히 하나를 항상 달고 있어야 합니다 — 폭(208px / 68px)은 그 두 클래스 블록에만 정의돼 있어요(design-system.css의.ds-lnb--expanded/.ds-lnb--collapsed)..ds-lnb자체엔 폭이 없습니다. - 메뉴는
LnbConfig = { sections: [{ label?, items }] }config로만 구성합니다(LnbSidebar.ts의LnbConfig/LnbSection/LnbItem타입). id없는 아이템은onSelect를 절대 발생시키지 않습니다 — 자식 토글 전용이에요(handleItemClick()). 아코디언 부모 그룹이 그렇습니다.- 렌더는 평평한
entries리스트 +display:contents래퍼입니다(get entries()). Alpine 3.14에서 중첩x-for가 깨져서 일부러 이렇게 한 거예요(파일 헤더 주석에 이유 있음) — 중첩x-for로 "정리"하지 마세요. - 아이콘은
getIcon레지스트리에서 옵니다(iconFor()). 렌더 박스는 18px(design-system.css의.ds-lnb__menu-icon).
반응형 3단 (⚠️ 컴포넌트가 아니라 앱이 구현합니다)
- 기준 앱은 admin(
dashboard.html의syncLnbViewport()): <768 드로어 / 768–1279 강제 레일(68px) / ≥1280 사용자 설정 복원. - 상태 플래그 3개:
lnbCollapsed·lnbDrawerOpen·lnbPrefCollapsed. 접기 버튼은lnbCollapsed와lnbPrefCollapsed를 둘 다 써야 해요 — 안 그러면 리사이즈 때 PC 설정이 날아갑니다. - 새 breakpoint를 만들지 마세요 —
md(768) ·xl(1280)만 씁니다. breakpoints 참고.
⚠️ 트랩 — PR #430에서 실제로 터졌고, 되돌리면 재발합니다
- 드로어 닫기는
onSelect안에서만 하세요..ds-lnb__top(또는.ds-lnb) 컨테이너에@click="lnbDrawerOpen = false"를 걸면 안쪽 탭에도 발화해서,id없는 부모 그룹(예: "문서 결재")을 누르는 순간 하위 메뉴가 열리기도 전에 드로어가 닫힙니다. 커밋c49f31fe가 정확히 이걸 제거했어요 — 다시 넣지 마세요. onToggleCollapse에는 모바일 가드가 필수입니다:if (window.innerWidth < 768) return;. 없으면 드로어가 열린 채lnbCollapsed=true가 되어, 다시 연 드로어가 68px 데스크톱 레일로 렌더됩니다.- ⚠️
apps/general-affairs·apps/management-dashboard에는 이 가드가 없습니다. 다만 먼저 이식이 필요한지부터 확인하세요 — 두 앱은?embedded=1이면 LNB를 통째로 숨깁니다(embeddedMode). admin의embedRegistry가 두 앱을 품게 되면(현재는 team-manager·hr-system만 임베드) 그 LNB는 죽은 껍데기가 되므로 반응형 이식은 헛수고입니다. 두 앱이 독립 실행으로 계속 남고 + 모바일을 지원해야 할 때에만 admin의 반응형 LNB 4종 세트(뷰포트 동기화 · 모바일 가드 ·lnbPrefCollapsed보존 · chevron 숨김)를 통째로 이식하세요. 가드만 따로 넣지 마세요 — 드로어 없는 앱에서는 접기 버튼만 죽습니다.
- ⚠️
- 모바일에서 접기 chevron(
.ds-lnb__toggle-inline)은 숨겨야 합니다. 드로어는 항상 "펼침"이라, chevron이 보이면 사용자가 드로어를 레일로 접어버릴 수 있어요. onToggleCollapse를 주면isCollapsed도 같이 주세요(LnbSidebar.ts의isCollapsed?: () => booleanprop). 없으면 DOM 탐침으로 폴백해서 Alpine 한 틱 늦게 반응합니다.openFlyout()첫 줄의ev.stopPropagation()을 지우지 마세요 — 연 그 클릭이@click.outside로 바로 닫아버립니다.- 앱 CSS는
@import뒤라 소스 순서로 이미 이깁니다 —!important쓰지 마세요.
라이브 미리보기
아래는 실제로 클릭되는 라이브 미리보기입니다 — 프로덕션과 동일한
.ds-lnb*/.ws-brand클래스로 렌더되어 active 전환·부모 메뉴 서브메뉴 토글·.ds-lnb--expanded↔.ds-lnb--collapsed폭 전환이 그대로 동작합니다. 브랜드 우측 버튼으로 접고, 접힘 상태에서 아이콘에 호버(커스텀 툴팁)·하위 메뉴가 있는 부모를 클릭(우측 플라이아웃)해 보세요. 활성 하위를 가진 부모는 접힘에서도 강조가 유지됩니다.
선택: 직원 관리 · 펼침 — 메뉴 클릭 전환 · 부모 메뉴로 서브메뉴 토글 · 브랜드 우측 버튼으로 접기. 접힘 상태에서: 아이콘 호버(툴팁), 부모 클릭(우측 플라이아웃), 활성 하위를 가진 부모는 강조 유지
패널 모달 내비게이션 (in-modal LNB)
패널 모달(.ds-modal--panel)의 좌측 메뉴 — LNB 패턴의 밝은 배경 인-모달 변형입니다. 좌측 메뉴 클릭 시 우측(모달 현황 자리)이 전환됩니다. (LNB와의 상세 비교는 아래 패널 모달 내비게이션 참고)
선택 섹션: 연장근무 유형— 밝은 배경 · 플랫(단일 depth) · active는 bg-white. 다크 레일(.ds-lnb)과 달리 페이지가 아니라 모달 우측 현황을 전환합니다.
단일 출처
| 파트 | 파일 |
|---|---|
| Alpine 컴포넌트 | packages/components/src/components/LnbSidebar.ts |
| HTML partial | packages/components/html/lnb-sidebar.html |
| 스타일 | packages/static/styles/design-system.css (.ds-lnb*) |
구조
- expanded 폭: 208px / collapsed 폭: 68px
- header — 브랜드/스위처 + (workspace 패턴) 접기 토글 우측 배치
- section —
section-head(라벨 + 구분선) + 메뉴 묶음 - menu — 단일 메뉴 (id 클릭) 또는 부모 메뉴 (children 토글)
- submenu — 부모 expand 시 노출 (들여쓰기 34px)
- bottom (profile + collapse) — 레거시 변형. workspace(admin)는 프로필을 PageHeader 아바타로, 토글을 헤더로 이동해 하단 영역을 제거 (2026-06). 다른 앱 단독 화면은 하단 유지
- 메뉴 영역 스크롤바는 표시하지 않음 (
scrollbar-width: none) — 스크롤 동작은 유지
두 가지 사용 패턴
1. 메뉴만 사용 (가장 일반적)
<div x-data="lnbSidebar({
config: lnbConfig,
isActive: (id) => activeTab === id,
onSelect: (id) => switchTab(id),
})">
<!-- include "../../packages/components/html/lnb-sidebar.html" -->
</div>lnbConfig 예시:
const lnbConfig: LnbConfig = {
sections: [
{
label: "조직",
items: [
{ id: "organization", label: "조직관리", icon: "ic-org" },
{ id: "employees", label: "직원관리", icon: "ic-employee", badge: "5" },
{ id: "roles", label: "역할관리", icon: "ic-role" },
],
},
{
label: "근태/급여",
items: [
{
label: "급여",
icon: "ic-payroll",
defaultExpanded: true,
children: [
{ id: "payroll", label: "급여대장", icon: "ic-payroll" },
{ id: "allowance", label: "수당 설정", icon: "ic-allow" },
],
},
{ id: "leaves", label: "휴가 관리", icon: "ic-leave" },
],
},
],
};2. 전체 LNB Shell — workspace(admin) 최신 패턴
브랜드(정적, 비클릭) + 접기 토글이 헤더에 함께 배치되고 하단 영역은 없다. 프로필은 PageHeader 아바타로 이동.
<aside class="ds-lnb" :class="lnbCollapsed ? 'ds-lnb--collapsed' : 'ds-lnb--expanded'">
<div class="ds-lnb__header" x-data="workspaceSwitcher({ current: 'admin' })">
<div class="ws-brand"><!-- Medical 심볼 + hereby Medical / 조직명 텍스트 --></div>
<button class="ds-lnb__toggle-inline" @click="lnbCollapsed = !lnbCollapsed">…</button>
</div>
<div class="ds-lnb__top" x-data="lnbSidebar({...})">
<!-- include ".../lnb-sidebar.html" -->
</div>
</aside>(레거시) 스위처 + 하단 profile/collapse 변형
총무·경영 대시보드 등 단독 화면이 아직 쓰는 기존 변형 — 헤더에 워크스페이스 스위처(드롭다운), 하단 .ds-lnb__bottom에 프로필 + 접기 토글.
패널 모달 내비게이션 (in-modal LNB)
패널 모달(.ds-modal--panel, 예: 근태 「유형·기준 통합 관리」)의 좌측 메뉴는 LNB 패턴을 모달 안으로 가져온 경량 변형입니다. 라이브 미리보기는 페이지 상단 라이브 미리보기 → 패널 모달 내비게이션에서 직접 조작할 수 있습니다.
다크 레일 컴포넌트(.ds-lnb, lnbSidebar())를 그대로 쓰는 게 아니라 밝은 배경의 인-모달 섹션 스위처입니다 — 하지만 "config로 항목을 정의하고, 정체성 아이콘을 붙이고, 하나만 active" 라는 LNB의 핵심 원칙은 동일합니다.
LNB와 무엇이 같고 다른가
| 항목 | 앱 LNB (.ds-lnb) | 패널 모달 좌측 메뉴 |
|---|---|---|
| 컴포넌트 | lnbSidebar() Alpine factory | 모달 내 인라인 마크업(type-manager.html) |
| 배경 | 다크(neutral-900) + 글래스 톤 | 밝음(bg-neutral-50) + 우측 1px 라인 |
| 항목 정의 | LnbConfig.sections[].items | typeManagerMenu({ id, label, icon }[]) |
| 아이콘 | 24px 아웃라인(ds-lnb__menu-icon) | 16px(.type-manager__nav-icon) |
| active 표현 | is-active (글래스 강조) | bg-white·neutral-900·600 |
| 계층 | section/부모/서브메뉴 · 접힘 레일 | 단일 depth(플랫), 접힘 없음 |
| 선택 결과 | 페이지/탭 전환 | 우측 콘텐츠(현황 표) 전환 |
공유 규칙 — 아이콘 일치: 패널 모달 좌측 메뉴의 각 항목 아이콘은 아이콘 사용 규칙과 같은 원칙을 따릅니다 — 그 섹션의 출처 페이지가 LNB·헤더에서 쓰던 정체성 아이콘을 그대로 재사용합니다(근무 설정=슬라이더, 연장근무=시계플러스, 외근=맵핀, 연차 부여 기준=달력체크, 휴가 유형=달력). 같은 화면을 가리키는 LNB·헤더·패널 메뉴의 아이콘은 항상 일치해야 합니다.
바로가기 — 좌측 메뉴가 팝업 안에서 어떻게 보이는지, 패널 전체 라이브 예시·규격은 → 모달 → 패널 모달
API
lnbSidebar(opts: LnbSidebarOptions) Alpine.data factory.
| Prop | 타입 | 설명 |
|---|---|---|
config | LnbConfig | { sections: LnbSection[] } — 섹션·메뉴 트리 |
isActive | (id: string) => boolean | 현재 활성 메뉴 판단 |
onSelect | (id: string) => void | 메뉴 클릭 핸들러 |
타입
| Type | 필드 |
|---|---|
LnbItem | id?, label, icon, badge?: string | number | LnbBadge, disabled?, children?, defaultExpanded? |
LnbBadge | text: string | number, tone?, line?, shape?(기본 round), size?(기본 xs) |
LnbSection | label?, items: LnbItem[] |
LnbConfig | sections: LnbSection[] |
id없는 item은 클릭해도 select 안 됨 (children 토글만)defaultExpanded→ 부모 메뉴는 기본으로 펼쳐짐(미지정=펼침). 접힌 상태로 시작하려면defaultExpanded: false를 지정badge→ 메뉴 우측 칩. 배지는 DS.ds-label컴포넌트로 렌더된다(전용 스타일 X).- 문자열/숫자(예:
badge: "5") → 알림 카운트 필(primary·round). 문서 결재 대기 건수 등에 사용 - 객체(
LnbBadge) → DS 라벨 변형을 직접 지정:tone(색)·line(테두리)·shape(square/round)·size(xs/sm/md) - 다크 레일 주의: LNB 배경이 어두우므로(neutral-900) 준비중 라벨 등엔 다크 서피스 tone(
gray-dark·light-gray-dark)을 써야 글자가 읽힌다. 예) 총무 준비 메뉴 ={ text: "준비중", tone: "light-gray-dark", size: "xs", shape: "square" }(solid). 밝은 배경용gray/light-gray는 글자가 어두워 다크 레일에서 안 보임
- 문자열/숫자(예:
States
| State | 표현 |
|---|---|
| default | color: var(--ds-glass-70) (희미한 흰색) |
| hover | background: rgba(255,255,255,0.06) (정확 토큰 부재), color: var(--ds-bw-white) |
| active | background: var(--ds-glass-10), color: var(--ds-bw-white) |
| child-active (collapsed) | 활성 하위를 가진 부모 아이콘에 active와 동일 강조 (is-child-active) — 하위가 숨겨지는 접힘 레일 전용 |
| disabled | color: var(--ds-glass-30), pointer-events: none |
| expanded parent | chevron 180° 회전 |
| collapsed sidebar | width 68px, menu-label · badge · section-label 숨김 |
접힘(collapsed) 레일 동작
접힘 레일은 라벨이 숨겨지므로 두 가지 보조 UI가 자동으로 동작한다 (별도 옵션 없음):
플라이아웃 — .ds-lnb__flyout
- 하위 메뉴가 있는 부모를 클릭하면 아이콘 우측에 하위 메뉴 그룹 표시
- 흰 바탕(
--ds-bw-white) + 1px 라인(--color-line-normal) +--radius-8+--shadow-popover, 행은 아이콘 + 타이틀(+배지) - 클릭한 버튼 좌표 기준 fixed 배치 — 레일의
overflow/스크롤에 잘리지 않음 (z-60) - 항목 선택·바깥 클릭·같은 부모 재클릭으로 닫힘. disabled 항목(준비중)은 흐림 + 클릭 불가
커스텀 툴팁 — .ds-lnb__tip
- 접힘 레일에서 메뉴 호버 시 아이콘 우측에 라벨 표시 (
neutral-900바탕 +--ds-bw-white텍스트 +--radius-4, z-70) - 네이티브
title미사용 — 네이티브 툴팁은 떠 있는 동안 코드로 지울 수 없어 플라이아웃을 가린다. 접근성은aria-label로 유지 - 클릭 즉시 숨김. 플라이아웃이 열려 있을 때 억제는 "열린 메뉴 자신"으로 한정 — 다른 메뉴의 툴팁은 그룹 위(z-70 > 60)로 표시
사용된 토큰 (fix 후)
| 카테고리 | 토큰 |
|---|---|
| Spacing | --ds-spacing-{2, 4, 6, 8, 10, 12, 16, 18, 20, 22, 24, 32, 36, 68, 208, 220} 등 |
| Color (dark) | --color-neutral-{25, 50, 100, 700, 900}, --color-red-500, --color-orange-{50, 500} |
| Glass tints | --ds-glass-{08, 10, 18, 30, 55, 60, 70} |
| Brand | --ds-bw-white |
| Radius | --radius-4, --radius-8, --ds-radius-sm, --radius-full |
| Opacity | --ds-opacity-60 (submenu bullet), --ds-opacity-40 (flyout disabled) |
| Typography | --text-b4, --text-b5, --text-b6 (tip), --text-c1, --text-c2 |
| Shadow | --ds-shadow-xs (collapsed toggle), --shadow-blur4 (profile menu), --shadow-popover (flyout) |
| Motion | --duration-normal (sidebar width transition) |
| Flyout/Tip color | --ds-bw-white(바탕), --color-line-normal(라인), --color-neutral-{50, 75, 500, 900} |
아이콘 사용 규칙
- 메뉴 아이콘은
@hereby/componentsicon registry(packages/components/src/icons.ts)의 24px 아웃라인 세트(스트로크 1.5)만 사용 - 같은 LNB 안에서 아이콘 중복 금지 — 메뉴 식별이 목적이므로 항목마다 구분되는 통상적 의미의 아이콘 사용. 의미가 같은 메뉴(예: 섹션이 다른 두 "설정")만 동일 아이콘 허용
- 페이지 헤더 아이콘과 일치 — 같은 화면을 가리키는 LNB 항목·헤더 타이틀·embed 레지스트리의 아이콘은 동일해야 한다
- 2026-06 확장: 명찰(역할)·서식문서·표(시트)·시계(근태)·슬라이더(설정류)·달력시계(스케줄러)·달력체크(연차)·시계플러스(연장근무)·막대차트(현황)·태그(유형)·맵핀(외근)·게이지(대시보드)·목록체크(업무) 13종 추가
검토 필요 사항
토큰 체계가 아직 커버하지 못해 하드코딩으로 남겨둔 항목. 디자이너 결정 후 토큰화 진행:
- 🟡 rgba(255, 255, 255, 0.06) (4곳: border, hover backgrounds) —
--ds-glass-04(0.04) /--ds-glass-08(0.08) 사이. 0.06 토큰 추가 vs 0.08로 정렬 결정 필요 - 🟡 rgba(255, 255, 255, 0.9) (1곳: profile-name) —
--ds-glass-70초과. 0.9 토큰 추가 필요 - 🟡 rgba(255, 255, 255, 0.45) (1곳: section-label) —
--ds-glass-30/55사이. 0.45 토큰 또는 정렬 - 🟡 rgba(143, 143, 143, 0.5) (1곳: collapse toggle border) — neutral-400 + 0.5 alpha. 단일 토큰 부재
- 🟡 font-weight: 400/500/600/700 — 모두 하드코딩. weight 토큰 미정의
- 🟡 transition 120ms / 150ms (~8곳) — motion scale 부족
- 🟡 padding-left 13px / 34px (menu / submenu) — 2px step 미정렬. 메뉴 들여쓰기 정책 검토
- ✅ badge — 전용
.ds-lnb__badge인셋 스타일 제거 완료. 배지는 이제 DS.ds-label(크기·모양·색)로 렌더되고,.ds-lnb__badge는 접힘 레일에서 배지를 숨기는 훅으로만 남음 - 🟡 right -10px / -23px (collapse toggle 음수 offset) — half-pixel positioning 정책
- 🟡 1px hairline (3곳: section-line, profile-menu-divider) — spacing 최소가 2px. hairline 정책 결정
- 🟡 avatar linear-gradient — 브랜드 그라데이션, 단일 토큰 부재. 컴포넌트 전용으로 유지 가능
사용처
- admin (apps/admin/src/views/dashboard.html) — 4-group 계층 LNB
- general-affairs (apps/general-affairs/src/views/dashboard.html)
- management-dashboard (apps/management-dashboard/src/views/dashboard.html)
- 패널 모달 좌측 메뉴 (apps/admin/src/views/modals/type-manager.html) — LNB 패턴의 인-모달 변형 → 패널 모달 내비게이션
- 기타 LNB 사용 앱
목표 & 로드맵
| 항목 | 현재 | 목표 |
|---|---|---|
| Config-driven 렌더링 | ✅ section/item/submenu 트리 | 유지 |
| Spacing 토큰 매핑 | ✅ 완료 | 유지 |
| Color hex → 토큰 | ✅ 완료 (fallback 정합 포함) | 유지 |
| Glass tint 매핑 | ✅ 정확 매칭만 (08/10/18/30/55/60/70) | 0.06 / 0.45 / 0.9 추가 |
| Shadow 매핑 | ✅ blur4 / xs | 유지 |
| Font-weight 토큰화 | 🔴 미정의 | typography 페이지 weight scale 도입 후 적용 |
| Motion 토큰 확장 | 🟡 fast/normal만 | 120ms / 150ms 등 세분화 |
| 2px step 외 spacing | 🟡 13/34/-10/-23 (메뉴/툴글) | step 정렬 또는 layout offset 토큰 도입 |
| Hairline 토큰 정책 | 🔴 미정의 | border-width / spacing 둘 중 결정 |
| 컴포넌트 가이드라인 | 🔴 미작성 | section 그룹 사용 시점, badge 사용 규칙 등 |
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-07-02 | 배지를 DS .ds-label로 렌더: 전용 .ds-lnb__badge 시각 스타일 제거, LnbItem.badge가 string | number(카운트 필) 또는 LnbBadge 객체(tone·line·shape·size 변형) 허용. 다크 레일용 tone(gray-dark·light-gray-dark) 지원. 총무 준비중 메뉴 = light-gray-dark solid 배지 + disabled. 라이브 미리보기·API 표 갱신 |
| 2026-06-12 | 접힘 레일 UX: 플라이아웃(.ds-lnb__flyout)·커스텀 툴팁(.ds-lnb__tip, 네이티브 title 대체)·is-child-active 강조 추가. workspace 패턴: 접기 토글 헤더(브랜드 우측) 이동·하단 profile 제거(헤더 아바타로)·ws-brand 정적 브랜드. 레일 스크롤바 숨김. 아이콘 13종 추가 + 중복 제거 리맵 |
| 2026-05-28 | 토큰 audit + fix: spacing ~50곳, radius 7곳, font-size 8곳, color hex 5곳, glass tint 5곳, color fallback 3곳, shadow 2곳, opacity 1곳, transition 3곳 → DS 토큰 매핑. 시각 변화 ≈ 0 |
| 이전 | config-driven LnbSidebar 컴포넌트 + 4-group 계층 admin 적용 |