mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
feat(FE-326): Support custom header rows and cell render hook
This commit is contained in:
+110
-45
@@ -14,6 +14,8 @@ import {
|
||||
SortingState,
|
||||
OnChangeFn,
|
||||
Row,
|
||||
HeaderGroup,
|
||||
Column,
|
||||
} from '@tanstack/react-table';
|
||||
import { rankItem } from '@tanstack/match-sorter-utils';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -32,6 +34,20 @@ interface TableClassNames {
|
||||
bodyRowClassName?: string;
|
||||
bodyColumnClassName?: string;
|
||||
paginationClassName?: string;
|
||||
customHeaderRowClassName?: string;
|
||||
customHeaderCellClassName?: string;
|
||||
}
|
||||
|
||||
export interface CustomHeaderRow {
|
||||
id: string;
|
||||
cells: Array<{
|
||||
id: string;
|
||||
content: ReactNode;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
className?: string;
|
||||
}>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface TableProps<TData extends object> {
|
||||
@@ -52,6 +68,13 @@ export interface TableProps<TData extends object> {
|
||||
rowSelection?: Record<string, boolean>;
|
||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||
customHeaderRows?: CustomHeaderRow[];
|
||||
renderCustomHeaders?: boolean;
|
||||
onCustomHeaderCellRender?: <TData extends object>(
|
||||
cell: ReactNode,
|
||||
column: Column<TData, unknown>,
|
||||
headerGroup: HeaderGroup<TData>
|
||||
) => ReactNode;
|
||||
}
|
||||
|
||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||
@@ -85,6 +108,8 @@ const Table = <TData extends object>({
|
||||
bodyRowClassName: '',
|
||||
bodyColumnClassName: '',
|
||||
paginationClassName: '',
|
||||
customHeaderRowClassName: '',
|
||||
customHeaderCellClassName: '',
|
||||
},
|
||||
emptyContent = emptyContentDefaultValue,
|
||||
sorting,
|
||||
@@ -93,6 +118,9 @@ const Table = <TData extends object>({
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
enableRowSelection,
|
||||
customHeaderRows = [],
|
||||
renderCustomHeaders = false,
|
||||
onCustomHeaderCellRender,
|
||||
}: TableProps<TData>) => {
|
||||
const isServerSideTable =
|
||||
totalItems !== undefined &&
|
||||
@@ -195,55 +223,92 @@ const Table = <TData extends object>({
|
||||
<div className={className.tableWrapperClassName}>
|
||||
<table className={className.tableClassName}>
|
||||
<thead className={className.tableHeaderClassName}>
|
||||
{renderCustomHeaders &&
|
||||
customHeaderRows.length > 0 &&
|
||||
customHeaderRows.map((headerRow) => (
|
||||
<tr
|
||||
key={headerRow.id}
|
||||
className={
|
||||
headerRow.className || className.customHeaderRowClassName
|
||||
}
|
||||
>
|
||||
{headerRow.cells.map((cell) => (
|
||||
<th
|
||||
key={cell.id}
|
||||
colSpan={cell.colSpan}
|
||||
rowSpan={cell.rowSpan}
|
||||
className={
|
||||
cell.className || className.customHeaderCellClassName
|
||||
}
|
||||
>
|
||||
{cell.content}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
className={cn(
|
||||
header.column.getCanSort()
|
||||
? 'cursor-pointer select-none'
|
||||
: '',
|
||||
className.headerColumnClassName
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center gap-1'>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
{headerGroup.headers.map((header) => {
|
||||
let cellContent = flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
);
|
||||
|
||||
{header.column.getCanSort() && (
|
||||
<div className='flex items-center'>
|
||||
<Icon
|
||||
icon='lucide:arrow-up'
|
||||
width={12}
|
||||
height={12}
|
||||
className={cn(
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'asc'
|
||||
? 'text-black'
|
||||
: 'text-black/30'
|
||||
)}
|
||||
/>
|
||||
<Icon
|
||||
icon='lucide:arrow-down'
|
||||
width={12}
|
||||
height={12}
|
||||
className={cn(
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'desc'
|
||||
? 'text-black'
|
||||
: 'text-black/30'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
if (onCustomHeaderCellRender) {
|
||||
cellContent = onCustomHeaderCellRender(
|
||||
cellContent,
|
||||
header.column,
|
||||
headerGroup
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<th
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
rowSpan={header.rowSpan}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
className={cn(
|
||||
header.column.getCanSort()
|
||||
? 'cursor-pointer select-none'
|
||||
: '',
|
||||
className.headerColumnClassName
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
>
|
||||
<div className='flex items-center gap-1'>
|
||||
{cellContent}
|
||||
|
||||
{header.column.getCanSort() && (
|
||||
<div className='flex items-center'>
|
||||
<Icon
|
||||
icon='lucide:arrow-up'
|
||||
width={12}
|
||||
height={12}
|
||||
className={cn(
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'asc'
|
||||
? 'text-black'
|
||||
: 'text-black/30'
|
||||
)}
|
||||
/>
|
||||
<Icon
|
||||
icon='lucide:arrow-down'
|
||||
width={12}
|
||||
height={12}
|
||||
className={cn(
|
||||
'transition-all ease-in-out duration-200',
|
||||
header.column.getIsSorted() === 'desc'
|
||||
? 'text-black'
|
||||
: 'text-black/30'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user