Sidebar
페이지 내용 옆의 열이며, 창이 좁아져 열을 담을 수 없게 되면 drawer가 됩니다. 실제 <aside>를 렌더링하며 이는 complementary 랜드마크입니다.
import { List, ListItem, Sidebar } from 'neba';
<Sidebar label="목차">
<List variant="text">
<ListItem href="/overview" selected>
개요
</ListItem>
<ListItem href="/components">컴포넌트</ListItem>
</List>
</Sidebar>;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| children | ReactNode | — | 안에 들어가는 것, 탐색, 필터 패널, 목차 |
| side공통 | 'start' | 'end' | 'start' | 어느 끝을 차지하는지. 물리적이 아니라 논리적이라 RTL에서 뒤집힙니다. PageLayout 안에서는 어느 자리에 넘겼는지로 이미 정해집니다 |
| width | number | string | size | 열의 너비. 숫자는 픽셀. resizable일 때는 시작 너비이며, 드래그가 이 값을 덮어씁니다 |
| resizable | boolean | false | 안쪽 가장자리를 끌어 너비를 바꿀 수 있게 합니다. 키보드에서는 좌우 화살표 |
| minWidth | number | string | 160 | 드래그로 좁힐 수 있는 한계 |
| maxWidth | number | string | 480 | 넓힐 수 있는 한계 |
| onResize | (width: number) => void | — | 끄는 동안 매 걸음, 픽셀 단위로 |
| onResizeEnd | (width: number) => void | — | 놓을 때 한 번. 너비를 저장해 둘 자리 |
| collapseBelow | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none' | PageLayout | 이 너비보다 좁아지면 열이 아니라 drawer가 됩니다. PageLayout의 값을 물려받으며, PageLayout 밖에서는 none입니다. 여는 버튼 없이 접히면 사이드바에 닿을 방법이 없기 때문입니다 |
| open | boolean | — | drawer가 열려 있는지. 접힌 뒤에만 의미가 있습니다. PageLayout 안에서는 레이아웃이 이 상태를 가지므로 거기서 다루세요 |
| defaultOpen | boolean | false | 레이아웃 밖에서 쓸 때의 처음 상태 |
| onOpenChange | (open: boolean) => void | — | 열리고 닫힐 때. 어느 쪽이 상태를 갖든 항상 호출됩니다 |
| sticky | boolean | true | 페이지가 지나가는 동안 자리를 지키는지. 헤더 아래에서 시작해 남은 창 높이만큼인 sticky 열이 됩니다 |
| title | ReactNode | — | drawer일 때만 그려지는 제목. 열에는 주위의 페이지가 그것이 무엇인지 말해 주지만, 페이지를 덮은 패널에는 없습니다 |
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 면의 무게. 패널은 색으로 물들지 않습니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 기본 너비와 안쪽 여백 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| divider | boolean | true | 내용을 마주보는 안쪽 가장자리의 헤어라인. 바깥쪽은 창에 닿아 있어 나눌 것이 없습니다 |
| padded | boolean | true | 안쪽 여백 |
| label | string | locale('Sidebar') | 영역의 이름. 사이드바가 둘인 페이지는 반드시 써야 합니다. 아니면 스크린 리더가 “complementary”라는 영역 두 개를 내놓습니다 |
| locale | string | PageLayout | 사이드바가 쓰는 단어의 언어. PageLayout 안에서는 물려받습니다 |
<aside>의 native 속성은 그대로 전달됩니다. 다만 color와 title은 예외입니다. 공통 축은 prop 규칙에서 설명합니다.
자기 children만 배치합니다. 페이지가 사이드바를 둘러싸도록 하려면 PageLayout의 sidebar나 endSidebar 자리에 넣으세요.
예시
width · size
size가 열의 기본 너비를 정하고(md는 16rem), width가 픽셀 숫자나 CSS 길이로 그것을 덮어씁니다.
resizable
안쪽 가장자리를 끌 수 있게 합니다. minWidth와 maxWidth가 범위를 정하고, onResize는 끄는 동안 매 걸음, onResizeEnd는 놓을 때 한 번 호출됩니다. 너비를 저장해 둘 자리입니다. 핸들은 focus를 받는 role="separator"라 좌우 화살표로도 같은 일을 할 수 있습니다.
import { useState } from 'react';
import { List, ListItem, Sidebar, Typography } from 'neba';
export default function SidebarResizable() {
const [width, setWidth] = useState(200);
return (
<div className="flex h-72 w-full overflow-hidden rounded-(--neba-radius-md) border border-(--neba-border)">
<Sidebar
collapseBelow="none"
resizable
width={200}
minWidth={140}
maxWidth={320}
onResize={setWidth}
>
<Typography level="overline">Files</Typography>
<List variant="text" size="sm">
<ListItem href="#">src</ListItem>
<ListItem href="#" selected>
package.json
</ListItem>
<ListItem href="#">README.md</ListItem>
</List>
</Sidebar>
<div className="min-w-0 flex-1 p-4">
<Typography level="h6">Drag the inner edge</Typography>
<Typography color="secondary">{Math.round(width)}px</Typography>
</div>
</div>
);
}side
왼쪽·오른쪽이 아니라 start와 end입니다. 탐색 레일은 어떤 쓰기 방향에서도 자기가 속한 본문 옆에 있기 때문입니다. PageLayout 안에서는 어느 자리에 넣었는지가 정해 주므로 이 prop이 필요 없습니다.
import { List, ListItem, Sidebar, Typography } from 'neba';
export default function SidebarSides() {
return (
<div className="flex h-72 w-full overflow-hidden rounded-(--neba-radius-md) border border-(--neba-border)">
<Sidebar collapseBelow="none" width={160} label="Sections">
<Typography level="overline">Sections</Typography>
<List variant="text" size="sm">
<ListItem href="#" selected>
Overview
</ListItem>
<ListItem href="#">Props</ListItem>
</List>
</Sidebar>
<div className="min-w-0 flex-1 p-4">
<Typography level="h6">Two sidebars</Typography>
<Typography color="secondary">Each has its own width and its own drawer.</Typography>
</div>
<Sidebar collapseBelow="none" side="end" width={160} label="On this page" variant="text">
<Typography level="overline">On this page</Typography>
<List variant="text" size="sm">
<ListItem href="#">Props</ListItem>
<ListItem href="#">Examples</ListItem>
</List>
</Sidebar>
</div>
);
}collapseBelow
열이 scrim 위의 Drawer가 되는 너비입니다. focus가 안에 갇히고, Escape로 닫히며, 닫으면 focus가 trigger로 돌아갑니다. 어느 모습이든 children은 문서에 한 번만 존재합니다. title은 drawer일 때만 그려집니다. 열에는 주위의 페이지가 그것이 무엇인지 말해 주지만, 페이지를 덮은 패널에는 없기 때문입니다.
기본값은 PageLayout의 값이며 레이아웃 밖에서는 none입니다. 되돌릴 방법이 없는 채로 접힌 사이드바는 독자가 잃어버린 사이드바이기 때문입니다.
sticky
기본값은 켜짐입니다. 페이지가 스크롤될 때는 header 아래에서 시작해 남은 창 높이만큼인 sticky 열이 되고, 내용만 스크롤될 때는 이미 레이아웃 높이만큼이라 아무것도 달라지지 않습니다.
SidebarTrigger
창이 좁아져 담을 수 없게 된 사이드바를 다시 불러오는 버튼입니다. Header의 brand 자리, 로고 앞에 두세요.
import { Header, PageLayout, Sidebar, SidebarTrigger } from 'neba';
<PageLayout header={<Header brand={<SidebarTrigger />} />} sidebar={<Sidebar>…</Sidebar>}>
페이지
</PageLayout>;| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| side공통 | 'start' | 'end' | 'start' | 레이아웃의 두 사이드바 중 어느 쪽을 여는지 |
| collapseBelow | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none' | PageLayout | 버튼이 나타나는 너비. 사이드바가 접히는 그 너비이며, PageLayout에서 정하는 것이 맞습니다 |
| icon | ReactNode | 햄버거 | 글리프. 기본은 세 줄 |
| label | string | locale('Open sidebar') | 하는 일을 말로. 기본값은 열림 상태에 따라 “사이드바 열기”와 “사이드바 닫기” |
| locale | string | PageLayout | 그 단어의 언어 |
IconButton이 받는 나머지 prop은 그대로 전달됩니다. 열 대상이 있으려면 PageLayout 안에 있어야 하며, 밖에서는 아무것도 그리지 않습니다. breakpoint 이상에서는 없어지는 대신 class로 숨겨지므로, 페이지가 도착하고 잠시 뒤에 header 안으로 튀어나오는 일이 없습니다.
접근성
<aside>(complementary랜드마크)를 렌더링하고,label이 없으면locale의 "사이드바"에 해당하는 단어로 스스로를 이름 짓습니다. 사이드바가 둘인 페이지는 반드시 둘 다 이름을 주어야 합니다.- 접힌 상태는 modal dialog입니다. focus가 안에 갇히고, Escape로 닫히며, focus는 trigger로 돌아갑니다.
- 크기 조절 핸들은
tabindex="0"인role="separator"이며locale이 이름을 붙입니다. 좌우 화살표가 16px씩 움직입니다. locale은 PageLayout에서 물려받으므로 페이지당 한 번만 씁니다.