Toolbar
컨트롤이 늘어선 바: 애플리케이션 헤더, 페이지의 액션 줄, 편집기 아래를 가로지르는 띠.
import { Toolbar } from 'neba';
<Toolbar render={<header />} start={<Logo />} end={<Button>배포</Button>}>
워크스페이스
</Toolbar>;자리 셋과 한 줄. start와 end는 각자의 끝에 고정되고 children이 남은 폭을 가져갑니다. 지금까지의 모든 툴바가 가졌던 배치이므로, 쓰는 쪽과 그 사람이 잊지 않고 넣어야 할 스페이서 <div>에 맡기지 않고 여기서 배치합니다.
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>의 네이티브 속성은 그대로 전달됩니다.
예시
밀도
Toolbar에는 자기 높이가 없습니다. 안에 든 컨트롤 높이에 여백을 더한 만큼 높고, 그 여백은 다른 모든 표면이 쓰는 size / density 짝입니다 — 그래서 같은 뜻의 두 번째 prop 없이도, 타입 스케일을 건드리지 않고도 density="compact"가 빽빽한 바를 줍니다.
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은 CSS 자신의 세 값이고, CSS가 쓰는 그대로 씁니다.
sticky는 애플리케이션 헤더가 대개 원하는 것입니다. 자기 자리를 차지하므로 아래에 있는 것에 여백을 줄 필요가 없습니다.fixed는 흐름에서 완전히 빠지므로, 페이지가 스스로 여백을 마련하지 않으면 첫 화면이 바 뒤에 깔립니다.
고정된 바는 반지름을 버립니다. 화면 가장자리에 맞닿은 둥근 모서리는 뒤에 아무것도 없는 틈이기 때문입니다. divider는 아래에 내용이 있다고 말해 주는 헤어라인을 그립니다.
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>
);
}고정된 바에서도 elevation은 0으로 남습니다. 헤더 밑의 그림자는 "이 아래에 내용이 있다"고 말하는 방법이고, 그것은 페이지를 스크롤한 뒤에야 참입니다 — 그러니 스크롤에 맞춰 직접 올리거나, 납작하게 두고 divider를 켜세요.
role="toolbar"가 없는 이유
그 role은 키보드 동작에 대한 약속입니다. 바 전체가 탭 정지 하나이고, 안의 컨트롤 사이는 화살표 키로 오간다는 약속. 그것을 구현하지 않은 채 주장하는 바는 아무것도 주장하지 않은 바보다 키보드 독자에게 나쁩니다.
페이지 헤더가 원하는 것은 render={<header />} — 진짜 랜드마크이고, 스크린 리더 사용자가 실제로 그것을 기준으로 이동합니다. 진짜 로빙 포커스 툴바가 원하는 것은 ButtonGroup이고, 그것이 바로 그것입니다.
Material UI에서 옮겨올 때
| MUI | Neba |
|---|---|
<AppBar><Toolbar>…</Toolbar></AppBar> | 컴포넌트 하나입니다. position이 그 위에 바로 있습니다 |
position="sticky" | 같습니다. 'static', 'sticky', 'fixed' |
variant="dense" | density="compact" — 여백이고, density가 바꿔도 되는 것은 그것뿐입니다 |
color="primary" | color는 헤어라인과 포커스 링에 닿습니다. 남의 컨트롤을 담는 바는 자기 시트에 색을 들이지 않습니다 |
elevation={4} | elevation은 0–3이고, 0은 그림자 없음입니다 |
스페이서로 쓰는 <Box sx={{ flexGrow: 1 }} /> | 필요 없습니다. start, children, end가 세 자리입니다 |