Skip to content

Commit

Permalink
feat(PHC-4380): add sorting #331
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnzhu committed Mar 21, 2023
1 parent d5bde5e commit 37e673e
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 32 deletions.
66 changes: 65 additions & 1 deletion src/components/TableModule/ReactTableModule.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { createColumnHelper, Row } from '@tanstack/react-table';
import { createColumnHelper, SortingState } from '@tanstack/react-table';
import { Checkbox } from '../Checkbox';

import { ReactTableModule } from './ReactTableModule';
Expand Down Expand Up @@ -150,6 +150,70 @@ Default.args = {
config,
};

export const ManualSort: ComponentStory<typeof ReactTableModule> = (args) => {
const tableRef = useRef<HTMLTableElement>(null);

const [sorting, setSorting] = React.useState<SortingState>([]);
const [sortedData, setSortedData] = React.useState(data);

args.state = { sorting };

React.useEffect(() => {
const sort = sorting?.[0];
const index = sort?.id.toLowerCase();
console.log('sorting', sort);
if (sort && index) {
setSortedData(
[...data].sort((a: any, b: any) => {
if (!sort.desc) {
return a[index!] - b[index!];
}

if (sort.desc) {
return b[index!] - a[index!];
}

return 0;
})
);
}
}, [sorting, setSortedData]);

return (
<div style={{ overflow: 'auto', width: '80%', height: '400px' }}>
<ReactTableModule
{...args}
data={sortedData}
config={config}
onSortingChange={setSorting}
enableSorting={true}
ref={tableRef}
/>
</div>
);
};

export const DefaultSort: ComponentStory<typeof ReactTableModule> = (args) => {
const tableRef = useRef<HTMLTableElement>(null);

const [sorting, setSorting] = React.useState<SortingState>([]);

args.state = { sorting };

return (
<div style={{ overflow: 'auto', width: '80%', height: '400px' }}>
<ReactTableModule
{...args}
data={data}
columns={columns}
onSortingChange={setSorting}
enableSorting={true}
ref={tableRef}
/>
</div>
);
};

export const RowSelection = Template.bind({});
const selectionColumn = {
id: 'select',
Expand Down
28 changes: 23 additions & 5 deletions src/components/TableModule/ReactTableModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import clsx from 'clsx';

import {
getCoreRowModel,
getSortedRowModel,
useReactTable,
ColumnDef,
OnChangeFn,
TableState,
Updater,
SortingState,
} from '@tanstack/react-table';

import {
Expand All @@ -24,7 +27,10 @@ export interface ReactTableProps<T> extends TableModuleProps {
data: Array<T>;
columns?: ColumnDef<T, any>[];
enableRowSelection?: boolean;
enableSorting?: boolean;
onRowSelectionChange?: OnChangeFn<T>;
manualSorting?: boolean;
onSortingChange?: OnChangeFn<T>;
state?: Partial<TableState>;
}

Expand All @@ -37,9 +43,12 @@ export const ReactTableModule = React.memo(
className,
data,
enableRowSelection,
enableSorting,
isLoading = false,
onRowSelectionChange,
onSortingChange,
rowRole,
sortState = { sortKey: null, sortDirection: null },
maxCellWidth,
rowClickLabel,
state,
Expand All @@ -49,19 +58,24 @@ export const ReactTableModule = React.memo(
) => {
const classes = useStyles({});

if (columns === undefined && !!config) {
// use legacy when using config of TableModule
if (config) {
columns = config.map(mapTableConfigToColumnDef);
}

console.log('columns', columns);
// it does manual sorting in legacy mode
const manualSorting = !!config;

const table = useReactTable({
data,
columns,
enableRowSelection,
getCoreRowModel: getCoreRowModel(),
enableSorting,
onRowSelectionChange,
onSortingChange,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state,
manualSorting,
});

const headers = table.getHeaderGroups()[0].headers;
Expand All @@ -85,7 +99,11 @@ export const ReactTableModule = React.memo(
<TableHeaderCell
index={i}
key={header.id}
header={{ ...header, label: header.id }}
header={{ label: header.id }}
coreHeader={header}
onClick={header.column.getToggleSortingHandler()}
sortDirection={null}
headingsCount={headerGroup.headers.length}
></TableHeaderCell>
))}
</tr>
Expand Down
34 changes: 24 additions & 10 deletions src/components/TableModule/TableHeaderCell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import * as React from 'react';

import { flexRender } from '@tanstack/react-table';
import { Header, flexRender } from '@tanstack/react-table';
import { ChevronDown } from '@lifeomic/chromicons';
import { makeStyles } from '../../styles/index';
import { GetClasses } from '../../typeUtils';
Expand Down Expand Up @@ -98,6 +98,7 @@ export type TableHeaderCellClasses = GetClasses<typeof useStyles>;
export interface TableHeaderCellProps extends TableSortDirection {
isSorting?: boolean;
header: TableHeader;
coreHeader?: Header<any, any>;
onClick?: (header: TableSortClickProps) => any;
index: number;
headingsCount: number;
Expand All @@ -107,6 +108,7 @@ export interface TableHeaderCellProps extends TableSortDirection {

export const TableHeaderCell: React.FC<TableHeaderCellProps> = ({
header,
coreHeader,
isSorting = false,
sortDirection,
onClick,
Expand All @@ -117,20 +119,29 @@ export const TableHeaderCell: React.FC<TableHeaderCellProps> = ({
...rootProps
}) => {
const classes = useStyles({});
// use column API when coreHeader is available
const isSorted = coreHeader ? coreHeader.column.getIsSorted() : isSorting;
// TODO combine these two props into one
const sortedDirection = coreHeader
? coreHeader.column.getIsSorted()
: sortDirection;

const handleClick = () => {
onClick?.({ index, sortDirection, header });
};

const canSort = onClick && header.onSort;
// use column API when coreHeader is available
const canSort = coreHeader
? coreHeader.column.getCanSort()
: onClick && header.onSort;

const Tag = !header?.content && !header.label ? 'td' : 'th';

return (
<Tag
className={clsx(
classes.root,
header.onSort && classes.clickable,
canSort && classes.clickable,
canSort && classes.rootPeekIconHover,
// Rules of alignment:
// - `header.align` wins over default behavior
Expand All @@ -150,22 +161,25 @@ export const TableHeaderCell: React.FC<TableHeaderCellProps> = ({
onClick={canSort ? handleClick : undefined}
role="columnheader"
aria-sort={
!isSorting || !sortDirection
!isSorted || !sortedDirection
? 'none'
: sortDirection === 'asc'
: sortedDirection === 'asc'
? 'ascending'
: 'descending'
}
style={{ left: left }}
{...rootProps}
>
{header.column
? flexRender(header.column.columnDef.header, header.getContext())
{coreHeader
? flexRender(
coreHeader.column.columnDef.header,
coreHeader.getContext()
)
: header.content
? header.content(header)
: header.label}
{/* We aren't actively sorting this column, but we want to display a "peek" icon so they know they can sort it */}
{(!sortDirection || !isSorting) && canSort && (
{(!sortedDirection || !isSorted) && canSort && (
<ChevronDown
className={clsx(classes.icon, classes.peekIcon, classes.rotatedIcon)}
role="img"
Expand All @@ -175,11 +189,11 @@ export const TableHeaderCell: React.FC<TableHeaderCellProps> = ({
/>
)}
{/* We have a sort active */}
{isSorting && sortDirection && (
{isSorted && sortedDirection && (
<ChevronDown
className={clsx(
classes.icon,
sortDirection === 'asc' && classes.rotatedIcon
sortedDirection === 'asc' && classes.rotatedIcon
)}
role="img"
aria-hidden
Expand Down
9 changes: 4 additions & 5 deletions src/components/TableModule/TableModuleRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import * as React from 'react';
import { flexRender } from '@tanstack/react-table';
import { Cell, flexRender } from '@tanstack/react-table';
import { useStyles } from './TableModule';
import { getTestProps } from '../../testUtils/getTestProps';
import { TableCell } from './types';
Expand All @@ -10,6 +10,7 @@ import { TableModuleActions } from './TableModuleActions';
import { IconButton } from '../IconButton';
import { ChevronRight } from '@lifeomic/chromicons';
import { Tooltip } from '../Tooltip';
import { cellContentAccessor } from './utils';

export interface TableModuleRowProps
extends React.DetailedHTMLProps<
Expand All @@ -22,7 +23,7 @@ export interface TableModuleRowProps
maxCellWidth?: 1 | 2;
row: any;
headingsLength: number;
cells: Array<TableCell>;
cells: Array<TableCell> | Cell[];
rowActions?: TableModuleProps['rowActions'];
rowClickLabel?: TableModuleProps['rowClickLabel'];
stickyCols?: Array<number>;
Expand Down Expand Up @@ -82,9 +83,7 @@ const TableModuleRow: React.FC<TableModuleRowProps> = React.memo(
// table-core API
const cellContent = cell.column
? flexRender(cell.column.columnDef.cell, cell.getContext())
: cell.content // private API
? cell.content(row)
: cell.valuePath && row[cell.valuePath];
: cellContentAccessor(cell)(row);
return (
<TableModuleCell
key={`column-${colIndex}`}
Expand Down
7 changes: 2 additions & 5 deletions src/components/TableModule/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Cell, Header } from '@tanstack/react-table';
/**
* @description A default interface for capturing the different
* "sortDirection" options.
Expand Down Expand Up @@ -27,16 +26,14 @@ export interface TableSortClickProps extends TableSortDirection {
header: TableHeader;
}

export interface TableHeader extends TableAlignOptions, Header<any, any> {
export interface TableHeader extends TableAlignOptions {
label?: string;
content?(header: TableHeader): any;
onSort?(sort: TableSortClickProps): any;
className?: string;
}

export interface TableCell<Item = any>
extends TableAlignOptions,
Cell<any, any> {
export interface TableCell<Item = any> extends TableAlignOptions {
valuePath?: string;
content?(cell: Item): any;
className?: string;
Expand Down
24 changes: 18 additions & 6 deletions src/components/TableModule/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { createColumnHelper, ColumnDef } from '@tanstack/react-table';
import { TableConfiguration } from './types';

const columnHelper = createColumnHelper();
import { ColumnDef } from '@tanstack/react-table';
import { TableConfiguration, TableCell } from './types';

/**
* returns react table ColumnDef.
Expand All @@ -11,8 +9,22 @@ export const mapTableConfigToColumnDef = (
config: TableConfiguration<any>
): ColumnDef<any, any> => {
// TODO support accessor columnFn
return {
const columnDef: ColumnDef<any, any> = {
id: config.header.label || config.header.content(config.header),
accessorFn: config.cell.content,
accessorFn: cellContentAccessor(config.cell),
};

return columnDef;
};

/**
* returns an accessorFn.
* @param cell
*/
export const cellContentAccessor = (cell: TableCell): ((row: any) => any) => {
return cell.content // private API
? cell.content
: (row: any): any => {
return cell.valuePath && row[cell.valuePath];
};
};

0 comments on commit 37e673e

Please sign in to comment.