Sidebar
제품의 주요 영역과 현재 위치를 지속적으로 연결합니다.
목적
Sidebar는 제품의 주요 영역과 현재 위치를 안정적으로 연결하는 navigation frame이다. 넓은 화면에서는 반복 업무의 위치를 고정하고, 좁은 화면에서는 Sheet로 적응한다. 기능 수가 많아도 현재 영역, 다음 이동, 본문 공간의 우선순위가 차분하게 유지되어야 한다.
선택 기준
- 여러 화면에서 반복되는 제품 전역 또는 작업 영역 navigation에 사용한다.
- 한 페이지 안의 짧은 section 목차에는 별도 local navigation을 사용한다.
- 제품이 상태 보존이나 단축키를 실제로 필요로 할 때만 cookie와 global shortcut을 명시적으로 켠다.
- 본문보다 브랜드 표현이 먼저 보이는 큰 장식 영역으로 사용하지 않는다.
구조
전체를 SidebarProvider로 감싸고 Sidebar와 SidebarInset을 형제로 둔다. Sidebar 안은 SidebarHeader, SidebarContent, SidebarFooter로 나누며 Content에는 SidebarGroup, SidebarGroupLabel, SidebarGroupContent를 사용한다.
메뉴는 SidebarMenu → SidebarMenuItem → SidebarMenuButton 순서다. 보조 action, badge, loading placeholder에는 각각 SidebarMenuAction, SidebarMenuBadge, SidebarMenuSkeleton을 사용한다. 하위 navigation은 SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton으로 구성한다. SidebarTrigger는 keyboard와 touch에서 sidebar를 여는 주 control이고 SidebarRail은 pointer 보조 control이다.
변형과 크기
Sidebar props:
side:left또는right; 기본leftvariant:sidebar,floating,inset; 기본sidebarcollapsible:offcanvas,icon,none; 기본offcanvas
기본 desktop 너비는 16rem, mobile Sheet 너비는 18rem, icon collapse 너비는 3rem이다. SidebarMenuButton은 default | outline variant와 sm | default | lg size를 제공한다. SidebarMenuSubButton size는 sm | md다.
상태와 동작
Provider는 expanded/collapsed, mobile open 상태를 관리한다. defaultOpen, open, onOpenChange로 제어·비제어 구성이 가능하다. stateCookie를 주면 상태 변경 시 cookie를 쓰지만 초기 cookie를 읽지는 않으므로 초기값 복원은 소비 제품이 책임진다. keyboardShortcut은 기본 false다. 문자열을 지정하면 Ctrl 또는 Command와 해당 key 조합으로 toggle하며 input, select, textarea, content-editable 안에서는 동작하지 않는다.
768px 미만에서는 collapsible Sidebar가 Sheet로 열린다. collapsible="none"은 responsive Sheet 전환 없이 고정 영역으로 렌더한다. useSidebar는 현재 state와 toggle 함수를 제공하며 Provider 밖에서 호출하면 오류가 발생한다.
콘텐츠
Group label과 menu label은 사용자가 이해하는 짧고 안정적인 명사를 사용한다. 현재 항목은 isActive로 표시하고 위치를 text와 구조로도 이해할 수 있게 한다. badge는 수량 같은 보조 정보에만 사용한다. navigation 이름을 내부 조직명이나 route 이름과 동일하게 둘 필요는 없다.
접근성
SidebarTrigger의 label 기본값은 영문이므로 제품 언어에 맞는 값을 명시한다. icon collapse에서 SidebarMenuButton의 tooltip을 제공하되 Tooltip이 유일한 이름이 되지 않도록 실제 link text를 DOM에 유지한다. SidebarRail은 tabIndex={-1}인 pointer 보조 수단이므로 keyboard 사용자를 위해 Trigger를 반드시 제공한다. hover에서만 나타나는 MenuAction도 focus-within에서 보여야 하며 필수 action을 완전히 숨기지 않는다.
좁은 화면과 긴 콘텐츠
좁은 화면에서는 본문 공간을 유지하기 위해 Sidebar가 Sheet로 바뀐다. menu text는 마지막 span을 말줄임 처리하므로 서로 비슷한 긴 이름은 앞부분에서 구분되게 쓴다. group 수가 많으면 사용자 업무 흐름에 따라 재분류하고 scroll 영역 안에서 현재 위치가 사라지지 않는지 확인한다.
공개 API
- 상태:
SidebarProvider,SidebarProviderProps,SidebarStateCookie,SidebarContextProps,useSidebar - frame:
Sidebar,SidebarInset,SidebarTrigger,SidebarTriggerProps,SidebarRail,SidebarRailProps - 영역:
SidebarHeader,SidebarContent,SidebarFooter,SidebarSeparator,SidebarInput - group:
SidebarGroup,SidebarGroupLabel,SidebarGroupAction,SidebarGroupContent - menu:
SidebarMenu,SidebarMenuItem,SidebarMenuButton,SidebarMenuAction,SidebarMenuBadge,SidebarMenuSkeleton - sub menu:
SidebarMenuSub,SidebarMenuSubItem,SidebarMenuSubButton
SidebarMenuButton은 asChild, isActive, tooltip, variant, size를 지원한다. SidebarMenuAction은 asChild, showOnHover, Skeleton은 showIcon을 지원한다.
예제
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from '@classum/dot-design-system/ui';
export function ProductFrame() {
return (
<SidebarProvider stateCookie={{ name: 'daily_sidebar' }} keyboardShortcut="d">
<Sidebar collapsible="offcanvas">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>운영</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild isActive>
<a href="/curriculums">
<span>커리큘럼</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<header className="flex h-14 items-center px-4">
<SidebarTrigger label="내비게이션 열기" />
</header>
<main className="p-4">현재 화면</main>
</SidebarInset>
</SidebarProvider>
);
}피해야 할 사용
- 제품마다 다른 위치와 이름으로 같은 navigation을 재구성하지 않는다.
- cookie 이름을 namespace 없이 공용 이름으로 사용하지 않는다.
- 단축키를 입력 중에도 가로채는 custom handler를 추가하지 않는다.
- collapse 상태에서 text label 없이 icon과 색만으로 항목을 구분하지 않는다.
- SidebarRail만 두고 keyboard로 사용할 Trigger를 생략하지 않는다.
- 캐릭터, gradient, motion을 일반 navigation 배경 장식으로 사용하지 않는다.