material-react-table
Version:
A fully featured Material UI V6 implementation of TanStack React Table V8, written from the ground up in TypeScript.
210 lines (195 loc) • 6.33 kB
text/typescript
import { useMemo } from 'react';
import { type Row } from '@tanstack/react-table';
import {
type DropdownOption,
type MRT_Column,
type MRT_ColumnDef,
type MRT_ColumnOrderState,
type MRT_DefinedColumnDef,
type MRT_DefinedTableOptions,
type MRT_FilterOption,
type MRT_Header,
type MRT_RowData,
type MRT_TableInstance,
} from '../types';
export const getColumnId = <TData extends MRT_RowData>(
columnDef: MRT_ColumnDef<TData>,
): string =>
columnDef.id ?? columnDef.accessorKey?.toString?.() ?? columnDef.header;
export const getAllLeafColumnDefs = <TData extends MRT_RowData>(
columns: MRT_ColumnDef<TData>[],
): MRT_ColumnDef<TData>[] => {
const allLeafColumnDefs: MRT_ColumnDef<TData>[] = [];
const getLeafColumns = (cols: MRT_ColumnDef<TData>[]) => {
cols.forEach((col) => {
if (col.columns) {
getLeafColumns(col.columns);
} else {
allLeafColumnDefs.push(col);
}
});
};
getLeafColumns(columns);
return allLeafColumnDefs;
};
export const prepareColumns = <TData extends MRT_RowData>({
columnDefs,
tableOptions,
}: {
columnDefs: MRT_ColumnDef<TData>[];
tableOptions: MRT_DefinedTableOptions<TData>;
}): MRT_DefinedColumnDef<TData>[] => {
const {
aggregationFns = {},
defaultDisplayColumn,
filterFns = {},
sortingFns = {},
state: { columnFilterFns = {} } = {},
} = tableOptions;
return columnDefs.map((columnDef) => {
//assign columnId
if (!columnDef.id) columnDef.id = getColumnId(columnDef);
//assign columnDefType
if (!columnDef.columnDefType) columnDef.columnDefType = 'data';
if (columnDef.columns?.length) {
columnDef.columnDefType = 'group';
//recursively prepare columns if this is a group column
columnDef.columns = prepareColumns({
columnDefs: columnDef.columns,
tableOptions,
});
} else if (columnDef.columnDefType === 'data') {
//assign aggregationFns if multiple aggregationFns are provided
if (Array.isArray(columnDef.aggregationFn)) {
const aggFns = columnDef.aggregationFn as string[];
columnDef.aggregationFn = (
columnId: string,
leafRows: Row<TData>[],
childRows: Row<TData>[],
) =>
aggFns.map((fn) =>
aggregationFns[fn]?.(columnId, leafRows, childRows),
);
}
//assign filterFns
if (Object.keys(filterFns).includes(columnFilterFns[columnDef.id])) {
columnDef.filterFn =
filterFns[columnFilterFns[columnDef.id]] ?? filterFns.fuzzy;
(columnDef as MRT_DefinedColumnDef<TData>)._filterFn =
columnFilterFns[columnDef.id];
}
//assign sortingFns
if (Object.keys(sortingFns).includes(columnDef.sortingFn as string)) {
// @ts-expect-error
columnDef.sortingFn = sortingFns[columnDef.sortingFn];
}
} else if (columnDef.columnDefType === 'display') {
columnDef = {
...(defaultDisplayColumn as MRT_ColumnDef<TData>),
...columnDef,
};
}
return columnDef;
}) as MRT_DefinedColumnDef<TData>[];
};
export const reorderColumn = <TData extends MRT_RowData>(
draggedColumn: MRT_Column<TData>,
targetColumn: MRT_Column<TData>,
columnOrder: MRT_ColumnOrderState,
): MRT_ColumnOrderState => {
if (draggedColumn.getCanPin()) {
draggedColumn.pin(targetColumn.getIsPinned());
}
const newColumnOrder = [...columnOrder];
newColumnOrder.splice(
newColumnOrder.indexOf(targetColumn.id),
0,
newColumnOrder.splice(newColumnOrder.indexOf(draggedColumn.id), 1)[0],
);
return newColumnOrder;
};
export const getDefaultColumnFilterFn = <TData extends MRT_RowData>(
columnDef: MRT_ColumnDef<TData>,
): MRT_FilterOption => {
const { filterVariant } = columnDef;
if (filterVariant === 'multi-select') return 'arrIncludesSome';
if (filterVariant?.includes('range')) return 'betweenInclusive';
if (filterVariant === 'select' || filterVariant === 'checkbox')
return 'equals';
return 'fuzzy';
};
export const getColumnFilterInfo = <TData extends MRT_RowData>({
header,
table,
}: {
header: MRT_Header<TData>;
table: MRT_TableInstance<TData>;
}) => {
const {
options: { columnFilterModeOptions },
} = table;
const { column } = header;
const { columnDef } = column;
const { filterVariant } = columnDef;
const isDateFilter = !!(
filterVariant?.startsWith('date') || filterVariant?.startsWith('time')
);
const isAutocompleteFilter = filterVariant === 'autocomplete';
const isRangeFilter =
filterVariant?.includes('range') ||
['between', 'betweenInclusive', 'inNumberRange'].includes(
columnDef._filterFn,
);
const isSelectFilter = filterVariant === 'select';
const isMultiSelectFilter = filterVariant === 'multi-select';
const isTextboxFilter =
['autocomplete', 'text'].includes(filterVariant!) ||
(!isSelectFilter && !isMultiSelectFilter);
const currentFilterOption = columnDef._filterFn;
const allowedColumnFilterOptions =
columnDef?.columnFilterModeOptions ?? columnFilterModeOptions;
const facetedUniqueValues = column.getFacetedUniqueValues();
return {
allowedColumnFilterOptions,
currentFilterOption,
facetedUniqueValues,
isAutocompleteFilter,
isDateFilter,
isMultiSelectFilter,
isRangeFilter,
isSelectFilter,
isTextboxFilter,
} as const;
};
export const useDropdownOptions = <TData extends MRT_RowData>({
header,
table,
}: {
header: MRT_Header<TData>;
table: MRT_TableInstance<TData>;
}): DropdownOption[] | undefined => {
const { column } = header;
const { columnDef } = column;
const {
facetedUniqueValues,
isAutocompleteFilter,
isMultiSelectFilter,
isSelectFilter,
} = getColumnFilterInfo({ header, table });
return useMemo<DropdownOption[] | undefined>(
() =>
columnDef.filterSelectOptions ??
((isSelectFilter || isMultiSelectFilter || isAutocompleteFilter) &&
facetedUniqueValues
? Array.from(facetedUniqueValues.keys())
.filter((value) => value !== null && value !== undefined)
.sort((a, b) => a.localeCompare(b))
: undefined),
[
columnDef.filterSelectOptions,
facetedUniqueValues,
isMultiSelectFilter,
isSelectFilter,
],
);
};