Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(component): persist selection across TableNext pages #985

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']) {
chanceaclark marked this conversation as resolved.
Show resolved Hide resolved
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