Skip to content

Commit

Permalink
feat(component): stateful table sortFn (#353)
Browse files Browse the repository at this point in the history
  • Loading branch information
deini authored Mar 25, 2020
1 parent 9a9eeb4 commit 3a715a3
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { Table, TableColumn, TableItem, TableProps, TableSelectable, TableSortDi

import { createReducer, createReducerInit } from './reducer';

export type StatefulTableSortFn<T> = (a: T, b: T, direction: TableSortDirection) => number;

export interface StatefulTableColumn<T> extends Omit<TableColumn<T>, 'isSortable'> {
sortKey?: keyof T;
sortFn?: StatefulTableSortFn<T>;
}

export interface StatefulTableProps<T>
Expand All @@ -33,7 +36,7 @@ const InternalStatefulTable = <T extends TableItem>({
}: StatefulTableProps<T>): React.ReactElement<StatefulTableProps<T>> => {
const reducer = useMemo(() => createReducer<T>(), []);
const reducerInit = useMemo(() => createReducerInit<T>(), []);
const sortable = useMemo(() => columns.some(column => column.sortKey), [columns]);
const sortable = useMemo(() => columns.some(column => column.sortKey || column.sortFn), [columns]);

const [state, dispatch] = useReducer(reducer, { columns, defaultSelected, items, pagination }, reducerInit);

Expand Down Expand Up @@ -63,8 +66,8 @@ const InternalStatefulTable = <T extends TableItem>({
[onSelectionChange],
);

const onSort = useCallback((columnHash: string, direction: TableSortDirection, column: StatefulTableColumn<T>) => {
dispatch({ type: 'SORT', columnHash, direction, sortKey: column.sortKey });
const onSort = useCallback((_columnHash: string, direction: TableSortDirection, column: StatefulTableColumn<T>) => {
dispatch({ type: 'SORT', column, direction });
}, []);

const paginationOptions = useMemo(
Expand Down
29 changes: 20 additions & 9 deletions packages/big-design/src/components/StatefulTable/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Reducer } from 'react';

import { TableSortDirection } from '../Table';

import { StatefulTableColumn } from './StatefulTable';
import { StatefulTableColumn, StatefulTableSortFn } from './StatefulTable';

interface State<T> {
currentItems: T[];
Expand Down Expand Up @@ -58,7 +58,7 @@ export type Action<T> =
| { type: 'ITEMS_PER_PAGE_CHANGE'; itemsPerPage: number }
| { type: 'PAGE_CHANGE'; page: number }
| { type: 'SELECTED_ITEMS'; selectedItems: T[] }
| { type: 'SORT'; direction: TableSortDirection; columnHash: string; sortKey?: keyof T };
| { type: 'SORT'; column: StatefulTableColumn<T>; direction: TableSortDirection };

export const createReducer = <T>(): Reducer<State<T>, Action<T>> => (state, action) => {
switch (action.type) {
Expand Down Expand Up @@ -141,15 +141,18 @@ export const createReducer = <T>(): Reducer<State<T>, Action<T>> => (state, acti
}

case 'SORT': {
const sortKey = action.sortKey;
const { hash, sortFn, sortKey } = action.column;
const direction = action.direction;
const columnHash = action.columnHash;

if (!sortKey) {
if (!sortKey && !sortFn) {
return state;
}

const items = sort(state.items, direction, sortKey);
const items = sortKey
? sort(state.items, direction, sortKey)
: sortFn
? sort(state.items, direction, sortFn)
: state.items;

const currentItems = getItems(items, state.isPaginationEnabled, {
currentPage: 1,
Expand All @@ -166,7 +169,7 @@ export const createReducer = <T>(): Reducer<State<T>, Action<T>> => (state, acti
},
sortable: {
direction,
columnHash,
columnHash: hash,
},
};
}
Expand All @@ -177,7 +180,7 @@ export const createReducer = <T>(): Reducer<State<T>, Action<T>> => (state, acti
};

function augmentColumns<T>(columns: Array<StatefulTableColumn<T>>) {
return columns.map(column => ({ ...column, isSortable: Boolean(column.sortKey) }));
return columns.map(column => ({ ...column, isSortable: Boolean(column.sortKey || column.sortFn) }));
}

function getItems<T>(
Expand All @@ -196,8 +199,16 @@ function getItems<T>(
return items.slice(firstItem, lastItem);
}

function sort<T>(items: T[], direction: TableSortDirection, sortKey: keyof T) {
function sort<T>(items: T[], direction: TableSortDirection, sortKey: keyof T): T[];
function sort<T>(items: T[], direction: TableSortDirection, sortFn: StatefulTableSortFn<T>): T[];
function sort<T>(items: T[], direction: TableSortDirection, sortKeyOrFn: keyof T | StatefulTableSortFn<T>) {
return [...items].sort((firstItem, secondItem) => {
if (typeof sortKeyOrFn === 'function') {
return sortKeyOrFn(firstItem, secondItem, direction);
}

const sortKey = sortKeyOrFn;

const firstValue = firstItem[sortKey];
const secondValue = secondItem[sortKey];

Expand Down
40 changes: 40 additions & 0 deletions packages/big-design/src/components/StatefulTable/spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,46 @@ test('sorts numerically', () => {
expect(lastItemContent).toBe('104');
});

test('sorts using a custom sorting function', () => {
const { getAllByTestId, getByText } = render(
getSimpleTable({
columns: [
{ header: 'Name', hash: 'name', render: ({ name }) => <span data-testid="name">{name}</span>, sortKey: 'name' },
{
header: 'Stock',
hash: 'stock',
render: ({ stock }) => <span data-testid="stock">{stock}</span>,
sortFn: (a, b, dir) => (dir === 'ASC' ? a.stock - b.stock : b.stock - a.stock),
},
],
pagination: false,
}),
);

let items = getAllByTestId('stock');
let firstItemContent = items[0].textContent;
let lastItemContent = items[items.length - 1].textContent;

// Descending order
fireEvent.click(getByText('Stock'));

items = getAllByTestId('stock');
firstItemContent = items[0].textContent;
lastItemContent = items[items.length - 1].textContent;

expect(firstItemContent).toBe('104');
expect(lastItemContent).toBe('1');

// Ascending order
fireEvent.click(getByText('Stock'));
items = getAllByTestId('stock');
firstItemContent = items[0].textContent;
lastItemContent = items[items.length - 1].textContent;

expect(firstItemContent).toBe('1');
expect(lastItemContent).toBe('104');
});

test('renders custom actions', () => {
const { getByTestId } = render(getSimpleTable({ actions: () => <div data-testid="customAction">Test Action</div> }));

Expand Down
3 changes: 2 additions & 1 deletion packages/configs/tslint/tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"tslint-config-prettier"
],
"rules": {
"object-literal-sort-keys": false
"object-literal-sort-keys": false,
"unified-signatures": false
}
}
5 changes: 5 additions & 0 deletions packages/docs/PropTables/StatefulTablePropTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ const tableColumnsProps: Prop[] = [
required: true,
description: 'Unique identifier for column.',
},
{
name: 'sortFn',
types: `(a: Item, b: Item, dir: 'ASC' | 'DESC') => number`,
description: 'Enables sorting on the column using a custom sort function.',
},
{
name: 'sortKey',
types: 'string',
Expand Down

0 comments on commit 3a715a3

Please sign in to comment.