laif-ds
Version:
Design System di Laif con componenti React basati su principi di Atomic Design
403 lines (342 loc) • 13.1 kB
Markdown
# DataTable
## Overview
Powerful table built on TanStack Table v8. Supports client-side and server-side modes with a unified API, advanced filtering (badges + builder), global search, multi-column sorting, row selection, column pinning, dynamic pagination, i18n, and loading skeletons.
---
## Props
| Prop | Type | Default | Description |
| ---------------------- | ------------------------------------- | ---------------------- | --------------------------------------------------------------------- |
| `columns` | `ColumnDef<TData, TValue>[]` | — | TanStack column definitions. Use `meta` to enable features. |
| `data` | `TData[]` | — | Table data. |
| `loading` | `boolean` | `false` | Show skeleton rows. |
| `emptyComponent` | `ReactNode` | `undefined` | Rendered when no rows. |
| `className` | `string` | `undefined` | Wrapper classes. |
| `checkable` | `boolean` | `false` | Adds a left checkbox column for row selection. |
| `rowSelection` | `Record<string, boolean>` | `{}` | Controlled selection map. |
| `onRowSelectionChange` | `OnChangeFn<Record<string, boolean>>` | `undefined` | Selection change handler (controlled selection). |
| `onCheckedRowsChange` | `(checkedRows: TData[]) => void` | `undefined` | Returns selected row values (requires `checkable`). |
| `actions` | `DataTableActions[]` | `[]` | Actions for the actions row. |
| `hidePagination` | `boolean` | `false` | Hide pagination UI. |
| `hideActionsRow` | `boolean` | `false` | Hide actions row. |
| `i18n` | `DataTableI18n` | `defaultDataTableI18n` | Internationalization strings. |
| `maxSortedColumns` | `number` | `2` | Max number of sorted columns. |
| `initialState` | `DataTableState<TData>` | `undefined` | Initial filters, sorting, pagination and optional `columnVisibility`. |
| `serverMode` | `boolean` | `false` | Enable server-side mode. |
| `serverConfig` | `DataTableServerConfig` | `undefined` | `{ totalItems, onStateChange }` handler for server mode. |
| `disableAutoPageSize` | `boolean` | `false` | Disable auto pageSize (still updates pageIndex). |
---
## Column meta
Configure columns via `column.meta`:
```ts
type ColumnMeta = {
type:
| "string"
| "number"
| "boolean"
| "date"
| "datetime"
| "list_single_select"
| "list_multi_select"
| "other";
headerLabel?: string;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
pinned?: "left" | "right";
listOptions?: { value: string; label: string }[];
};
```
Notes:
- `id` must match the field key used for filtering/sorting.
- If `header` is a `ReactNode` (e.g. `header: () => <div>Nome</div>`), set `meta.headerLabel` to provide a stable text label for sorting/filtering/column-visibility UI.
- Filtering nested accessors (e.g., `"user.name"`) is not supported.
---
## Behavior
- **Filtering**: Badge filters + advanced builder with logical `_and`/`_or`.
- **Global search**: Across `meta.searchable` columns; strings use `like`, arrays use `array_overlap` (mapped automatically).
- **Operators**: Full operator set per type, including `eq_null`/`n_eq_null`, list operators (`array_overlap`, `n_array_overlap`), date/time before/after, checked/unchecked.
- **Sorting**: Multi-column (limited by `maxSortedColumns`).
- **Selection**: Integrated checkbox column when `checkable`.
- **Column visibility**: Columns can start hidden via `initialState.columnVisibility` and be toggled from the eye icon popover in the toolbar (supports drag-and-drop reordering).
- **Non-hideable columns**: Set `enableHiding: false` on a column to prevent it from being hidden.
- **Custom JSX headers**: Use `meta.headerLabel` when `header` is a ReactNode to provide a stable text label for menus.
- **Pinning**: `meta.pinned` supports left/right pinned columns.
- **Pagination**: Auto page size from container height; respects `disableAutoPageSize`.
- **Loading**: Skeleton rows adapt to viewport height (no hardcoded length).
- **i18n**: All labels (sorting/filtering menus included) use `DataTableI18n`.
- **Datetime**: Supports microseconds format like `2025-08-22T12:53:54.060315` and timezone-safe date handling.
---
## Server-side mode
When `serverMode=true`, the table emits state to `serverConfig.onStateChange(state)` with:
```ts
type ServerState = {
pagination: { pageIndex: number; pageSize: number };
filters?: IFilterState;
computedFilter?: SearchFilter;
computedSorting?: { sort_by: string[]; sort_order: ("asc" | "desc")[] };
};
```
The table manages debounced emissions and preserves computed values across updates.
---
## Examples
### Client mode using utilities (columns + filters + initial state)
```tsx
import * as React from "react";
import type { ColumnDef } from "@tanstack/react-table";
import {
DataTable,
createStringColumn,
createNumberColumn,
createSingleSelectColumn,
createMultiSelectColumn,
createActionColumn,
createInitialState,
createStringFilter,
createNumberFilter,
} from "laif-ds";
import { Button } from "laif-ds";
type Person = {
id: string;
name: string;
age: number;
role: string;
tags: string[];
created_at: string;
active: boolean;
};
const columns = [
createStringColumn<Person>({
accessorKey: "name",
header: "Nome",
sortable: true,
filterable: true,
searchable: true,
}),
createNumberColumn<Person>({
accessorKey: "age",
header: "Età",
sortable: true,
filterable: true,
}),
createSingleSelectColumn<Person>({
accessorKey: "role",
header: "Ruolo",
options: ["Admin", "User", "Guest"],
filterable: true,
searchable: true,
}),
createMultiSelectColumn<Person>({
accessorKey: "tags",
header: "Tags",
options: ["frontend", "backend", "devops"],
filterable: true,
}),
createActionColumn<Person>({
id: "actions",
header: "Azioni",
pinned: "right",
cell: (row) => <Button size="sm">Edit {row.name}</Button>,
}),
] satisfies ColumnDef<Person, any>[];
const initialState = createInitialState({
filters: [
createStringFilter("name", "name", "Nome", "like", "John"),
createNumberFilter("age", "age", "Età", "ge", 18),
],
searchbarFilter: "developer",
sorting: [{ column: "name", order: "asc" }],
pagination: { pageIndex: 0, pageSize: 15 },
});
const actions = [
{
label: "Export CSV",
icon: "FileDown",
onClick: () => {
/* ... */
},
},
{
label: "Bulk Delete",
icon: "Trash2",
onClick: () => {
/* ... */
},
},
];
export function ClientTable({ data }: { data: Person[] }) {
return (
<DataTable
columns={columns}
data={data}
initialState={initialState}
actions={actions}
checkable
onCheckedRowsChange={(rows) => console.log("checked rows", rows)}
/>
);
}
```
### Server mode using utilities (state-driven fetch)
```tsx
import * as React from "react";
import type { ServerState } from "laif-ds";
import { DataTable, createInitialState } from "laif-ds";
export function ServerTable({
data,
total,
}: {
data: Person[];
total: number;
}) {
const [loading, setLoading] = React.useState(false);
const handleState = React.useCallback(async (state: ServerState) => {
// Use state.pagination, state.computedFilter, state.computedSorting
// to fetch from your backend
setLoading(true);
try {
await fetch("/api/people", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
});
// Update your data + total here based on response
} finally {
setLoading(false);
}
}, []);
return (
<DataTable
columns={columns}
data={data}
loading={loading}
serverMode
serverConfig={{ totalItems: total, onStateChange: handleState }}
initialState={createInitialState({
pagination: { pageIndex: 0, pageSize: 20 },
})}
checkable
/>
);
}
```
### Initial column visibility
You can start with some columns hidden and let the user control them via the column-visibility (eye) popover:
```tsx
const initialStateWithHiddenColumns = createInitialState<Person>({
pagination: { pageIndex: 0, pageSize: 15 },
sorting: [{ column: "name", order: "asc" }],
columnVisibility: {
// these columns will start hidden in the UI
secret_field: false,
debug_column: false,
},
});
export function TableWithHiddenColumns({ data }: { data: Person[] }) {
return (
<DataTable
columns={columns}
data={data}
initialState={initialStateWithHiddenColumns}
/>
);
}
```
### Non-hideable columns
If a column must always stay visible, set `enableHiding: false` on the column definition.
```tsx
import type { ColumnDef } from "@tanstack/react-table";
const columns: ColumnDef<Person>[] = [
{
accessorKey: "name",
header: "Nome",
meta: { type: "string", sortable: true, searchable: true },
},
{
accessorKey: "email",
header: "Email",
enableHiding: false,
meta: { type: "string", searchable: true },
},
];
export function TableWithNonHideableColumn({ data }: { data: Person[] }) {
return <DataTable columns={columns} data={data} />;
}
```
### Checkbox selection column (`checkable`)
When `checkable` is enabled, the DataTable adds a left checkbox column for row selection. This column is not hideable and not draggable in the column visibility popover.
```tsx
export function TableCheckable({ data }: { data: Person[] }) {
return <DataTable columns={columns} data={data} checkable />;
}
```
### Drag-and-drop reordering
You can reorder columns by opening the column visibility popover (eye icon) and dragging items (grip icon). The order is managed internally via table `columnOrder` state.
```tsx
export function TableWithReordering({ data }: { data: Person[] }) {
return <DataTable columns={columns} data={data} />;
}
```
### ReactNode headers + `meta.headerLabel`
If you use a `ReactNode` header, set `meta.headerLabel` so that sorting/filtering/visibility menus can show a readable label.
```tsx
import type { ColumnDef } from "@tanstack/react-table";
const columns: ColumnDef<Person>[] = [
{
accessorKey: "name",
header: () => <div>Nome</div>,
meta: {
type: "string",
headerLabel: "Nome",
sortable: true,
filterable: true,
searchable: true,
},
},
];
```
### Utility recipes (pinning, list options, quick filters)
```tsx
import {
pinColumns,
updateColumnListOptions,
toSelectOptions,
createFilterBadges,
createStringFilter,
createNumberFilter,
} from "laif-ds";
// Pin columns to left/right
const pinnedColumns = pinColumns(columns, {
left: ["name"],
right: ["actions"],
});
// Update list options dynamically (e.g., fetched from API)
const roleOptions = toSelectOptions(["Admin", "User", "Guest", "Manager"]);
const columnsWithUpdatedRoles = updateColumnListOptions(
columns,
"role",
roleOptions,
);
// Build multiple filter badges quickly
const quickFilters = createFilterBadges([
{
columnId: "name",
columnAccessorKey: "name",
columnLabel: "Nome",
columnType: "string",
operator: "like",
value: "Jane",
},
{
columnId: "age",
columnAccessorKey: "age",
columnLabel: "Età",
columnType: "number",
operator: "ge",
value: 30,
},
]);
```
---
## Notes
- **Performance**: Client mode filters/sorts in-memory; use server mode for large datasets.
- **UX**: Page size auto-adapts; use `disableAutoPageSize` to manage it manually.
- **Internationalization**: Provide custom `i18n` for localized actions and labels.