DataGrid is a composable data surface for product-grade tabular experiences.
It combines TanStack Table behavior with opinionated UI composition for search,
filters, pagination, cards/table view switching, and exports.
Mental model
DataGrid owns table interaction state and rendering orchestration.
You provide:
- row data and column definitions
- optional custom renderers (
renderCard,renderSubComponent) - optional server query integration (
onQueryChange)
Features
- client mode and server mode query orchestration
- global fuzzy search and per-column filtering
- column sorting and visibility control
- pagination with configurable page sizes
- row expansion with nested content rendering
- switchable table and card presentations
- command-based controls for filters/view/export
Quick start
type User = {
id: string;
name: string;
email: string;
status: 'active' | 'trial' | 'paused';
};
const columns: ColumnDef<User>[] = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'status', header: 'Status', enableColumnFilter: true },
];
<DataGrid data={users} columns={columns} />;
Query modes
Client mode (default)
Filtering, sorting, and pagination are computed locally by TanStack Table. Use this when your dataset is already in memory.
Server mode
Set mode='server' and subscribe to onQueryChange.
Use returned query state to call APIs and push back new data.
<DataGrid
mode='server'
data={rows}
columns={columns}
pageCount={pageCount}
totalRows={totalRows}
onQueryChange={query => {
// query: { pageIndex, pageSize, sorting, globalFilter, columnFilters }
refetch(query);
}}
/>
Toolbar extensibility
Use toolbarStart and toolbarEnd to inject additional controls.
Each slot accepts either static nodes or a function that receives toolbar
context (table, query, filter actions, loading state).
<DataGrid
...
toolbarEnd={({ clearFilters }) => (
<Button size='sm' variant='outline' onClick={clearFilters}>
Clear filters
</Button>
)}
/>
Legacy render props (renderToolbarStart, renderToolbarEnd) are still
supported for backward compatibility.
Props reference
| Prop | Type | Default | Notes |
|---|---|---|---|
data | TData[] | required | Row dataset to render. |
columns | ColumnDef<TData, unknown>[] | required | TanStack column definitions. |
mode | 'client' | 'server' | 'client' | Query execution strategy. |
totalRows | number | - | Total row count in server mode. |
pageCount | number | - | Explicit page count override in server mode. |
loading | boolean | false | Enables loading placeholders. |
className | string | - | Root grid wrapper classes. |
tableClassName | string | - | Table view wrapper classes. |
cardClassName | string | - | Cards grid wrapper classes. |
emptyMessage | string | 'No results found.' | Empty-state copy. |
initialView | 'table' | 'cards' | 'table' | Uncontrolled initial view. |
view | 'table' | 'cards' | - | Controlled view override. |
onViewChange | (view) => void | - | Called when view changes. |
enableGlobalFilter | boolean | true | Global search enablement. |
enableColumnFilters | boolean | true | Per-column filters enablement. |
enableColumnVisibility | boolean | true | Column visibility controls enablement. |
enableViewModes | boolean | true | Table/cards toggle enablement. |
exportOptions | DataGridExportOptions | { enabled: true, fileName: 'data-grid', mode: 'visible', prettyJson: true } | Export configuration for toolbar command. |
toolbarClassName | string | - | Toolbar wrapper classes. |
toolbarStart | DataGridToolbarSlot<TData> | - | Start slot node or render function. |
toolbarEnd | DataGridToolbarSlot<TData> | - | End slot node or render function. |
renderToolbarStart | (context) => ReactNode | - | Legacy start slot callback. |
renderToolbarEnd | (context) => ReactNode | - | Legacy end slot callback. |
pageSizeOptions | number[] | [10, 20, 30, 50] | Pagination page size choices. |
initialState | Partial<DataGridQueryState> | { pageIndex: 0, pageSize: 10, sorting: [], globalFilter: '', columnFilters: [] } | Initial query state values. |
getRowId | (originalRow, index, parent?) => string | - | Custom row id strategy. |
getSubRows | (originalRow, index) => TData[] | undefined | - | Sub-row resolver for expandable trees. |
renderCard | (context) => ReactNode | - | Custom cards mode renderer. |
renderSubComponent | (context) => ReactNode | - | Expanded row content renderer. |
onQueryChange | (query: DataGridQueryState) => void | - | Query snapshot callback for server orchestration. |
Filter model
Column filter behavior is configured in column.columnDef.meta.filter.
Supported filter kinds:
multi-selectnumber-rangedate-range
Example:
{
accessorKey: 'usage',
header: 'Usage',
enableColumnFilter: true,
filterFn: 'numberRange',
meta: {
filter: {
kind: 'number-range',
label: 'Usage',
min: 0,
max: 100,
step: 1,
},
},
}
View customization
renderCardcustomizes cards mode item rendering.renderSubComponentrenders expandable table row details.
If no renderCard is provided, DataGrid renders a default generic card from
visible cell values.
Export behavior
Exports are controlled through exportOptions:
enabled: show/hide export controlsfileName: output base file namemode:visible(default) ororiginalprettyJson: JSON pretty-print formatting
visible mode exports current visible columns in their active order.
Recommended usage patterns
- Use
mode='server'for large datasets. - Provide stable
getRowIdfor non-id-based data. - Keep
columnDef.meta.filterdeclarative; avoid ad-hoc filter state outside grid query state. - Prefer
renderCardfor domain-specific cards rather than overloading cell renderers for cards mode.
Accessibility notes
- View controls and filter command menu are keyboard accessible.
- Sortable headers expose sort state semantics.
- Row expansion toggles include explicit labels.
See also
Tablefor low-level table primitives.ARCHITECTURE.mdin this folder for maintainer-focused internals.
Preview
Usage
'use client';
import { formatTimestamp, toTitleCase } from '@branditdev/utils';
import { type ColumnDef } from '@tanstack/react-table';
import {
BarChartIcon,
BriefcaseIcon,
CalendarIcon,
GlobeIcon,
MailIcon,
PlusIcon,
TrendingUp,
UserIcon,
} from 'lucide-react';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
import { Avatar, AvatarFallback, Button, Card, CardContent, CardFooter, CardHeader, CardTitle, Chart, Text, DataGrid } from '@branditdev/ui';
type TeamRow = {
id: string;
name: string;
email: string;
role: 'Engineer' | 'Designer' | 'Product' | 'Support';
region: 'US' | 'EU' | 'APAC';
status: 'active' | 'trial' | 'paused';
usage: number;
dateStarted: Date;
subRows?: TeamRow[];
};
function randomDateInLastTenYears() {
// Set the end date to today
const endDate = new Date();
// Set the start date to exactly five years ago
const startDate = new Date();
startDate.setFullYear(endDate.getFullYear() - 10);
// Get the time values (milliseconds since Jan 1, 1970 UTC)
const startTime = startDate.getTime();
const endTime = endDate.getTime();
// Calculate a random time value between the start and end times
// Math.random() returns a value between 0 (inclusive) and 1 (exclusive)
const randomTime = startTime + Math.random() * (endTime - startTime);
// Create a new Date object using the random time
return new Date(randomTime);
}
const StatusBadge = ({ status }: { status: TeamRow['status'] }) => {
const palette = {
active: 'bg-emerald-100 text-emerald-800',
trial: 'bg-amber-100 text-amber-800',
paused: 'bg-slate-200 text-slate-700',
};
return (
<span
className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${palette[status]}`}
>
{status}
</span>
);
};
const firstNames = [
'Noah',
'Emma',
'Liam',
'Olivia',
'Mason',
'Ava',
'Ethan',
'Sophia',
'Lucas',
'Isabella',
];
const lastNames = [
'Carter',
'Morgan',
'Nguyen',
'Kim',
'Lopez',
'Bennett',
'Singh',
'Campbell',
'Murphy',
'Reed',
];
const roles: TeamRow['role'][] = ['Engineer', 'Designer', 'Product', 'Support'];
const regions: TeamRow['region'][] = ['US', 'EU', 'APAC'];
const statuses: TeamRow['status'][] = ['active', 'trial', 'paused'];
function cycleValue<T>(values: T[], index: number): T {
return values[index % values.length]!;
}
const generatedRows: TeamRow[] = Array.from({ length: 100 }, (_, index) => {
const id = index + 6;
const firstName = cycleValue(firstNames, index);
const lastName = cycleValue(lastNames, Math.floor(index / firstNames.length));
const name = `${firstName} ${lastName}`;
const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}${id}@marketfuel.com`;
return {
id: `u${id}`,
name,
email,
role: cycleValue(roles, index),
region: cycleValue(regions, index),
status: cycleValue(statuses, index),
usage: 35 + ((index * 7) % 66),
dateStarted: randomDateInLastTenYears(),
};
});
const columns: ColumnDef<TeamRow>[] = [
{
meta: {
icon: UserIcon,
},
accessorKey: 'name',
header: 'Name',
enableColumnFilter: true,
},
{
meta: {
icon: CalendarIcon,
filter: {
kind: 'date-range',
label: 'Date Started',
},
},
accessorKey: 'dateStarted',
header: 'Date Started',
enableColumnFilter: true,
filterFn: 'dateRange',
cell: info => {
const value = info.getValue<Date>();
const formatted = formatTimestamp(value.toISOString(), {
format: {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: undefined,
minute: undefined,
},
});
return <Text variant='body-sm'>{formatted}</Text>;
},
},
{
meta: {
icon: MailIcon,
},
accessorKey: 'email',
header: 'Email',
enableColumnFilter: true,
},
{
meta: {
icon: BriefcaseIcon,
filterLabel: 'Role',
},
accessorKey: 'role',
header: 'Role',
enableColumnFilter: true,
},
{
meta: {
icon: GlobeIcon,
filterLabel: 'Region',
},
accessorKey: 'region',
header: 'Region',
enableColumnFilter: true,
},
{
meta: {
icon: TrendingUp,
filterLabel: 'Status',
},
accessorKey: 'status',
header: 'Status',
enableColumnFilter: true,
cell: info => {
const value = info.getValue<TeamRow['status']>();
return <StatusBadge status={value} />;
},
},
{
accessorKey: 'usage',
header: 'Usage',
enableColumnFilter: true,
filterFn: 'numberRange',
meta: {
cardLabel: 'Usage Score',
icon: BarChartIcon,
filter: {
kind: 'number-range',
label: 'Usage',
min: 0,
max: 100,
step: 1,
},
},
cell: info => `${info.getValue<number>()}%`,
},
];
export function DataGridDemo() {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 3000);
}, []);
return (
<div className='space-y-3'>
<DataGrid
toolbarEnd={
<Button
variant='outline'
size='sm'
// eslint-disable-next-line no-console
onClick={() => console.info('Create User action clicked')}
>
<PlusIcon className='size-4' />
<span className='hidden md:inline'>Create User</span>
</Button>
}
loading={loading}
data={generatedRows}
columns={columns}
getSubRows={row => row.subRows}
pageSizeOptions={[10, 20, 30, 50]}
renderSubComponent={({ row }) => (
<div className='text-muted-foreground text-xs'>
Expanded details for {row.original.name}
</div>
)}
renderCard={({ row }) => (
<Card key={row.id} className='overflow-hidden'>
<CardHeader>
<div className='flex items-center gap-3'>
<Avatar>
<AvatarFallback>
{row.original.name
.split(' ')
.map(part => part[0])
.join('')
.slice(0, 2)}
</AvatarFallback>
</Avatar>
<CardTitle>{row.original.name}</CardTitle>
</div>
</CardHeader>
<CardContent className='space-y-4'>
<div className='grid grid-cols-2 gap-3 text-sm'>
<div className='bg-muted/40 rounded-md p-3'>
<Text
variant='body-sm'
className='text-muted-foreground flex items-center gap-1'
>
<TrendingUp className='size-3.5' /> Status
</Text>
<Text variant='body-sm' className='mt-1 font-medium'>
{toTitleCase(row.original.status)}
</Text>
</div>
<div className='bg-muted/40 rounded-md p-3'>
<Text
variant='body-sm'
className='text-muted-foreground flex items-center gap-1'
>
<CalendarIcon className='size-3.5' /> Started
</Text>
<Text variant='body-sm' className='mt-1 font-medium'>
{formatTimestamp(row.original.dateStarted.toISOString(), {
format: {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: undefined,
minute: undefined,
},
})}
</Text>
</div>
<div className='bg-muted/40 rounded-md p-3'>
<Text
variant='body-sm'
className='text-muted-foreground flex items-center gap-1'
>
<BriefcaseIcon className='size-3.5' /> Role
</Text>
<Text variant='body-sm' className='mt-1 font-medium'>
{row.original.role}
</Text>
</div>
<div className='bg-muted/40 rounded-md p-3'>
<Text
variant='body-sm'
className='text-muted-foreground flex items-center gap-1'
>
<GlobeIcon className='size-3.5' /> Region
</Text>
<Text variant='body-sm' className='mt-1 font-medium'>
{row.original.region}
</Text>
</div>
<div className='bg-muted/40 rounded-md p-3 col-span-2'>
<Text
variant='body-sm'
className='text-muted-foreground flex items-center gap-1'
>
<BarChartIcon className='size-3.5' /> Usage
</Text>
<Chart className='h-24 p-2 mt-1 font-medium'>
<ResponsiveContainer width='100%' height='100%'>
<PieChart>
<Pie
data={[
{ name: 'used', value: row.original.usage },
{
name: 'remaining',
value: 100 - row.original.usage,
},
]}
dataKey='value'
cx='50%'
cy='50%'
innerRadius={24}
outerRadius={36}
stroke='none'
>
<Cell fill='var(--chart-5)' />
<Cell fill='var(--chart-2)' />
</Pie>
<text
x='50%'
y='50%'
textAnchor='middle'
dominantBaseline='middle'
className='fill-foreground text-xs font-semibold'
>
{row.original.usage}%
</text>
</PieChart>
</ResponsiveContainer>
</Chart>
</div>
</div>
</CardContent>
<CardFooter className='border-t pt-4 flex items-center justify-center space-x-2'>
<Button
variant='ghost'
size='sm'
className='flex-1'
onClick={() =>
window.open(`mailto:${row.original.email}`, '_blank')
}
>
<MailIcon className='size-4' />
Contact
</Button>
<Button
size='sm'
className='flex-1'
onClick={() =>
// eslint-disable-next-line no-console
console.info(`Open profile for ${row.original.id}`)
}
>
<UserIcon className='size-4' />
View profile
</Button>
</CardFooter>
</Card>
)}
/>
</div>
);
}
API
DataGrid<
TData>(props):Element
Defined in: components/data-grid/data-grid.tsx:53
Robust data grid with table/card views and client/server query modes.
Type Parameters
TData
TData extends unknown
Parameters
| Name | Type | Optional | Default | Description |
|---|---|---|---|---|
| props | DataGridProps<TData> | no | - | Data grid configuration and data source options. |
Returns
Element
A composable data visualization surface.
Source
'use client';
import type { DragEndEvent } from '@dnd-kit/core';
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
import { arrayMove } from '@dnd-kit/sortable';
import {
type RowData,
getCoreRowModel,
getExpandedRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import * as React from 'react';
import { cn, DataGridCards, DataTablePagination, dataGridActions, dataGridReducer, DataGridTable, DataGridToolbar } from '@branditdev/ui';
import {
type DataGridProps,
type DataGridQueryState,
type DataGridToolbarContext,
type DataGridViewMode,
defaultState,
dateRangeFilter as dateRange,
fuzzyFilter as fuzzy,
numberRangeFilter as numberRange,
resolveToolbarSlot,
} from './data-grid-utils';
export function DataGrid<TData extends RowData>({
data,
columns,
mode = defaultState.mode,
totalRows,
pageCount,
loading = defaultState.loadingState,
className,
tableClassName,
cardClassName,
emptyMessage = defaultState.emptyMessage,
initialView = defaultState.initialView,
view,
onViewChange,
enableGlobalFilter = defaultState.enableGlobalFilter,
enableColumnFilters = defaultState.enableColumnFilters,
enableColumnVisibility = defaultState.enableColumnVisibility,
enableViewModes = defaultState.enableViewModes,
exportOptions = defaultState.exportOptions,
toolbarClassName,
toolbarStart,
toolbarEnd,
renderToolbarStart,
renderToolbarEnd,
pageSizeOptions = defaultState.pageSizeOptions,
initialState = defaultState.initialState,
getRowId,
getSubRows,
renderCard,
renderSubComponent,
onQueryChange,
}: DataGridProps<TData>) {
const [state, dispatch] = React.useReducer(dataGridReducer, {
sorting: initialState?.sorting ?? defaultState.initialState.sorting,
columnFilters:
initialState?.columnFilters ?? defaultState.initialState.columnFilters,
globalFilter:
initialState?.globalFilter ?? defaultState.initialState.globalFilter,
pagination: {
pageIndex: initialState?.pageIndex ?? defaultState.initialState.pageIndex,
pageSize: initialState?.pageSize ?? pageSizeOptions[0] ?? 10,
},
columnVisibility: {},
expanded: {},
view: initialView ?? defaultState.initialView,
columnOrder: columns
.map(column => {
if (column.id) return column.id;
if ('accessorKey' in column && column.accessorKey)
return String(column.accessorKey);
return undefined;
})
.filter((id): id is string => Boolean(id)),
});
const {
setSorting,
setColumnFilters,
setGlobalFilter,
setPagination,
setColumnVisibility,
setExpanded,
setView,
setColumnOrder,
} = React.useMemo(
() => ({
setSorting: dataGridActions.setSorting(dispatch),
setColumnFilters: dataGridActions.setColumnFilters(dispatch),
setGlobalFilter: dataGridActions.setGlobalFilter(dispatch),
setPagination: dataGridActions.setPagination(dispatch),
setColumnVisibility: dataGridActions.setColumnVisibility(dispatch),
setExpanded: dataGridActions.setExpanded(dispatch),
setView: dataGridActions.setView(dispatch),
setColumnOrder: dataGridActions.setColumnOrder(dispatch),
}),
[dispatch]
);
const activeView = view ?? state.view;
const setActiveView = React.useCallback(
(nextView: DataGridViewMode) => {
onViewChange?.(nextView);
if (!view) setView(nextView);
},
[onViewChange, view, setView]
);
const table = useReactTable({
data,
columns,
filterFns: { fuzzy, numberRange, dateRange },
globalFilterFn: 'fuzzy',
defaultColumn: {
filterFn: 'fuzzy',
minSize: 60,
size: 180,
maxSize: 800,
},
state: {
sorting: state.sorting,
columnFilters: state.columnFilters,
globalFilter: state.globalFilter ?? '',
pagination: state.pagination,
columnVisibility: state.columnVisibility,
expanded: state.expanded,
columnOrder: state.columnOrder,
},
getRowId,
getSubRows,
manualPagination: mode === 'server',
manualSorting: mode === 'server',
manualFiltering: mode === 'server',
pageCount:
mode === 'server'
? (pageCount ??
(typeof totalRows === 'number'
? Math.ceil(totalRows / state.pagination.pageSize)
: -1))
: undefined,
enableColumnResizing: true,
columnResizeMode: 'onChange',
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
onPaginationChange: setPagination,
onColumnVisibilityChange: setColumnVisibility,
onExpandedChange: setExpanded,
onColumnOrderChange: setColumnOrder,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: mode === 'client' ? getFilteredRowModel() : undefined,
getFacetedRowModel: mode === 'client' ? getFacetedRowModel() : undefined,
getFacetedUniqueValues:
mode === 'client' ? getFacetedUniqueValues() : undefined,
getSortedRowModel: mode === 'client' ? getSortedRowModel() : undefined,
getPaginationRowModel:
mode === 'client' ? getPaginationRowModel() : undefined,
getExpandedRowModel: getExpandedRowModel(),
});
React.useEffect(() => {
onQueryChange?.({
pageIndex: state.pagination.pageIndex,
pageSize: state.pagination.pageSize,
sorting: state.sorting,
globalFilter: state.globalFilter ?? '',
columnFilters: state.columnFilters,
});
}, [
state.globalFilter,
onQueryChange,
state.pagination,
state.sorting,
state.columnFilters,
]);
const loadingCardCount = defaultState.loadingCardCount;
const queryState: DataGridQueryState = {
pageIndex: state.pagination.pageIndex,
pageSize: state.pagination.pageSize,
sorting: state.sorting,
globalFilter: state.globalFilter ?? '',
columnFilters: state.columnFilters,
};
const clearGlobalFilter = () => setGlobalFilter('');
const clearFilters = () => {
setGlobalFilter('');
setColumnFilters([]);
};
const toolbarContext: DataGridToolbarContext<TData> = {
table,
query: queryState,
activeView,
setActiveView,
globalFilter: state.globalFilter ?? '',
setGlobalFilter,
clearGlobalFilter,
clearFilters,
loading,
};
const toolbarStartSlot = resolveToolbarSlot(toolbarContext, toolbarStart);
const toolbarEndSlot = resolveToolbarSlot(toolbarContext, toolbarEnd);
const legacyToolbarStart = renderToolbarStart?.(toolbarContext);
const legacyToolbarEnd = renderToolbarEnd?.(toolbarContext);
const toolbarStartContent = (
<>
{toolbarStartSlot}
{legacyToolbarStart}
</>
);
const toolbarEndContent = (
<>
{toolbarEndSlot}
{legacyToolbarEnd}
</>
);
React.useEffect(() => {
const nextIds = table.getAllLeafColumns().map(column => column.id);
setColumnOrder(prev => {
const retained = prev.filter(id => nextIds.includes(id));
const additions = nextIds.filter(id => !retained.includes(id));
const merged = [...retained, ...additions];
if (
merged.length === prev.length &&
merged.every((id, index) => id === prev[index])
)
return prev;
return merged;
});
}, [columns, setColumnOrder, table]);
const dndContextId = React.useId();
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (active && over && active.id !== over.id) {
const updateOrder = () => {
const oldIndex = state.columnOrder.indexOf(active.id as string);
const newIndex = state.columnOrder.indexOf(over.id as string);
if (oldIndex < 0 || newIndex < 0) return state.columnOrder;
return arrayMove(state.columnOrder, oldIndex, newIndex); //this is just a splice util
};
setColumnOrder(updateOrder());
}
};
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
);
return (
<DndContext
id={dndContextId}
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
>
<div className={cn('space-y-4', className)}>
<DataGridToolbar
toolbarClassName={toolbarClassName}
searchControl={
<DataGridToolbar.SearchControl
enableGlobalFilter={enableGlobalFilter}
globalFilter={state.globalFilter ?? ''}
setGlobalFilter={setGlobalFilter}
clearGlobalFilter={clearGlobalFilter}
/>
}
viewToggle={
<DataGridToolbar.ViewToggle
enableViewModes={enableViewModes}
view={activeView}
setActiveView={setActiveView}
/>
}
filtersCommand={
<DataGridToolbar.FiltersCommand
enableColumnFilters={enableColumnFilters}
exportOptions={exportOptions}
enableColumnVisibility={enableColumnVisibility}
table={table}
activeView={activeView}
setActiveView={setActiveView}
enableViewModes={enableViewModes}
/>
}
toolbarStartContent={toolbarStartContent}
toolbarEndContent={toolbarEndContent}
/>
{activeView === 'table' ? (
<DataGridTable tableClassName={tableClassName}>
<DataGridTable.Header
enableColumnFilters={enableColumnFilters}
table={table}
/>
<DataGridTable.Body
loading={loading}
emptyMessage={emptyMessage}
table={table}
pagination={state.pagination}
renderSubComponent={renderSubComponent}
/>
</DataGridTable>
) : (
<DataGridCards cardClassName={cardClassName}>
<DataGridCards.Cards
loading={loading}
loadingCardCount={loadingCardCount}
emptyMessage={emptyMessage}
table={table}
renderCard={renderCard}
/>
</DataGridCards>
)}
<DataTablePagination table={table} pageSizeOptions={pageSizeOptions} />
</div>
</DndContext>
);
}