Data table card
The table surface: one bordered card holding a title, tabs, a filter toolbar, the grid and a footer — with the same dividers and gutters every time.
Preview
Interactive examples use the same @flux/ui build as production. Toggle theme in the header for light and dark. Motion tokens and layout patterns live under Motion and Patterns in the sidebar.
Live preview
Installation
Add the package to your app (full Next.js + Tailwind steps on Installation):
npm install @payglocal_ui/flux-uiUsage
Import
import { DataTableCard, TableToolbarActions } from "@payglocal_ui/flux-ui";Example
// DataTable is the grid. DataTableCard is everything around it — which is
// where tables actually drift: one feature puts its filters above the card,
// another inside it; one draws a divider under the tabs, another does not.
<DataTableCard
columns={columns}
data={rows}
rowKey={(r) => r.id}
isLoading={isLoading}
toolbar={<FilterToolbar search={<RotatingSearchInput … />} chips={…} />}
pagination={{ mode: "page", page, pageSize, total, onPageChange: setPage }}
onRowClick={(row) => openDetails(row)}
emptyTitle="No transactions found"
/>
// A named section — for a grid that titles itself, rather than a page-level
// grid whose name is the page header.
<DataTableCard
title="Top 10 Merchants by Volume"
description="Last 30 days"
actions={<ToolbarButton>Refresh</ToolbarButton>}
columns={columns}
data={rows}
rowKey={(r) => r.id}
maxBodyHeight="34rem"
/>
// The body scrolls inside the card by default, so the toolbar and footer stay
// put and the page does not grow. Pass "none" to let the card grow instead.
<DataTableCard … maxBodyHeight="none" />
// `errorState` replaces the rows when the request failed, `emptyState` when it
// succeeded and returned none. Keeping them apart matters: column headers over
// nothing say "we looked and found none", which is wrong for a failed fetch.
<DataTableCard
…
errorState={isError ? <PlaceholderState variant="error" … /> : undefined}
emptyState={<PlaceholderState variant="no-transactions" … />}
/>
// Below the table's breakpoint, pair it with DataCardList rather than hiding
// columns: both mount, CSS shows one, and they take the same `pagination`
// object so they cannot disagree about which page they are on.
<DataTableCard className="hidden lg:block" … pagination={pagination} />
<DataCardList className="lg:hidden" … pagination={pagination} />