<TableHeader table={table} />
export default function TableHeader({ table }: { table: Table<Product> }) {
/**
* Creates a configuration object for filterable columns based on their metadata.
* Each filterable column gets an entry with its filter variant and empty arrays for range and unique values.
*
* @returns Object mapping column IDs to their filter configurations
* @source
*/
const filterableColumns = useMemo(() => {
return table.options.columns.reduce<Record<string, ColumnMeta<Product, unknown>>>(
(accu, column: ColumnDef<Product, unknown>) => {
const meta = column.meta as ColumnMeta<Product, unknown> | undefined;
if (meta?.filterVariant === undefined || !column.id) return accu;
accu[column.id] = {
filterVariant: meta.filterVariant,
rangeValues: [],
uniqueValues: [],
};
return accu;
},
{},
);
}, [table.options.columns]);
// Process the filterable columns with memoization
useMemo(() => {
for (const [colName, { filterVariant }] of Object.entries(filterableColumns)) {
const col = table.options.columns.find((col) => col.id === colName);
if (col === undefined) continue;
if (filterVariant === "range") {
/**
* Calculates the range values (min and max) for numeric columns.
* @returns Array containing [min, max] values
* @source
*/
const rangeValues = table.options.data.reduce(
(accu, row: Product) => {
const value = Number(row[colName as keyof Product]);
if (value < accu[0]) {
accu[0] = value;
} else if (value > accu[1]) {
accu[1] = value;
}
return accu;
},
[0, 0],
);
filterableColumns[colName].rangeValues = rangeValues;
continue;
}
/**
* Collects unique values for non-range columns.
* @returns Array of unique values
* @source
*/
const uniqueValues = table.options.data.reduce<string[]>((accu, row: Product) => {
const value = String(row[colName as keyof Product]);
if (value !== undefined && accu.indexOf(value) === -1) {
accu.push(value);
}
return accu;
}, []);
filterableColumns[colName].uniqueValues = uniqueValues;
}
}, [filterableColumns, table.options.data, table.options.columns]);
return (
<thead>
{table.getHeaderGroups().map((headerGroup: HeaderGroup<Product>) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header: Header<Product, unknown>) => {
// If the column has filterable values, populate the unique values for the column
if (filterableColumns[header.id] !== undefined) {
const meta = header.column.columnDef.meta as ColumnMeta<Product, unknown>;
header.column.columnDef.meta = {
...meta,
...filterableColumns[header.id],
};
}
return (
<TableHeaderCell
key={header.id}
colSpan={header.colSpan}
headerSize={header.getSize()}
colSize={header.column.getSize()}
>
{header.isPlaceholder ? null : (
<>
<ColumnResizer
isResizing={header.column.getIsResizing()}
onDoubleClick={header.column.resetSize}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={`${styles.resizer} ${header.column.getIsResizing() ? styles.isResizing : ""}`}
/>
<SortableHeaderContent
canSort={header.column.getCanSort()}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <ArrowDropUpIcon className={styles["sort-icon"]} />,
desc: <ArrowDropDownIcon className={styles["sort-icon"]} />,
}[String(header.column.getIsSorted())] ?? null}
</SortableHeaderContent>
</>
)}
</TableHeaderCell>
);
})}
</tr>
))}
</thead>
);
}
TableHeader component that renders the header row of the product results table. It handles column resizing, sorting, and filter configuration.