Data Table
v0.2.1TanStack-table wrapper with sorting, pagination, column toggle, row selection, and widget-owned loading/empty/error states.
Install
npx shadcn@latest add @signalos/data-tableRequires a configured @signalos registry and a valid SIGNALOS_REGISTRY_TOKEN - get access. Registry dependencies (@signalos/tokens, @signalos/utils, @signalos/button, @signalos/dropdown-menu, @signalos/select, @signalos/skeleton) are pulled automatically.
Preview
Example & code
data-table.example.tsx
// Example: three data-table variants from the same widget.
// This file is consumer-side code - copy it into a screen and adapt.
import { useState } from "react"
import type { ColumnDef, RowSelectionState } from "@tanstack/react-table"
import { DataTable } from "@/components/widgets/data-table/DataTable"
import { StatusBadge } from "@/components/widgets/status-badge/StatusBadge"
import type { Severity } from "@/components/widgets/status-badge/StatusBadge.types"
interface Signal {
id: string
title: string
owner: string
severity: Severity
}
const columns: ColumnDef<Signal, string>[] = [
{ accessorKey: "id", header: "ID" },
{ accessorKey: "title", header: "Title" },
{ accessorKey: "owner", header: "Owner" },
{
accessorKey: "severity",
header: "Severity",
// Screens map domain values → widget props right in the column def:
cell: ({ row }) => (
<StatusBadge severity={row.original.severity} appearance="chip" />
),
},
]
const signals: Signal[] = [
{
id: "SIG-1042",
title: "Latency spike on ingest queue",
owner: "M. Chen",
severity: "critical",
},
{
id: "SIG-1041",
title: "Duplicate decision records",
owner: "A. Okafor",
severity: "high",
},
{
id: "SIG-1038",
title: "Stale cache on summary tiles",
owner: "R. Patel",
severity: "medium",
},
]
/** Variant 1 - server-paginated table with column toggle. */
export function PaginatedSignalsTable() {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
return (
<DataTable
columns={columns}
data={signals}
showColumnToggle
pagination={{
page,
pageSize,
total: 128,
totalPages: Math.ceil(128 / pageSize),
onPageChange: setPage,
onRowsPerPageChange: setPageSize,
}}
onRowClick={(signal) => console.info("open", signal.id)}
/>
)
}
/** Variant 2 - selectable rows (bulk actions). */
export function SelectableSignalsTable() {
const [selection, setSelection] = useState<RowSelectionState>({})
return (
<DataTable
columns={columns}
data={signals}
rowSelection={selection}
onRowSelectionChange={setSelection}
maxHeight="320px"
/>
)
}
/** Variant 3 - compact embedded list (no pagination, capped height). */
export function CompactSignalsTable() {
return (
<DataTable
columns={columns}
data={signals}
maxHeight="240px"
emptyMessage="Nothing routed to you."
/>
)
}
export default function Example() {
return (
<div className="flex flex-col gap-8">
<PaginatedSignalsTable />
<CompactSignalsTable />
</div>
)
}
Props
PaginationConfig
| Prop | Type | Default | Description |
|---|---|---|---|
| page* | number | - | Current page (1-indexed). |
| pageSize* | number | - | |
| total* | number | - | Total record count across all pages. |
| totalPages* | number | - | |
| pageSizeOptions | number[] | undefined | [5, 10, 25, 50] | |
| onPageChange* | (page: number) => void | - | |
| onRowsPerPageChange* | (pageSize: number) => void | - |
DataTableErrorConfig
| Prop | Type | Default | Description |
|---|---|---|---|
| message | string | undefined | "Something went wrong." | Error message shown in the table body. |
| onRetry | (() => void) | undefined | - | When provided, renders a Retry button. |
DataTableProps
| Prop | Type | Default | Description |
|---|---|---|---|
| columns* | ColumnDef<TData, TValue>[] | - | |
| data* | TData[] | - | |
| maxHeight | string | undefined | "calc(100vh - 400px)" | Max height of the scrollable body. |
| isLoading | boolean | undefined | - | Renders skeleton rows while true. Takes precedence over error/empty. |
| error | DataTableErrorConfig | null | undefined | - | Error state owned by the widget. Rendered when set and not loading. |
| showColumnToggle | boolean | undefined | - | Shows the column visibility toggle. |
| onRowClick | ((row: TData) => void) | undefined | - | |
| emptyMessage | string | undefined | "No results found." | |
| pagination | PaginationConfig | undefined | - | |
| rowSelection | RowSelectionState | undefined | - | |
| onRowSelectionChange | OnChangeFn<RowSelectionState> | undefined | - | |
| className | string | undefined | - | Merged with `cn()` onto the root element. |
| data-testid | string | undefined | "data-table" | Test identifier rendered as `data-testid` on the root element. Internal parts (`-empty`, `-error`, `-pagination`, etc.) derive their ids from this value. |
DataTableSkeletonProps
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | number | undefined | 6 | |
| rows | number | undefined | 5 | |
| showPagination | boolean | undefined | true | |
| className | string | undefined | - | Merged with `cn()` onto the root element. |
| data-testid | string | undefined | "data-table-skeleton" | Test identifier rendered as `data-testid` on the root element. |
npm dependencies
@tanstack/react-table@^8.21.3lucide-react@^1.7.0Changelog
0.2.1
- Fix the record count rendering as
1-10 of 42records. JSX drops the newline between an expression and text on the following line, so a prettier line-wrap silently removed the space. The test only asserted thedata-totalattribute, not the visible string; it now asserts both.
data-table
0.2.0
- Add
className(merged onto the root element) and a"data-testid"prop (@default "data-table"); all internal ids (-empty,-error,-retry,-column-toggle,-pagination,-page-size-trigger,-record-count,-prev-page,-next-page,-page-{n},-sort-{id}) now derive from it. Default behavior is unchanged for existing consumers. - Sortable column headers now render as a real
<button>(keyboard-reachable, Enter/Space toggles sort) instead of a plain<div onClick>, and the<th>now setsaria-sort="ascending" | "descending" | "none"from the column's sort state. - Error state div now has
role="alert".
0.1.2
- Use semantic status tokens (text-text-error, bg-bg-warning, border-border-, bg-status-) instead of raw palette scales so client theme overrides restyle status colors.
0.1.1
- Content sync fix to bring source in line with published registry payload.
0.1.0
- Initial release, lifted near-verbatim from
signal-core-uicommon/data-table.tsx(alldata-testids, skeleton rows, pagination, column toggle, row selection preserved). - Added a widget-owned error state (
error: { message?, onRetry? }) with render precedence loading → error → rows → empty, plusdata-table-empty/data-table-error/data-table-retrytest ids. - Prop interfaces extracted to
DataTable.types.ts.