@olundot/ui · Navigation/Data
BulkActionBar
선택된 항목 수와 일괄 명령을 하단 floating pill 또는 목록 위 inline 띠로 제공하는 독립형 선택 액션바입니다.
- count
- `N개 선택됨` 문구를 tabular number로 표시하며 count가 0이면 기본적으로 렌더링하지 않습니다.
- actions
- 각 action은 key, label, onClick, optional icon, variant, disabled, title을 갖습니다.
- emptyHint
- 모바일 선택 모드처럼 count가 0이어도 bar가 필요할 때 안내 문구를 표시합니다.
- placement
- 테이블 선택의 유일한 일괄 액션 표면입니다. 기본 floating은 sticky bottom, mx-auto, w-fit 배치와 safe-area 하단 여백을 포함합니다. inline은 툴바 아래 상태 줄 자리에 sticky top 띠로 들어가며, 목록보다 앞에 두어 키보드 순서상 행보다 먼저 닿습니다.
- unit · summary
- unit으로 `12명 선택됨`처럼 단위를 바꾸고(기본 `개`), summary 자리에 '이 조건의 N명 모두 선택' 같은 제안을 둡니다. inline에서 폭이 모자라면 summary는 줄바꿈하지 않고 잘리며, compactSummary(짧은 문구나 aria-label을 단 IconButton)를 주면 그것으로 바뀝니다.
- idleContent
- 선택 0일 때 bar 대신 보여 줄 개수 표시(예: `100 / 3,214명`)입니다. emptyHint보다 우선하며, 둘 다 없으면 숨깁니다.
- trailing
- 선택 해제(✕) 뒤에 붙는 자리입니다(0.17.1). 편집 모드 [편집]/[완료] 토글처럼 선택과 별개인 컨트롤을 둡니다. inline 띠·floating pill은 ✕ 바로 뒤, idle 상태 줄은 오른쪽 끝이며, 상태 줄에서는 role="status" 밖이라 개수만 다시 읽힙니다.
선택 상태
count, actions, onClear코드 보기tsx
import { BulkActionBar } from "@olundot/ui";
export function SelectedBulkActionBar() {
return (
<BulkActionBar
count={3}
onClear={() => undefined}
actions={[
{ key: "message", label: "메시지", onClick: () => undefined },
{ key: "archive", label: "보관", onClick: () => undefined },
{ key: "remove", label: "제외", variant: "destructive", onClick: () => undefined },
]}
/>
);
}비활성 액션
disabled, title코드 보기tsx
import { BulkActionBar } from "@olundot/ui";
export function DisabledBulkActionBar() {
return (
<BulkActionBar
count={2}
onClear={() => undefined}
actions={[
{ key: "publish", label: "게시", disabled: true, title: "검토 완료 후 사용할 수 있습니다", onClick: () => undefined },
{ key: "delete", label: "삭제", variant: "destructive", onClick: () => undefined },
]}
/>
);
}빈 선택 모드
emptyHint코드 보기tsx
import { BulkActionBar } from "@olundot/ui";
export function EmptyModeBulkActionBar() {
return (
<BulkActionBar
count={0}
emptyHint="선택 모드"
onClear={() => undefined}
actions={[{ key: "message", label: "메시지", onClick: () => undefined }]}
/>
);
}상단 일괄 작업 띠
placement="inline", unit, summary, compactSummary, idleContent12명 선택됨
코드 보기tsx
import { useState } from "react";
import { ListChecks } from "lucide-react";
import { BulkActionBar, Button, IconButton } from "@olundot/ui";
export function InlineBulkActionBar() {
const [count, setCount] = useState(12);
return (
<div className="flex flex-col gap-3">
<BulkActionBar
placement="inline"
count={count}
unit="명"
idleContent="100 / 3,214명"
summary={
<Button variant="link" size="sm" className="h-auto p-0">
이 조건의 3,214명 모두 선택
</Button>
}
compactSummary={
<IconButton size="sm" variant="ghost" aria-label="이 조건의 3,214명 모두 선택" icon={ListChecks} />
}
onClear={() => setCount(0)}
actions={[
{ key: "grade", label: "학년·분반 변경", onClick: () => undefined },
{ key: "delete", label: "삭제", variant: "destructive", onClick: () => undefined },
]}
/>
{/* 목록은 띠 아래에 둔다 — 키보드 순서상 일괄 작업이 행보다 앞선다. */}
<Button variant="outline" size="sm" className="self-start" onClick={() => setCount((value) => (value === 0 ? 12 : 0))}>
{count === 0 ? "12명 선택" : "선택 비우기"}
</Button>
</div>
);
}편집 모드 토글
trailing, idleContent, placement="inline"코드 보기tsx
import { useState } from "react";
import { BulkActionBar, Button } from "@olundot/ui";
export function EditModeBulkActionBar() {
const [editing, setEditing] = useState(false);
const [count, setCount] = useState(0);
return (
<div className="flex flex-col gap-3">
<BulkActionBar
placement="inline"
count={count}
unit="명"
idleContent="100 / 3,214명"
// ✕ 뒤(선택 전에는 상태 줄 오른쪽 끝). 라이브 영역 밖이라 개수만 다시 읽힌다.
trailing={
<Button
variant="outline"
size="sm"
aria-pressed={editing}
onClick={() => {
setEditing((value) => !value);
setCount(0);
}}
>
{editing ? "완료" : "편집"}
</Button>
}
onClear={() => setCount(0)}
actions={[{ key: "grade", label: "학년·분반 변경", onClick: () => undefined }]}
/>
<Button
variant="outline"
size="sm"
className="self-start"
disabled={!editing}
onClick={() => setCount((value) => (value === 0 ? 12 : 0))}
>
{count === 0 ? "12명 선택" : "선택 비우기"}
</Button>
</div>
);
}