FilterTabs
Mẫu chuẩnMột hàng tab dùng để lọc một danh sách, không phải để đổi vùng nội dung. Mỗi tab có thể kèm một số đếm. Composite báo một giá trị, là khoá của bộ lọc đang chọn, qua một callback.
Playground
Đổi brand và theme rồi chuyển bộ lọc để xem tên trợ năng ghép số đếm đúng cách.
Đang chọn "Tất cả". Composite không render danh sách đã lọc — host tự render bên dưới.
import { FilterTabs } from "@nexobox/ui";
const [value, setValue] = useState("all");
<FilterTabs
aria-label="Lọc đề xuất"
value={value}
onValueChange={setValue}
options={[
{ value: "all", label: "Tất cả", count: 12 },
{ value: "pending", label: "Đang chờ", count: 3 },
{ value: "rejected", label: "Đã từ chối" },
]}
/>Bài toán
Một danh sách đề xuất hay hoạt động cần lọc theo trạng thái. Người dùng cần thấy mỗi bộ lọc có bao nhiêu mục trước khi chọn. Tabs giải được việc chọn, nhưng không có chỗ đặt số đếm, và trình đọc màn hình sẽ đọc "Đang chờ3" nếu ghép chữ tay. Composite gắn số đếm vào tab theo một cách, kèm tên trợ năng đúng.
Khi nào sử dụng
Nên dùng
Hai đến năm bộ lọc loại trừ nhau trên cùng một danh sách. Khi số đếm của từng bộ lọc giúp người dùng quyết định chọn tab nào.
Khi nào không dùng
Tránh dùng
| Không dùng cho | Dùng thay |
|---|---|
| Đổi giữa các vùng nội dung khác nhau (Tổng quan / Cài đặt) | Tabs |
| Nhiều bộ lọc chọn cùng lúc | CheckboxCardGroup hoặc một menu lọc |
| Trên năm lựa chọn | Select |
Dựng từ
| Primitive tầng 1 | Vai trong composite |
|---|---|
| Tabs, TabsList, TabsTrigger | Hàng tab, chọn một, điều hướng bằng mũi tên (Radix Tabs). |
| Badge | Số đếm, variant="quiet". Nền mute, không viền. Không dùng variant trạng thái cho số. |
| FilterTabs thêm gì |
|---|
| Số đếm trong tab, kèm tên trợ năng "Đang chờ, 3 mục" thay vì đọc liền chữ |
Không render TabsContent: danh sách đã lọc do host render bên dưới |
Anatomy
Tab được chọn hiển thị theo TabsTrigger, composite không vẽ lại. Số đếm nằm sau nhãn, cách space-2.
States
| Trạng thái | Biểu hiện |
|---|---|
| Chọn / không chọn | data-state="active" của Radix |
| Hover, focus | Từ TabsTrigger |
| Số đếm bằng 0 | Vẫn hiện "0". Composite không tự ẩn tab |
| Không có số đếm | Tab không kèm Badge |
Bàn phím & trợ năng
Seam — host sở hữu gì
Composite không lọc dữ liệu, không đếm, không đồng bộ với URL. Không fetch, không router, không next.
| Host sở hữu | Cấp qua |
|---|---|
| Danh sách bộ lọc | options |
| Số đếm (tính từ dữ liệu) | options[].count |
| Giá trị đang chọn | value / onValueChange |
| Render danh sách đã lọc | Host, bên dưới FilterTabs |
API & triển khai
| Prop | Mặc định | Quy ước |
|---|---|---|
| options | — | Bắt buộc. ReadonlyArray<{ value: string; label: string; count?: number }>. Từ 2 đến 5 mục. |
| value | — | Bắt buộc. |
| onValueChange | — | Bắt buộc. (value: string) => void. |
| aria-label | — | Bắt buộc. Ví dụ "Lọc đề xuất". |
Design tokens
Hàng tab đọc --nf-component-tabs-list-gap và --nf-component-tabs-trigger-padding-inline. Số đếm là Badge quiet: --nf-semantic-surface-subtle, --nf-semantic-text-muted, không viền. Khoảng nhãn–số là --nf-foundation-space-2 trên trigger. Chiều cao là chiều cao của TabsTrigger; composite không ghi đè.
Do / Don't
Nên dùng
Nhãn là trạng thái: "Đang chờ". Hiện "0" khi bộ lọc trống. Dùng cho một danh sách.
Tránh dùng
Nhãn chứa số: "Đang chờ (3)" — số đếm là prop count. Ẩn tab khi trống — người dùng mất dấu bộ lọc. Dùng thay Tabs để đổi trang con.