UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

357 lines (356 loc) 10.2 kB
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', }), ), ); };