ginv-react-table
v1.0.0-beta.1
Published
react-table base on @tanstack/react-table and shadcn
Maintainers
Readme
React Table
A powerful and customizable React table component built on top of @tanstack/react-table, featuring sticky headers, columns, and advanced data table functionality.
Table of Contents
Features
- 📌 Sticky header and footer
- 📍 Sticky columns (left and/or right pinning)
- 🎨 Fully customizable styling
- 🔍 Built-in filtering and search
- 📊 Sorting support
- 📄 Pagination with URL state management
- ✅ Row selection and bulk actions
- 📱 Responsive design
- ⚡ Optimized scrolling with fixed headers and columns
Installation
npm install @ginv/react-table @tanstack/react-table
# or
yarn add @ginv/react-table @tanstack/react-table
# or
pnpm add @ginv/react-table @tanstack/react-tableQuick Start
Follow these steps to implement a basic data table:
- Wrap your table with
TableProvidercomponent - Define columns with field configurations, sorting, and visibility options
- Configure pagination with page size and total count
- Add filtering using
DataTableToolbarcomponent - Enable row selection for batch operations
- Configure scrolling with sticky headers and columns
Full Example
import type { ColumnPinningState, SortingState, VisibilityState } from '@tanstack/react-table'
import { TableCell, TableRow } from '@/components/ui/table'
import {
DataTablePagination,
DataTableToolbar,
getPinnedColumnCellClassName,
getPinnedColumnStyle,
TableProvider,
} from '@ginv/react-table'
interface ColumnMeta {
className?: string
tdClassName?: string
thClassName?: string
}
interface ScheduleTableProps {
data: ScheduleListResponse
loading?: boolean
}
export function ScheduleTable({ data, loading = false }: ScheduleTableProps) {
const { content, totalElements: total } = data
const { currentRow } = useSchedules()
// Configure row selection state based on current row
const rowSelection = useMemo(
() =>
currentRow !== null
? { [String(currentRow.id)]: true }
: {},
[currentRow],
)
const [sorting, setSorting] = useState<SortingState>([])
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const [columnPinning] = useState<ColumnPinningState>({
left: ['name'],
right: ['actions'],
})
// User options for filtering
const [userOptionsList, setUserOptionsList] = useState<{ label: string, value: number }[]>([])
// Manage URL query string state
const {
globalFilter,
onGlobalFilterChange,
columnFilters,
onColumnFiltersChange,
pagination,
onPaginationChange,
ensurePageInRange,
} = useTableUrlStateWithNuqs({
pagination: { defaultPage: 1, defaultPageSize: 10 },
globalFilter: { enabled: true, key: 'filter' },
columnFilters: [
{ columnId: 'type', searchKey: 'type', type: 'array' },
{ columnId: 'runState', searchKey: 'runState', type: 'array' },
{
columnId: 'commitBy',
searchKey: 'commitBy',
type: 'array',
},
],
})
// Calculate total page count (server-side pagination)
const pageCount = Math.ceil(total / pagination.pageSize)
const table = useReactTable({
data: content,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
globalFilter,
pagination,
columnPinning,
},
enableRowSelection: true,
onSortingChange: setSorting,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
manualFiltering: true,
pageCount,
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
onPaginationChange,
onGlobalFilterChange,
onColumnFiltersChange,
getRowId: row => String(row.id),
})
useEffect(() => {
ensurePageInRange(pageCount)
}, [pageCount, ensurePageInRange])
useEffect(() => {
const loadUserOptions = async () => {
try {
const options = await fetchUserOptions({ workspaceId: 1 })
setUserOptionsList(options)
}
catch (error) {
console.error('Failed to fetch user options:', error)
setUserOptionsList([])
}
}
loadUserOptions()
}, [])
return (
<div
className={cn(
'p-4',
// Add margin bottom on mobile when toolbar is visible
'max-sm:has-[div[role="toolbar"]]:mb-16',
'flex flex-1 flex-col gap-4 w-full overflow-hidden',
)}
>
<DataTableToolbar
table={table}
searchPlaceholder="search..."
filters={[
{
columnId: 'type',
title: 'Task Types',
options: types,
popoverContentClassName: 'w-[230px]',
},
{
columnId: 'commitBy',
title: 'Commit User',
options: userOptionsList,
},
]}
/>
<TableProvider
table={table}
loading={loading}
renderWrapper
scrollArea={{
className: 'max-h-[calc(100vh-(--spacing(47)))]',
tableClass: 'min-w-[1200px]',
resetScrollDeps: [pagination.pageIndex, columnFilters, globalFilter],
}}
>
{row => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'} className="group">
{row.getVisibleCells().map((cell) => {
const isPinned = cell.column.getIsPinned()
return (
<TableCell
key={cell.id}
style={{
zIndex: isPinned ? 1 : undefined,
...getPinnedColumnStyle(isPinned),
}}
className={cn(
(cell.column.columnDef.meta as ColumnMeta | undefined)?.className,
(cell.column.columnDef.meta as ColumnMeta | undefined)?.tdClassName,
getPinnedColumnCellClassName(isPinned),
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
)
})}
</TableRow>
)}
</TableProvider>
<DataTablePagination table={table} className="mt-auto" />
</div>
)
}API Reference
TableProvider
The main wrapper component for the data table.
Props:
| Prop | Type | Description |
|------|------|-------------|
| table | Table<TData> | TanStack table instance |
| loading | boolean | Show loading state |
| renderWrapper | boolean | Wrap table with scroll area |
| scrollArea | object | Scroll area configuration |
| children | (row: Row<TData>) => ReactNode | Render function for table rows |
DataTableToolbar
Toolbar component with search and filter functionality.
Props:
| Prop | Type | Description |
|------|------|-------------|
| table | Table<TData> | TanStack table instance |
| searchPlaceholder | string | Search input placeholder text |
| filters | FilterConfig[] | Array of filter configurations |
DataTablePagination
Pagination component with page size selector.
Props:
| Prop | Type | Description |
|------|------|-------------|
| table | Table<TData> | TanStack table instance |
| className | string | Additional CSS classes |
Utility Functions
getPinnedColumnStyle(pinned: ColumnPinningPosition): Returns inline styles for pinned columnsgetPinnedColumnCellClassName(pinned: ColumnPinningPosition): Returns className for pinned column cells
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
