@atlaskit/dynamic-table
Version:
A dynamic table displays rows of data with built-in pagination, sorting, and re-ordering functionality.
251 lines (248 loc) • 8.24 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React, { forwardRef, lazy, Suspense, useEffect, useRef, useState } from 'react';
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
import noop from '@atlaskit/ds-lib/noop';
import { assertIsSortable } from '../internal/assert-is-sortable';
import { getPageRows } from '../internal/get-page-rows';
import { validateSortKey } from '../internal/validate-sort-key';
import { Caption } from '../styled/caption';
import { EmptyViewContainer } from '../styled/empty-view-container';
import { EmptyViewWithFixedHeight } from '../styled/empty-view-with-fixed-height';
import { PaginationWrapper } from '../styled/pagination-wrapper';
import { Table } from '../styled/table';
import Body from './body';
import { ErrorBoundary } from './error-boundary';
import LoadingContainer from './loading-container';
import LoadingContainerAdvanced from './loading-container-advanced';
import ManagedPagination from './managed-pagination';
import TableHead from './table-head';
function toggleSortOrder(currentSortOrder) {
switch (currentSortOrder) {
case 'DESC':
return 'ASC';
case 'ASC':
return 'DESC';
default:
return currentSortOrder;
}
}
const DynamicTable = ({
caption,
head,
highlightedRowIndex,
rows,
sortKey,
sortOrder,
loadingLabel,
onPageRowsUpdate,
testId,
totalRows: passedDownTotalRows,
label,
isLoading = false,
isFixedSize = false,
rowsPerPage = Infinity,
onSetPage = noop,
onSort: providedOnSort = noop,
page = 1,
emptyView,
isRankable = false,
isRankingDisabled = false,
onRankStart = noop,
onRankEnd: providedOnRankEnd = noop,
loadingSpinnerSize,
paginationi18n = {
prev: 'Previous Page',
next: 'Next Page',
label: 'Pagination',
pageLabel: 'Page'
}
}) => {
const [isRanking, setIsRanking] = useState(false);
const tableBodyRef = useRef(null);
const onSort = usePlatformLeafEventHandler({
fn: providedOnSort,
action: 'sorted',
componentName: 'dynamicTable',
packageName: "@atlaskit/dynamic-table",
packageVersion: "19.3.3"
});
const onRankEnd = usePlatformLeafEventHandler({
fn: providedOnRankEnd,
action: 'ranked',
componentName: 'dynamicTable',
packageName: "@atlaskit/dynamic-table",
packageVersion: "19.3.3"
});
useEffect(() => {
validateSortKey(sortKey, head);
assertIsSortable(head);
}, [sortKey, head]);
const onSortHandler = item => () => {
const {
key
} = item;
if (!key) {
return;
}
if (onSort && isRankable && key === sortKey && sortOrder === 'DESC') {
onSort({
key: null,
sortOrder: null,
item
});
return;
}
const sortOrderFormatted = key !== sortKey ? 'ASC' : toggleSortOrder(sortOrder);
if (onSort) {
onSort({
key,
item,
sortOrder: sortOrderFormatted
});
}
};
const onSetPageHandler = (page, event) => {
onSetPage(page, event);
};
const onRankStartHandler = params => {
setIsRanking(true);
onRankStart(params);
};
const onRankEndHandler = params => {
setIsRanking(false);
onRankEnd(params);
};
const getSpinnerSize = () => {
if (loadingSpinnerSize) {
return loadingSpinnerSize;
}
return getPageRows(rows || [], page, rowsPerPage).length > 2 ? 'large' : 'small';
};
const renderEmptyBody = () => {
if (isLoading) {
return /*#__PURE__*/React.createElement(EmptyViewWithFixedHeight, {
testId: testId
});
}
return emptyView && /*#__PURE__*/React.createElement(EmptyViewContainer, {
testId: testId
}, emptyView);
};
const rowsLength = rows && rows.length;
let totalPages;
// set a flag to denote the dynamic table might get only one page of data
// for paginated data
let isTotalPagesControlledExternally = false;
if (passedDownTotalRows && Number.isInteger(passedDownTotalRows) && rowsPerPage && rowsLength && rowsLength <= passedDownTotalRows) {
/**
* If total number of rows / records have been passed down as prop
* Then table is being fed paginated data from server or other sources
* In this case, we want to respect information passed down by server or external source
* Rather than relying on our computation based on number of rows
*/
totalPages = Math.ceil(passedDownTotalRows / rowsPerPage);
isTotalPagesControlledExternally = true;
} else {
totalPages = rowsLength && rowsPerPage ? Math.ceil(rowsLength / rowsPerPage) : 0;
}
totalPages = totalPages < 1 ? 1 : totalPages;
const getPageNumber = page > totalPages ? totalPages : page; // page! required, because typescript can't yet see defaultProps to know that this won't be undefined
const rowsExist = !!rowsLength;
const spinnerSize = getSpinnerSize();
const emptyBody = renderEmptyBody();
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(LoadingContainerAdvanced, {
isLoading: isLoading && rowsExist,
spinnerSize: spinnerSize,
targetRef: () => tableBodyRef.current,
testId: testId,
loadingLabel: loadingLabel
}, /*#__PURE__*/React.createElement(Table, {
isFixedSize: isFixedSize,
"aria-label": label,
hasDataRow: rowsExist,
testId: testId,
isLoading: isLoading
}, !!caption && /*#__PURE__*/React.createElement(Caption, null, caption), head && /*#__PURE__*/React.createElement(TableHead, {
head: head,
onSort: onSortHandler,
sortKey: sortKey,
sortOrder: sortOrder,
isRanking: isRanking,
isRankable: isRankable,
isFixedSize: isFixedSize,
testId: testId
}), rowsExist && /*#__PURE__*/React.createElement(TableBody, {
ref: tableBodyRef,
highlightedRowIndex: highlightedRowIndex,
rows: rows,
head: head,
sortKey: sortKey,
sortOrder: sortOrder,
rowsPerPage: rowsPerPage,
page: getPageNumber,
isFixedSize: isFixedSize || false,
onPageRowsUpdate: onPageRowsUpdate,
isTotalPagesControlledExternally: isTotalPagesControlledExternally,
testId: testId,
isRankable: isRankable,
isRanking: isRanking,
onRankStart: onRankStartHandler,
onRankEnd: onRankEndHandler,
isRankingDisabled: isRankingDisabled || isLoading || false
}))), totalPages <= 1 ? null :
/*#__PURE__*/
// only show pagination if there's MORE than 1 page
React.createElement(PaginationWrapper, {
testId: testId
}, /*#__PURE__*/React.createElement(ManagedPagination, {
value: getPageNumber,
onChange: onSetPageHandler,
total: totalPages,
i18n: paginationi18n,
isDisabled: isLoading,
testId: testId
})), !rowsExist && emptyBody && /*#__PURE__*/React.createElement(LoadingContainer, {
isLoading: isLoading,
spinnerSize: "large",
testId: testId,
loadingLabel: loadingLabel
}, emptyBody));
};
const RankableTableBody = /*#__PURE__*/lazy(() => import( /* webpackChunkName: '@atlaskit-internal_dynamic-table' */'./rankable/body'));
const TableBody = /*#__PURE__*/forwardRef(function TableBody({
isRankable = false,
isRanking,
onRankStart,
onRankEnd,
isRankingDisabled,
...bodyProps
}, ref) {
const canRank = isRankable && !bodyProps.sortKey;
/**
* React 16 does not support SSR for lazy components,
* so we avoid rendering the `Suspense` on the server.
*/
const [canRenderRankable, setCanRenderRankable] = useState(false);
useEffect(() => {
if (canRank) {
setCanRenderRankable(true);
}
}, [canRank]);
const nonRankableBody = /*#__PURE__*/React.createElement(Body, _extends({
ref: ref
}, bodyProps));
return canRank && canRenderRankable ? /*#__PURE__*/React.createElement(ErrorBoundary, {
fallback: nonRankableBody
}, /*#__PURE__*/React.createElement(Suspense, {
fallback: nonRankableBody
}, /*#__PURE__*/React.createElement(RankableTableBody, _extends({
ref: ref
}, bodyProps, {
isRanking: isRanking,
onRankStart: onRankStart,
onRankEnd: onRankEnd,
isRankingDisabled: isRankingDisabled
})))) : nonRankableBody;
});
// eslint-disable-next-line @repo/internal/react/require-jsdoc
export default DynamicTable;