Components / Data
Data Table
A TanStack table shell: column pinning, drag-to-reorder, a selection rail and skeleton rows.
| Creator 01 | tiktok | Negotiating |
| Creator 02 | Replied | |
| Creator 03 | viber | Closed |
import {
type ColumnDef,
type SortingState,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { useState } from "react";
import { DataTable } from "@/components/ui/hiip-ui/data-table";
type DemoRow = { creator: string; platform: string; stage: string };
// ⚠️ Module scope, NOT inside the component, and this is not a style choice.
// `useReactTable` compares `columns` and `data` BY REFERENCE. Rebuilt on every
// render, the first sort click sets state, the re-render hands the table two
// fresh arrays, the table re-resolves and schedules another render — and the
// tab locks up with no error in the console. Stable identities break the loop.
const DEMO_COLUMNS: ColumnDef<DemoRow>[] = [
{ accessorKey: "creator", header: "Creator" },
{
accessorKey: "platform",
header: "Platform",
// A custom cell renderer, to show the shell does not care what a cell
// holds. Kept self-contained: a demo that reaches for another registry
// item ties two items' lifetimes together.
cell: (ctx) => (
<span className="rounded-md bg-muted px-1.5 py-0.5 font-mono text-xs">
{ctx.row.original.platform}
</span>
),
},
{ accessorKey: "stage", header: "Stage" },
];
// Placeholders, not invented people: a docs demo that puts plausible names on
// screen reads as real data the moment it is screenshotted into a deck.
const DEMO_ROWS: DemoRow[] = [
{ creator: "Creator 01", platform: "tiktok", stage: "Negotiating" },
{ creator: "Creator 02", platform: "whatsapp", stage: "Replied" },
{ creator: "Creator 03", platform: "viber", stage: "Closed" },
];
function DataTableDemo() {
// The shell renders a sort control for every column that CAN sort, so the
// caller owes it a sorted row model and somewhere to keep the state — with
// neither, the header is a button that does nothing.
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data: DEMO_ROWS,
columns: DEMO_COLUMNS,
state: { sorting },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
return (
<div className="w-full">
<DataTable table={table} rowKey={(r) => r.creator} autoHeight />
</div>
);
}Install — needs @hiip registered in components.json
bunx shadcn@latest add @hiip/data-tableOr by URL, with the token in the query
bunx shadcn@latest add "https://ui.hiip.ai/r/data-table.json?token=$HIIP_REGISTRY_TOKEN"Both need a token — set one up.
Anatomy
Import from components/ui/hiip-ui/data-table.
<DataTable
table={…} // a TanStack table instance the CALLER builds
rowKey={…}
emptyTitle="…"
emptyHint="…"
/>API Reference
Generated from the source. Props forwarded to the underlying element are not listed.
DataTable
The app's data-table shell: a `@tanstack/react-table` instance rendered through this repo's own `ui/table` primitives.
| Prop | Type | Default |
|---|---|---|
rowKeyrequiredStable row key — the index is not one once sorting moves rows around. | (row: Row) => string | number | — |
tablerequired | Table<Row> | — |
autoHeightSize the box to its ROWS instead of filling the space its parent gives it. | boolean | undefined | — |
className | string | undefined | — |
columnRulesDraw a hairline between every column — see `COLUMN_RULE`. | boolean | undefined | true |
disabledFreeze the PAGER while the page is busy — a load in flight, or a write that will change what is on the page. | boolean | undefined | — |
emptyHint | string | undefined | "Nothing here yet." |
emptyIcon | IconSvgElement | undefined | InboxIcon |
emptyTitleThe empty state. | string | undefined | "No results" |
errorA load FAILURE, never "nothing matched" — see the note on the error row below. | string | null | undefined | — |
footerLabelLeft side of the pager, e.g. "12 of 40 creators". | ReactNode | — |
isLoading | boolean | undefined | — |
manualPagerServer-cursor pagination: the caller owns both edges, and the TOTAL IS UNKNOWN. | { pageIndex: number; hasNextPage: boolean; onPrevious: () => void; onNext: () => void; } | undefined | — |
onRowClick | ((row: Row) => void) | undefined | — |
pinnedThroughHow far the frozen run reaches — the id of the LAST pinned column. | string | null | undefined | — |
DataTableColumnsMenu
The Columns control: which columns are drawn, in what ORDER, and how far the freeze reaches.
| Prop | Type | Default |
|---|---|---|
tablerequired | Table<Row> | — |
pinningOmit and the panel is show/hide + reorder only, with no freeze controls. | DataTablePinning | undefined | — |
- Type
registry:ui- Pulls in
- button, checkbox, popover, skeleton, tableshadcn installs these for you if they are missing.
- npm packages
@tanstack/react-table @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities @hugeicons/react @hugeicons/core-free-icons- Files written
- components/ui/hiip-ui/data-table.tsx