registry:block
Filter Table
Status chips that filter a task table in place.
Rendered example
TaskDateStatusOwner
Wire billing webhookOct 02To doS. Kim
Migrate auth to sessionsSep 28In ProgressA. Reyes
Draft Q4 roadmapOct 09To doM. Diallo
Fix flaky checkout testsSep 30In ProgressJ. Park
Ship onboarding v2Sep 14CompletedL. Fontaine
Audit CDN spendOct 05In ProgressT. Nakamura
Install
orion-themebadge
Open registry JSON registry/blocks/filter-table.tsx
"use client"
import * as React from "react"
import { Badge } from "@/registry/ui/badge"
/**
* FILTER TABLE
* Status chips directly filter a task table.
*/
type Status = "todo" | "progress" | "done"
const FILTERS: { key: "all" | Status; label: string; dot?: string; count: number }[] = [
{ key: "all", label: "All", count: 6 },
{ key: "todo", label: "To do", dot: "var(--orange)", count: 2 },
{ key: "progress", label: "In Progress", dot: "var(--accent)", count: 3 },
{ key: "done", label: "Completed", dot: "var(--green)", count: 1 },
]
const ROWS: { task: string; date: string; status: Status; owner: string }[] = [
{ task: "Wire billing webhook", date: "Oct 02", status: "todo", owner: "S. Kim" },
{ task: "Migrate auth to sessions", date: "Sep 28", status: "progress", owner: "A. Reyes" },
{ task: "Draft Q4 roadmap", date: "Oct 09", status: "todo", owner: "M. Diallo" },
{ task: "Fix flaky checkout tests", date: "Sep 30", status: "progress", owner: "J. Park" },
{ task: "Ship onboarding v2", date: "Sep 14", status: "done", owner: "L. Fontaine" },
{ task: "Audit CDN spend", date: "Oct 05", status: "progress", owner: "T. Nakamura" },
]
const PILLS: Record<Status, { label: string; className: string }> = {
todo: { label: "To do", className: "bg-orange-tint text-orange" },
progress: { label: "In Progress", className: "bg-accent-tint text-accent-ink" },
done: { label: "Completed", className: "bg-green-tint text-green" },
}
export function FilterTable() {
const [filter, setFilter] = React.useState<"all" | Status>("all")
return (
<div className="w-full max-w-105">
<div className="-mx-1 mb-1 flex items-center gap-1 overflow-x-auto px-1 py-1" style={{ scrollbarWidth: "none" }}>
{FILTERS.map((f) => {
const active = filter === f.key
return (
<Badge key={f.key} active={active} dotColor={f.dot} count={f.count} onClick={() => setFilter(f.key)}>
{f.label}
</Badge>
)
})}
</div>
<div
aria-label="Scrollable task table"
className="overflow-x-auto rounded-card bg-surface shadow-card"
role="region"
style={{ scrollbarWidth: "none" }}
>
<div className="min-w-[420px]">
<div className="grid grid-cols-[1.3fr_0.6fr_0.95fr_0.9fr] border-b border-line px-3 py-2 text-[11.5px] font-medium text-ink-3">
<span>Task</span>
<span>Date</span>
<span>Status</span>
<span>Owner</span>
</div>
{ROWS.map((row) => {
const shown = filter === "all" || row.status === filter
const pill = PILLS[row.status]
return (
<div
key={row.task}
className="grid transition-[grid-template-rows,opacity] duration-300"
style={{
gridTemplateRows: shown ? "1fr" : "0fr",
opacity: shown ? 1 : 0,
transitionTimingFunction: "cubic-bezier(0.23, 1, 0.32, 1)",
}}
>
<div className="overflow-hidden">
<div className="grid grid-cols-[1.3fr_0.6fr_0.95fr_0.9fr] items-center border-b border-line px-3 py-2 text-[12px] transition-colors duration-100 last:border-0 hover:bg-hover">
<span className="truncate font-medium text-ink">{row.task}</span>
<span className="text-ink-2 tabular-nums">{row.date}</span>
<span>
<Badge
variant="status"
tone={row.status === "todo" ? "orange" : row.status === "progress" ? "accent" : "green"}
>
{pill.label}
</Badge>
</span>
<span className="truncate text-ink-2">{row.owner}</span>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
)
}