@itwin/itwinui-react
Version:
A react component library for iTwinUI
357 lines (356 loc) • 10.2 kB
JavaScript
import * as React from 'react';
import cx from 'classnames';
import { IconButton } from '../Buttons/IconButton.js';
import { Button } from '../Buttons/Button.js';
import { DropdownButton } from '../Buttons/DropdownButton.js';
import { ProgressRadial } from '../ProgressIndicators/ProgressRadial.js';
import { MenuItem } from '../Menu/MenuItem.js';
import {
getBoundedValue,
useGlobals,
useContainerWidth,
SvgChevronLeft,
SvgChevronRight,
Box,
OverflowContainer,
useLayoutEffect,
} from '../../utils/index.js';
import { styles } from '../../styles.js';
let defaultLocalization = {
pageSizeLabel: (size) => `${size} per page`,
rangeLabel: (startIndex, endIndex, totalRows, isLoading) =>
isLoading
? `${startIndex}-${endIndex}…`
: `${startIndex}-${endIndex} of ${totalRows}`,
previousPage: 'Previous page',
nextPage: 'Next page',
goToPageLabel: (page) => `Go to page ${page}`,
rowsPerPageLabel: 'Rows per page',
rowsSelectedLabel: (totalSelectedRowsCount) =>
`${totalSelectedRowsCount} ${
1 === totalSelectedRowsCount ? 'row' : 'rows'
} selected`,
};
export const TablePaginator = (props) => {
let {
currentPage,
totalRowsCount,
pageSize,
onPageChange,
totalSelectedRowsCount = 0,
focusActivationMode = 'manual',
isLoading = false,
size = 'default',
pageSizeList,
onPageSizeChange,
localization: userLocalization,
className,
...rest
} = props;
useGlobals();
let localization = React.useMemo(
() => ({
...defaultLocalization,
...userLocalization,
}),
[userLocalization],
);
let pageListRef = React.useRef(null);
let [focusedIndex, setFocusedIndex] = React.useState(currentPage);
useLayoutEffect(() => {
setFocusedIndex(currentPage);
}, [currentPage]);
let needFocus = React.useRef(false);
let isMounted = React.useRef(false);
React.useEffect(() => {
if (isMounted.current && needFocus.current) {
let buttonToFocus = Array.from(
pageListRef.current?.querySelectorAll(
`.${styles['iui-table-paginator-page-button']}`,
) ?? [],
).find((el) => el.textContent?.trim() === (focusedIndex + 1).toString());
buttonToFocus?.focus();
needFocus.current = false;
}
isMounted.current = true;
}, [focusedIndex]);
let buttonSize = 'default' != size ? 'small' : void 0;
let totalPagesCount = Math.ceil(totalRowsCount / pageSize);
let onKeyDown = (event) => {
if (event.altKey) return;
let focusPage = (delta) => {
let newFocusedIndex = getBoundedValue(
focusedIndex + delta,
0,
totalPagesCount - 1,
);
needFocus.current = true;
if ('auto' === focusActivationMode) onPageChange(newFocusedIndex);
else setFocusedIndex(newFocusedIndex);
};
switch (event.key) {
case 'ArrowRight':
focusPage(1);
event.preventDefault();
break;
case 'ArrowLeft':
focusPage(-1);
event.preventDefault();
break;
case 'Enter':
case ' ':
case 'Spacebar':
if ('manual' === focusActivationMode) onPageChange(focusedIndex);
break;
default:
break;
}
};
let [paginatorResizeRef, paginatorWidth] = useContainerWidth();
let showPagesList = totalPagesCount > 1 || isLoading;
let showPageSizeList = pageSizeList && !!onPageSizeChange && !!totalRowsCount;
let hasNoRows = 0 === totalPagesCount;
let noRowsContent = React.createElement(
React.Fragment,
null,
isLoading
? React.createElement(ProgressRadial, {
indeterminate: true,
size: 'small',
})
: React.createElement(
Button,
{
styleType: 'borderless',
disabled: true,
size: buttonSize,
},
'1',
),
);
if (!showPagesList && !showPageSizeList) return null;
return React.createElement(
Box,
{
className: cx('iui-table-paginator', className),
ref: paginatorResizeRef,
...rest,
},
React.createElement(
Box,
{
className: 'iui-left',
},
totalSelectedRowsCount > 0 &&
React.createElement(
'span',
null,
localization.rowsSelectedLabel(totalSelectedRowsCount),
),
),
showPagesList &&
React.createElement(
OverflowContainer,
{
className: 'iui-center',
itemsCount: totalPagesCount,
},
React.createElement(
IconButton,
{
styleType: 'borderless',
disabled: 0 === currentPage,
onClick: () => onPageChange(currentPage - 1),
size: buttonSize,
'aria-label': localization.previousPage,
},
React.createElement(SvgChevronLeft, null),
),
React.createElement(
Box,
{
as: 'span',
className: 'iui-table-paginator-pages-group',
onKeyDown: onKeyDown,
ref: pageListRef,
},
hasNoRows
? noRowsContent
: React.createElement(TablePaginatorPageButtons, {
size: size,
focusedIndex: focusedIndex,
setFocusedIndex: setFocusedIndex,
totalPagesCount: totalPagesCount,
onPageChange: onPageChange,
currentPage: currentPage,
localization: localization,
isLoading: isLoading,
}),
),
React.createElement(
IconButton,
{
styleType: 'borderless',
disabled: currentPage === totalPagesCount - 1 || hasNoRows,
onClick: () => onPageChange(currentPage + 1),
size: buttonSize,
'aria-label': localization.nextPage,
},
React.createElement(SvgChevronRight, null),
),
),
React.createElement(
Box,
{
className: 'iui-right',
},
showPageSizeList &&
React.createElement(
React.Fragment,
null,
null !== localization.rowsPerPageLabel &&
paginatorWidth >= 1024 &&
React.createElement(
Box,
{
as: 'span',
className: 'iui-table-paginator-page-size-label',
},
localization.rowsPerPageLabel,
),
React.createElement(
DropdownButton,
{
styleType: 'borderless',
size: buttonSize,
menuItems: (close) =>
pageSizeList.map((size) =>
React.createElement(
MenuItem,
{
key: size,
isSelected: size === pageSize,
onClick: () => {
close();
onPageSizeChange(size);
},
},
localization.pageSizeLabel(size),
),
),
},
localization.rangeLabel(
currentPage * pageSize + 1,
Math.min(totalRowsCount, (currentPage + 1) * pageSize),
totalRowsCount,
isLoading,
),
),
),
),
);
};
let TablePaginatorPageButtons = (props) => {
let {
focusedIndex,
setFocusedIndex,
totalPagesCount,
onPageChange,
currentPage,
localization,
isLoading,
size,
} = props;
let { visibleCount } = OverflowContainer.useContext();
let buttonSize = 'default' != size ? 'small' : void 0;
let pageButton = React.useCallback(
(index, tabIndex = index === focusedIndex ? 0 : -1) =>
React.createElement(
Button,
{
key: index,
className: 'iui-table-paginator-page-button',
styleType: 'borderless',
size: buttonSize,
'data-iui-active': index === currentPage,
onClick: () => {
setFocusedIndex(index);
onPageChange(index);
},
'aria-current': index === currentPage,
'aria-label': localization.goToPageLabel?.(index + 1),
tabIndex: tabIndex,
},
index + 1,
),
[
focusedIndex,
buttonSize,
currentPage,
localization,
setFocusedIndex,
onPageChange,
],
);
let pageList = React.useMemo(
() =>
new Array(totalPagesCount)
.fill(null)
.map((_, index) => pageButton(index)),
[pageButton, totalPagesCount],
);
let halfVisibleCount = Math.floor(visibleCount / 2);
let startPage = focusedIndex - halfVisibleCount;
let endPage = focusedIndex + halfVisibleCount + 1;
if (startPage < 0) {
endPage = Math.min(totalPagesCount, endPage + Math.abs(startPage));
startPage = 0;
}
if (endPage > totalPagesCount) {
startPage = Math.max(0, startPage - (endPage - totalPagesCount));
endPage = totalPagesCount;
}
let ellipsis = React.createElement(
Box,
{
as: 'span',
className: cx('iui-table-paginator-ellipsis', {
'iui-table-paginator-ellipsis-small': 'small' === size,
}),
},
'…',
);
if (1 === visibleCount) return pageButton(focusedIndex);
let showStartEllipsis = startPage > 1;
let showEndEllipsis = endPage < totalPagesCount - 1;
return React.createElement(
React.Fragment,
null,
0 !== startPage &&
React.createElement(
React.Fragment,
null,
pageButton(0, 0),
showStartEllipsis ? ellipsis : null,
),
pageList.slice(startPage, endPage),
endPage !== totalPagesCount &&
!isLoading &&
React.createElement(
React.Fragment,
null,
showEndEllipsis ? ellipsis : null,
pageButton(totalPagesCount - 1, 0),
),
isLoading &&
React.createElement(
React.Fragment,
null,
ellipsis,
React.createElement(ProgressRadial, {
indeterminate: true,
size: 'small',
}),
),
);
};