chore: add new props (withPagination, getRowCanExpand, renderSubComponent, expanded, and getSubRows)

This commit is contained in:
ValdiANS
2026-01-26 20:57:05 +07:00
parent 66c537ec10
commit 34f01abb32
+116 -55
View File
@@ -1,11 +1,12 @@
'use client'; 'use client';
import { ReactNode, useCallback, useEffect, useState } from 'react'; import { Fragment, ReactNode, useCallback, useEffect, useState } from 'react';
import { import {
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getExpandedRowModel,
getSortedRowModel, getSortedRowModel,
TableOptions, TableOptions,
useReactTable, useReactTable,
@@ -15,6 +16,7 @@ import {
OnChangeFn, OnChangeFn,
Row, Row,
HeaderContext, HeaderContext,
ExpandedState,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { rankItem } from '@tanstack/match-sorter-utils'; import { rankItem } from '@tanstack/match-sorter-utils';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -33,6 +35,9 @@ interface TableClassNames {
bodyRowClassName?: string; bodyRowClassName?: string;
selectedBodyRowClassName?: string; selectedBodyRowClassName?: string;
bodyColumnClassName?: string; bodyColumnClassName?: string;
bodySubRowClassName?: (depth: number) => string;
selectedBodySubRowClassName?: (depth: number) => string;
bodySubRowColumnClassName?: (depth: number) => string;
tableFooterClassName?: string; tableFooterClassName?: string;
footerRowClassName?: string; footerRowClassName?: string;
footerColumnClassName?: string; footerColumnClassName?: string;
@@ -60,6 +65,7 @@ export interface TableProps<TData extends object> {
enableRowSelection?: boolean | ((row: Row<TData>) => boolean); enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
renderFooter?: boolean; renderFooter?: boolean;
withCheckbox?: boolean; withCheckbox?: boolean;
withPagination?: boolean;
rowOptions?: number[]; rowOptions?: number[];
/** /**
* Custom row renderer. Should return a complete <tr> element or null. * Custom row renderer. Should return a complete <tr> element or null.
@@ -67,6 +73,10 @@ export interface TableProps<TData extends object> {
* Return null to render the default row. * Return null to render the default row.
*/ */
renderCustomRow?: (row: Row<TData>) => ReactNode | null; renderCustomRow?: (row: Row<TData>) => ReactNode | null;
getRowCanExpand?: (row: Row<TData>) => boolean;
renderSubComponent?: (props: { row: Row<TData> }) => React.ReactElement;
expanded?: ExpandedState;
getSubRows?: (originalRow: TData, index: number) => TData[] | undefined;
} }
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}]; const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
@@ -92,7 +102,12 @@ export const TABLE_DEFAULT_STYLING = {
bodyRowClassName: bodyRowClassName:
'transition-all duration-200 border-t border-base-content/10 bg-transparent', 'transition-all duration-200 border-t border-base-content/10 bg-transparent',
selectedBodyRowClassName: 'bg-primary/5', selectedBodyRowClassName: 'bg-primary/5',
bodyColumnClassName: 'px-4 py-3 text-base-content', bodyColumnClassName: 'px-4 py-3 text-base-content font-medium',
bodySubRowClassName: (depth: number) =>
'transition-all duration-200 border-t border-base-content/10 bg-transparent',
selectedBodySubRowClassName: (depth: number) => 'bg-primary/5',
bodySubRowColumnClassName: (depth: number) =>
'px-4 py-3 text-base-content font-medium',
paginationClassName: 'px-3', paginationClassName: 'px-3',
tableFooterClassName: 'font-semibold border-base-content/10', tableFooterClassName: 'font-semibold border-base-content/10',
footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10', footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10',
@@ -120,8 +135,13 @@ const Table = <TData extends object>({
enableRowSelection, enableRowSelection,
renderFooter = false, renderFooter = false,
withCheckbox = false, withCheckbox = false,
withPagination = true,
rowOptions = [10, 20, 50, 100], rowOptions = [10, 20, 50, 100],
renderCustomRow, renderCustomRow,
getRowCanExpand,
renderSubComponent,
expanded = {},
getSubRows,
}: TableProps<TData>) => { }: TableProps<TData>) => {
const isServerSideTable = const isServerSideTable =
totalItems !== undefined && totalItems !== undefined &&
@@ -154,10 +174,14 @@ const Table = <TData extends object>({
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination, onPaginationChange: setPagination,
getExpandedRowModel: getExpandedRowModel(),
getRowCanExpand: getRowCanExpand ?? (getSubRows ? undefined : () => false),
getSubRows,
manualSorting, manualSorting,
state: { state: {
pagination, pagination,
globalFilter: fuzzySearchValue, globalFilter: fuzzySearchValue,
expanded,
}, },
filterFns: { filterFns: {
fuzzy: fuzzyFilter, fuzzy: fuzzyFilter,
@@ -228,7 +252,10 @@ const Table = <TData extends object>({
<div <div
className={cn( className={cn(
TABLE_DEFAULT_STYLING.containerClassName, TABLE_DEFAULT_STYLING.containerClassName,
tableClassNames.containerClassName tableClassNames.containerClassName,
{
'mb-0': !withPagination,
}
)} )}
> >
<div <div
@@ -352,36 +379,67 @@ const Table = <TData extends object>({
} }
return ( return (
<tr <Fragment key={row.id}>
key={row.id} <tr
className={cn( data-depth={row.depth}
TABLE_DEFAULT_STYLING.bodyRowClassName, className={cn(
tableClassNames.bodyRowClassName, row.depth > 0
{ ? tableClassNames.bodySubRowClassName(row.depth)
[tableClassNames.selectedBodyRowClassName]: : tableClassNames.bodyRowClassName,
row.getIsSelected(), {
} [tableClassNames.selectedBodyRowClassName!]:
)} row.getIsSelected() && row.depth === 0,
> [tableClassNames.selectedBodySubRowClassName(
{row.getVisibleCells().map((cell) => ( row.depth
<td )!]: row.getIsSelected() && row.depth > 0,
key={cell.id} }
className={cn( )}
{ 'first:w-9 first:pr-0': withCheckbox }, >
TABLE_DEFAULT_STYLING.bodyColumnClassName, {row.getVisibleCells().map((cell) => (
tableClassNames.bodyColumnClassName <td
)} key={cell.id}
> className={cn(
{!isLoading && { 'first:w-9 first:pr-0': withCheckbox },
flexRender( TABLE_DEFAULT_STYLING.bodyColumnClassName,
cell.column.columnDef.cell, row.depth > 0
cell.getContext() ? tableClassNames.bodySubRowColumnClassName(
row.depth
)
: tableClassNames.bodyColumnClassName
)} )}
>
{!isLoading &&
flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
{isLoading && <div className='skeleton w-full h-4' />} {isLoading && <div className='skeleton w-full h-4' />}
</td> </td>
))} ))}
</tr> </tr>
{row.getIsExpanded() && (
<>
{renderSubComponent && (
<tr
className={cn(
TABLE_DEFAULT_STYLING.bodySubRowClassName(1),
tableClassNames.bodySubRowClassName(1),
{
[tableClassNames.selectedBodySubRowClassName(1)]:
row.getIsSelected(),
}
)}
>
<td colSpan={row.getVisibleCells().length}>
{renderSubComponent({ row })}
</td>
</tr>
)}
</>
)}
</Fragment>
); );
})} })}
</tbody> </tbody>
@@ -425,30 +483,33 @@ const Table = <TData extends object>({
!isLoading && !isLoading &&
emptyContent} emptyContent}
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && ( {data.length > 0 &&
<div table.getRowModel().rows.length > 0 &&
className={cn( !isLoading &&
'mt-5', withPagination && (
TABLE_DEFAULT_STYLING.paginationClassName, <div
tableClassNames.paginationClassName className={cn(
)} 'mt-5',
> TABLE_DEFAULT_STYLING.paginationClassName,
<Pagination tableClassNames.paginationClassName
totalItems={isServerSideTable ? totalItems : table.getRowCount()} )}
itemsPerPage={table.getState().pagination.pageSize} >
currentPage={ <Pagination
isServerSideTable totalItems={isServerSideTable ? totalItems : table.getRowCount()}
? page itemsPerPage={table.getState().pagination.pageSize}
: table.getState().pagination.pageIndex + 1 currentPage={
} isServerSideTable
onPrevPage={prevPageClickHandler} ? page
onNextPage={nextPageClickHandler} : table.getState().pagination.pageIndex + 1
onPageChange={pageChangeHandler} }
rowOptions={rowOptions} onPrevPage={prevPageClickHandler}
onRowChange={onPageSizeChange} onNextPage={nextPageClickHandler}
/> onPageChange={pageChangeHandler}
</div> rowOptions={rowOptions}
)} onRowChange={onPageSizeChange}
/>
</div>
)}
</div> </div>
); );
}; };