Skip to content

Commit

Permalink
fix(component): persist selection across TableNext pages
Browse files Browse the repository at this point in the history
  • Loading branch information
chanceaclark committed Sep 27, 2022
1 parent ac723e3 commit 6b6fea3
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 123 deletions.
4 changes: 2 additions & 2 deletions packages/big-design/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
coverageThreshold: {
global: {
statements: 95,
statements: 96,
branches: 87,
functions: 97,
lines: 95,
lines: 96,
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const InternalActions = <T extends TableItem>({
isExpandable={isExpandable}
items={items}
onChange={onSelectionChange}
pagination={pagination}
selectedItems={selectedItems}
totalItems={totalItems}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import { Checkbox } from '../../Checkbox';
import { Flex, FlexItem } from '../../Flex';
import { Text } from '../../Typography';
import { TableExpandable, TableItem, TableSelectable } from '../types';
import { TableExpandable, TableItem, TablePaginationProps, TableSelectable } from '../types';

import { useSelectAllState } from './useSelectAllState';

Expand All @@ -14,18 +14,14 @@ export interface SelectAllProps<T> {
onChange?: TableSelectable['onSelectionChange'];
selectedItems: TableSelectable['selectedItems'];
totalItems: number;
pagination?: TablePaginationProps;
}

export const SelectAll = <T extends TableItem>({
expandedRowSelector,
isExpandable,
items = [],
onChange,
selectedItems,
totalItems,
}: SelectAllProps<T>) => {
export const SelectAll = <T extends TableItem>(props: SelectAllProps<T>) => {
const { allInPageSelected, handleSelectAll, label, someInPageSelected, totalSelectedItems } =
useSelectAllState({ expandedRowSelector, isExpandable, items, selectedItems, onChange });
useSelectAllState(props);

const { totalItems } = props;

return (
<FlexItem flexShrink={0} marginRight="xxSmall">
Expand Down
87 changes: 52 additions & 35 deletions packages/big-design/src/components/TableNext/SelectAll/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import { getPagedIndex } from '../helpers';
import { TableExpandable, TableSelectable } from '../types';

interface SelectAllRowsArg<T> {
isExpandable: boolean;
items: T[];
selectedItems: TableSelectable['selectedItems'];
expandedRowSelector?: TableExpandable<T>['expandedRowSelector'];
}
import { SelectAllProps } from './SelectAll';

export function getTotalSelectedItems<T>(
items: T[],
selectedItems: TableSelectable['selectedItems'],
) {
return items.reduce((acc, _parentRow, parentRowIndex) => {
if (selectedItems[parentRowIndex] !== undefined) {
return acc + 1;
}
type SelectAllRowsArg<T> = Omit<SelectAllProps<T>, 'onChange'>;

return acc;
}, 0);
export function getTotalSelectedItems(selectedItems: TableSelectable['selectedItems']) {
return Object.keys(selectedItems).filter((key) => !key.includes('.')).length;
}

export function getChildrenRows<T>(
Expand All @@ -38,20 +27,22 @@ export function areAllInPageSelected<T>({
items,
selectedItems,
expandedRowSelector,
pagination,
}: SelectAllRowsArg<T>) {
if (items.length <= 0) {
return false;
}

return items.every((parentRow, parentRowIndex) => {
const pagedIndex = getPagedIndex(parentRowIndex, pagination);
const childrenRows: T[] = getChildrenRows(parentRow, expandedRowSelector);

// Not need to check childrens since expandable mode is not used.
if (!isExpandable || childrenRows.length === 0) {
return selectedItems[parentRowIndex] !== undefined;
return selectedItems[pagedIndex] !== undefined;
}

return areAllParentsAndChildrenSelected(childrenRows, selectedItems, parentRowIndex);
return areAllParentsAndChildrenSelected(childrenRows, selectedItems, pagedIndex);
});
}

Expand All @@ -60,65 +51,91 @@ export function areSomeInPageSelected<T>({
items,
selectedItems,
expandedRowSelector,
pagination,
}: SelectAllRowsArg<T>): boolean {
if (items.length <= 0) {
return false;
}

return items.some((parentRow, parentRowIndex) => {
const pagedIndex = getPagedIndex(parentRowIndex, pagination);
const childrenRows: T[] = getChildrenRows(parentRow, expandedRowSelector);

// Not need to check childrens since expandable mode is not used.
if (!isExpandable || childrenRows.length === 0) {
return selectedItems[parentRowIndex] !== undefined;
return selectedItems[pagedIndex] !== undefined;
}

return areSomeParentsAndChildrenSelected(childrenRows, selectedItems, parentRowIndex);
return areSomeParentsAndChildrenSelected(childrenRows, selectedItems, pagedIndex);
});
}

function areAllParentsAndChildrenSelected<T>(
childrenRows: T[],
selectedItems: TableSelectable['selectedItems'],
parentRowIndex: number,
pagedIndex: number,
) {
const allChildrenRowsSelected = childrenRows.every((_childRow, childRowIndex) => {
return selectedItems[`${parentRowIndex}.${childRowIndex}`] !== undefined;
return selectedItems[`${pagedIndex}.${childRowIndex}`] !== undefined;
});

return selectedItems[parentRowIndex] !== undefined && allChildrenRowsSelected;
return selectedItems[pagedIndex] !== undefined && allChildrenRowsSelected;
}

function areSomeParentsAndChildrenSelected<T>(
childrenRows: T[],
selectedItems: TableSelectable['selectedItems'],
parentRowIndex: number,
pagedIndex: number,
) {
const someChildrenRowsInPageSelected = childrenRows.some((_childRow, childRowIndex) => {
return selectedItems[`${parentRowIndex}.${childRowIndex}`] !== undefined;
return selectedItems[`${pagedIndex}.${childRowIndex}`] !== undefined;
});

return selectedItems[parentRowIndex] !== undefined && someChildrenRowsInPageSelected;
return selectedItems[pagedIndex] !== undefined && someChildrenRowsInPageSelected;
}

export function selectAll<T>({
expandedRowSelector,
isExpandable,
items,
}: Omit<SelectAllRowsArg<T>, 'selectedItems'>): TableSelectable['selectedItems'] {
function deselectAllOnCurrentPage<T>(params: SelectAllRowsArg<T>) {
const { items, selectedItems, pagination } = params;

const filteredSelectedItems = Object.keys(selectedItems)
.filter((selectedKey) => {
const [parentIndex] = selectedKey.split('.').map((key) => parseInt(key, 10));
const item = items.find((_, index) => getPagedIndex(index, pagination) === parentIndex);

return !item;
})
.map<[string, true]>((key) => [key, true]);

return Object.fromEntries(filteredSelectedItems);
}

function selectAllOnCurrentPage<T>(params: SelectAllRowsArg<T>) {
const { isExpandable, items, selectedItems, expandedRowSelector, pagination } = params;

const newSelectedItems = items.map((parentRow, parentRowIndex) => {
const pagedIndex = getPagedIndex(parentRowIndex, pagination);
const childrenRows: T[] = getChildrenRows(parentRow, expandedRowSelector);

if (isExpandable) {
const newSelectedChildrenRows = childrenRows.map<[string, true]>((_child, childRowIndex) => {
return [`${parentRowIndex}.${childRowIndex}`, true];
return [`${pagedIndex}.${childRowIndex}`, true];
});

return [[`${parentRowIndex}`, true], ...newSelectedChildrenRows];
return [[`${pagedIndex}`, true], ...newSelectedChildrenRows];
}

return [[`${parentRowIndex}`, true]];
return [[`${pagedIndex}`, true]];
});

return Object.fromEntries(newSelectedItems.flat());
return { ...selectedItems, ...Object.fromEntries(newSelectedItems.flat()) };
}

export function getSelectAllState<T>(
params: SelectAllRowsArg<T>,
): TableSelectable['selectedItems'] {
if (areAllInPageSelected(params)) {
return deselectAllOnCurrentPage(params);
}

return selectAllOnCurrentPage(params);
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,25 @@
import { TableExpandable, TableSelectable } from '../types';

import {
areAllInPageSelected,
areSomeInPageSelected,
getSelectAllState,
getTotalSelectedItems,
selectAll,
} from './helpers';
import { SelectAllProps } from './SelectAll';

interface useSelectAllStateProps<T> {
isExpandable: boolean;
items: T[];
selectedItems: TableSelectable['selectedItems'];
expandedRowSelector?: TableExpandable<T>['expandedRowSelector'];
onChange?: TableSelectable['onSelectionChange'];
}

export const useSelectAllState = <T>({
expandedRowSelector,
isExpandable,
items,
selectedItems,
onChange,
}: useSelectAllStateProps<T>) => {
const allInPageSelected = areAllInPageSelected({
expandedRowSelector,
isExpandable,
items,
selectedItems,
});

const someInPageSelected = areSomeInPageSelected({
expandedRowSelector,
isExpandable,
items,
selectedItems,
});
export const useSelectAllState = <T>(props: SelectAllProps<T>) => {
const { selectedItems, onChange } = props;

const totalSelectedItems = getTotalSelectedItems(items, selectedItems);
const allInPageSelected = areAllInPageSelected(props);
const someInPageSelected = areSomeInPageSelected(props);
const totalSelectedItems = getTotalSelectedItems(selectedItems);
const label = allInPageSelected ? 'Deselect All' : 'Select All';

const handleSelectAll = () => {
if (typeof onChange !== 'function') {
return;
}

if (allInPageSelected) {
return onChange({});
}

const newSelectedItems = selectAll({
expandedRowSelector,
isExpandable,
items,
});
const newSelectedItems = getSelectAllState(props);

return onChange(newSelectedItems);
};
Expand Down
7 changes: 5 additions & 2 deletions packages/big-design/src/components/TableNext/TableNext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ExpandableHeaderCell,
HeaderCheckboxCell,
} from './HeaderCell/HeaderCell';
import { getPagedIndex } from './helpers';
import { useExpandable, useSelectable } from './hooks';
import { RowContainer } from './RowContainer';
import { StyledTable, StyledTableFigure } from './styled';
Expand Down Expand Up @@ -166,6 +167,7 @@ const InternalTableNext = <T extends TableItem>(
<Body ref={provided.innerRef} withFirstRowBorder={headerless} {...provided.droppableProps}>
{items.map((item: T, index) => {
const key = getItemKey(item, index);
const pagedIndex = getPagedIndex(index, pagination);

return (
<Draggable draggableId={String(key)} index={index} key={key}>
Expand All @@ -186,7 +188,7 @@ const InternalTableNext = <T extends TableItem>(
key={key}
onExpandedRow={onExpandedRow}
onItemSelect={onItemSelect}
parentRowIndex={index}
parentRowIndex={pagedIndex}
ref={provided.innerRef}
selectedItems={selectedItems}
showDragIcon={true}
Expand All @@ -208,6 +210,7 @@ const InternalTableNext = <T extends TableItem>(
<Body withFirstRowBorder={headerless}>
{items.map((item: T, index) => {
const key = getItemKey(item, index);
const pagedIndex = getPagedIndex(index, pagination);

return (
<RowContainer
Expand All @@ -224,7 +227,7 @@ const InternalTableNext = <T extends TableItem>(
key={key}
onExpandedRow={onExpandedRow}
onItemSelect={onItemSelect}
parentRowIndex={index}
parentRowIndex={pagedIndex}
selectedItems={selectedItems}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders a pagination component 1`] = `
exports[`pagination renders a pagination component 1`] = `
.c10 {
vertical-align: middle;
height: 2rem;
Expand Down
7 changes: 7 additions & 0 deletions packages/big-design/src/components/TableNext/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TablePaginationProps } from './types';

export function getPagedIndex(index: number, pagination?: TablePaginationProps) {
const { currentPage, itemsPerPage } = pagination ?? { currentPage: 1, itemsPerPage: 0 };

return index + (currentPage - 1) * itemsPerPage;
}
Loading

0 comments on commit 6b6fea3

Please sign in to comment.