UNPKG

@zendeskgarden/react-pagination

Version:

Components relating to pagination in the Garden Design System

164 lines (161 loc) 6.09 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React__default, { forwardRef, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { getControlledValue } from '@zendeskgarden/container-utilities'; import { useText } from '@zendeskgarden/react-theming'; import { StyledList } from '../../styled/OffsetPagination/StyledList.js'; import { StyledListItem } from '../../styled/OffsetPagination/StyledListItem.js'; import '../../styled/OffsetPagination/StyledPage.js'; import '../../styled/CursorPagination/StyledCursorPagination.js'; import '../../styled/CursorPagination/StyledCursor.js'; import '../../styled/CursorPagination/StyledIcon.js'; import '../../styled/OffsetPagination/StyledGapListItem.js'; import '../../styled/OffsetPagination/StyledNavigation.js'; import { StyledNav } from '../../styled/OffsetPagination/StyledNav.js'; import { Previous } from './components/Previous.js'; import { Next } from './components/Next.js'; import { Page } from './components/Page.js'; import { Gap } from './components/Gap.js'; const PREVIOUS_KEY = 'previous'; const NEXT_KEY = 'next'; const OffsetPagination = forwardRef((_ref, ref) => { let { currentPage: controlledCurrentPage, totalPages, pagePadding, pageGap, onChange, 'aria-label': ariaLabel, labels, ...otherProps } = _ref; const [focusedItem, setFocusedItem] = useState(); const [internalCurrentPage, setInternalCurrentPage] = useState(1); const navigationLabel = useText(OffsetPagination, { 'aria-label': ariaLabel }, 'aria-label', 'Pagination'); const currentPage = getControlledValue(controlledCurrentPage, internalCurrentPage); const handleFocus = useCallback(item => { setFocusedItem(item); }, []); const handleSelect = useCallback(item => { let updatedCurrentPage = item; let updatedFocusedKey = focusedItem; if (updatedCurrentPage === PREVIOUS_KEY && currentPage > 1) { updatedCurrentPage = currentPage - 1; if (updatedCurrentPage === 1 && focusedItem === PREVIOUS_KEY) { updatedFocusedKey = 1; } } else if (updatedCurrentPage === NEXT_KEY && currentPage < totalPages) { updatedCurrentPage = currentPage + 1; if (updatedCurrentPage === totalPages && updatedFocusedKey === NEXT_KEY) { updatedFocusedKey = totalPages; } } if (onChange && updatedCurrentPage !== undefined) { onChange(updatedCurrentPage); } setFocusedItem(updatedFocusedKey); setInternalCurrentPage(updatedCurrentPage); }, [currentPage, focusedItem, onChange, totalPages]); const renderPreviousPage = () => { const isFirstPageSelected = totalPages > 0 && currentPage === 1; return React__default.createElement(StyledListItem, null, React__default.createElement(Previous, { hidden: isFirstPageSelected, onFocus: () => handleFocus('previous'), onClick: () => handleSelect('previous'), "aria-label": labels?.previous })); }; const renderNextPage = () => { const isLastPageSelected = currentPage === totalPages; return React__default.createElement(StyledListItem, null, React__default.createElement(Next, { hidden: isLastPageSelected, onFocus: () => handleFocus('next'), onClick: () => handleSelect('next'), "aria-label": labels?.next })); }; const createGap = pageIndex => React__default.createElement(Gap, { key: `gap-${pageIndex}`, "aria-label": labels?.gap }); const createPage = pageIndex => { return React__default.createElement(StyledListItem, { key: pageIndex }, React__default.createElement(Page, { onFocus: () => handleFocus(pageIndex), onClick: () => handleSelect(pageIndex), "aria-current": currentPage === pageIndex ? 'page' : undefined, "aria-label": labels?.renderPage?.(pageIndex) }, pageIndex)); }; const renderPages = () => { const pages = []; const PADDING = pagePadding; const GAP = pageGap; for (let pageIndex = 1; pageIndex <= totalPages; pageIndex++) { if (pageIndex === currentPage || pageIndex < GAP || pageIndex > totalPages - GAP + 1) { pages.push(createPage(pageIndex)); continue; } let minimum; let maximum; if (currentPage <= GAP + PADDING) { minimum = GAP + 1; maximum = minimum + PADDING * 2; } else if (currentPage >= totalPages - GAP - PADDING) { maximum = totalPages - GAP; minimum = maximum - PADDING * 2; } else { minimum = currentPage - PADDING; maximum = currentPage + PADDING; } if (pageIndex >= minimum && pageIndex <= currentPage || pageIndex >= currentPage && pageIndex <= maximum) { pages.push(createPage(pageIndex)); continue; } if (pageIndex === GAP) { if (minimum > GAP + 1 && currentPage > GAP + PADDING + 1) { pages.push(createGap(pageIndex)); } else { pages.push(createPage(pageIndex)); } continue; } if (pageIndex === totalPages - GAP + 1) { if (maximum < totalPages - GAP && currentPage < totalPages - GAP - PADDING) { pages.push(createGap(pageIndex)); } else { pages.push(createPage(pageIndex)); } continue; } } return pages; }; return React__default.createElement(StyledNav, { "aria-label": navigationLabel }, React__default.createElement(StyledList, Object.assign({}, otherProps, { ref: ref }), renderPreviousPage(), totalPages > 0 && renderPages(), renderNextPage())); }); OffsetPagination.propTypes = { currentPage: PropTypes.number.isRequired, totalPages: PropTypes.number.isRequired, pagePadding: PropTypes.number, pageGap: PropTypes.number, onChange: PropTypes.func, labels: PropTypes.any }; OffsetPagination.defaultProps = { pagePadding: 2, pageGap: 2 }; OffsetPagination.displayName = 'OffsetPagination'; export { OffsetPagination };