Toolbar
컨트롤을 한 줄로 배치하는 바입니다. 애플리케이션 헤더, 페이지의 액션 줄, 편집기 아래의 상태 띠에 씁니다.
tsx
import { Toolbar } from 'neba';
<Toolbar render={<header />} start={<Logo />} end={<Button>배포</Button>}>
워크스페이스
</Toolbar>;Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'outline' | 'text' | 'outline' | 바의 무게. 컨테이너의 방식대로 시트에 색을 들이지 않습니다. 툴바는 남의 컨트롤을 담고, 그 컨트롤들은 자기 색을 가지고 옵니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 여백과 모서리의 스케일. 높이는 아닙니다. 툴바는 안에 든 컨트롤 높이에 여백을 더한 만큼 높습니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 별도의 dense prop이 없는 이유가 이것입니다 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 고정된 바에서도 기본이 0입니다. 헤더 밑의 그림자는 "아래에 내용이 있다"는 말이고, 그것은 페이지를 스크롤한 뒤에야 참입니다 |
| position공통 | 'static' | 'sticky' | 'fixed' | 'static' | 페이지 스크롤 안에서 어떻게 앉는지. sticky는 자기 자리를 차지한 채 가장자리에서 멈추므로 아래 내용에 여백을 줄 필요가 없고, fixed는 흐름에서 완전히 빠지므로 페이지가 스스로 여백을 마련해야 합니다 |
| side공통 | 'top' | 'bottom' | 'top' | static이 아닐 때 어느 가장자리에 붙는지 |
| divider | boolean | false | 내용을 마주 보는 쪽 가장자리에 헤어라인을 긋습니다. top 바는 아래, bottom 바는 위 |
| start | ReactNode | — | 바의 앞쪽에 고정, 로고, 제목, 뒤로 가기 |
| end | ReactNode | — | 뒤쪽에 고정, 액션들 |
| render | useRender.RenderProp | — | div 대신 다른 요소로 렌더링합니다 (render={<header />}). Base UI의 render prop 그대로 |
| children | ReactNode | — | 가운데. start와 end가 남긴 폭을 전부 가져갑니다 |
<div>의 native 속성은 그대로 전달됩니다.
자리는 셋입니다. start와 end가 각자의 끝에 고정되고 children이 남은 폭을 가져가므로, 간격을 벌리기 위한 spacer를 따로 넣을 필요가 없습니다.
예시
size와 density
Toolbar에는 고정 높이가 없습니다. 안에 든 컨트롤 높이에 여백을 더한 만큼 높아지고, 그 여백을 size와 density가 정합니다. density="compact"는 타입 스케일을 건드리지 않고 여백만 줄입니다.
tsx
import { Button, ButtonGroup, TextField, Toolbar, Typography } from 'neba';
const DENSITIES = ['default', 'compact'] as const;
/**
* A Toolbar has no height of its own. It is as tall as the controls in it plus
* its padding — and `density` is that padding, which is the only thing it is
* ever allowed to change.
*/
export default function ToolbarDensity() {
return (
<div className="flex w-full flex-col gap-5">
{DENSITIES.map((density) => (
<div key={density} className="flex flex-col gap-2">
<Typography level="caption">density="{density}"</Typography>
<Toolbar
density={density}
className="w-full"
start={<Typography level="h6">Files</Typography>}
end={
<ButtonGroup variant="outline" size="sm" color="secondary">
<Button>Rename</Button>
<Button>Move</Button>
</ButtonGroup>
}
>
<TextField size="sm" placeholder="Filter…" fullWidth />
</Toolbar>
</div>
))}
</div>
);
}position과 side
position은 CSS의 세 값을 그대로 씁니다.
sticky: 애플리케이션 헤더에 적합합니다. 자기 자리를 차지하므로 아래 콘텐츠에 여백을 줄 필요가 없습니다.fixed: 흐름에서 빠지므로, 페이지가 스스로 여백을 마련하지 않으면 첫 화면이 바 뒤에 깔립니다.
side는 바가 붙을 가장자리입니다. 고정된 바는 모서리 반경을 버립니다. 화면 가장자리의 둥근 모서리는 뒤에 아무것도 없는 틈이 됩니다.
tsx
import { Button, Toolbar, Typography } from 'neba';
/**
* `position="sticky"` is what an application header usually wants: it takes up
* its own space, so nothing underneath has to be padded around it. `divider`
* gives it the rule that says there is content beneath.
*
* The box below scrolls; the bar does not.
*/
export default function ToolbarSticky() {
return (
<div className="h-64 w-full overflow-y-auto rounded-(--neba-radius-md) [border:1px_solid_var(--neba-border)]">
<Toolbar
position="sticky"
divider
variant="solid"
density="compact"
start={<Typography level="h6">Changelog</Typography>}
end={
<Button size="sm" variant="text">
Subscribe
</Button>
}
/>
<div className="flex flex-col gap-3 p-4">
{Array.from({ length: 12 }, (_, index) => (
<Typography key={index} level="body">
{`1.0.${12 - index} — a release note long enough to make the page scroll under the bar.`}
</Typography>
))}
</div>
</div>
);
}divider와 elevation
divider는 바 아래에 선을 그어 아래에 콘텐츠가 있음을 보여 줍니다. 고정된 바에서도 elevation은 0으로 남으므로, 스크롤에 맞춰 직접 올리거나 divider를 켜세요.
color
color는 선과 focus ring에 적용됩니다. 남의 컨트롤을 담는 바이므로 sheet 자체는 색으로 채우지 않습니다.
접근성
role="toolbar"를 붙이지 않습니다. 그 role은 바 전체가 tab 정지 하나이고 내부를 방향키로 이동한다는 약속이므로, 구현하지 않은 채 주장하면 키보드 사용자에게 오히려 방해가 됩니다.- 페이지 헤더로 쓸 때는
render={<header />}를 주세요. 실제 landmark가 되어 screen reader 사용자가 이동 기준으로 쓸 수 있습니다. - 방향키로 이동하는 컨트롤 묶음이 필요하다면 ButtonGroup을 쓰세요.