hiip/ui
14 items

Components / Data

Data Table

A TanStack table shell: column pinning, drag-to-reorder, a selection rail and skeleton rows.

Creator 01tiktokNegotiating
Creator 02whatsappReplied
Creator 03viberClosed
Page 1 of 1
demos.tsx
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-table

Or 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.

PropTypeDefault
rowKeyrequired

Stable row key — the index is not one once sorting moves rows around.

(row: Row) => string | number—
tablerequiredTable<Row>—
autoHeight

Size the box to its ROWS instead of filling the space its parent gives it.

boolean | undefined—
classNamestring | undefined—
columnRules

Draw a hairline between every column — see `COLUMN_RULE`.

boolean | undefinedtrue
disabled

Freeze the PAGER while the page is busy — a load in flight, or a write that will change what is on the page.

boolean | undefined—
emptyHintstring | undefined"Nothing here yet."
emptyIconIconSvgElement | undefinedInboxIcon
emptyTitle

The empty state.

string | undefined"No results"
error

A load FAILURE, never "nothing matched" — see the note on the error row below.

string | null | undefined—
footerLabel

Left side of the pager, e.g. "12 of 40 creators".

ReactNode—
isLoadingboolean | undefined—
manualPager

Server-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—
pinnedThrough

How 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.

PropTypeDefault
tablerequiredTable<Row>—
pinning

Omit 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