UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

239 lines (236 loc) • 9.95 kB
import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react'; import React__default, { useState } from 'react'; import styled from 'styled-components'; import { get } from '../constants.js'; import { Button } from '../internal/components/ButtonReset.js'; import { LiveRegion, LiveRegionOutlet, Message } from '../internal/components/LiveRegion.js'; import { VisuallyHidden } from '../internal/components/VisuallyHidden.js'; import { warning } from '../utils/warning.js'; const StyledPagination = styled.nav.withConfig({ displayName: "Pagination__StyledPagination", componentId: "sc-1auq45q-0" })(["display:flex;align-items:center;justify-content:space-between;column-gap:1rem;width:100%;grid-area:footer;padding:0.5rem 1rem;border:1px solid ", ";border-top-width:0;border-end-start-radius:6px;border-end-end-radius:6px;.TablePaginationRange{color:", ";font-size:0.75rem;margin:0;}.TablePaginationSteps{display:flex;align-items:center;flex-wrap:wrap;list-style:none;color:", ";font-size:0.875rem;margin:0;padding:0;}.TablePaginationStep:first-of-type{margin-right:1rem;}.TablePaginationStep:last-of-type{margin-left:1rem;}.TablePaginationAction{display:flex;align-items:center;color:", ";font-size:0.875rem;line-height:calc(20 / 14);user-select:none;padding:0.5rem;border-radius:6px;}.TablePaginationAction[data-has-page]{color:", ";}.TablePaginationPage{min-width:2rem;min-height:2rem;display:flex;align-items:center;justify-content:center;font-size:0.875rem;line-height:calc(20 / 14);user-select:none;border-radius:6px;}.TablePaginationAction[data-has-page]:hover,.TablePaginationAction[data-has-page]:focus,.TablePaginationPage:hover,.TablePaginationPage:focus{background-color:", ";transition-duration:0.1s;}.TablePaginationPage[data-active='true']{background-color:", ";color:", ";}.TablePaginationTruncationStep{display:flex;align-items:center;justify-content:center;min-width:2rem;min-height:2rem;user-select:none;}"], get('colors.border.default'), get('colors.fg.muted'), get('colors.fg.default'), get('colors.fg.muted'), get('colors.accent.fg'), get('colors.actionListItem.default.hoverBg'), get('colors.accent.emphasis'), get('colors.fg.onEmphasis')); /** * Specifies the maximum number of items in between the first and last page, * including truncated steps */ const MAX_TRUNCATED_STEP_COUNT = 7; function Pagination({ 'aria-label': label, defaultPageIndex, id, onChange, pageSize = 25, totalCount }) { const { pageIndex, pageStart, pageEnd, pageCount, hasPreviousPage, hasNextPage, selectPage, selectNextPage, selectPreviousPage } = usePagination({ defaultPageIndex, onChange, pageSize, totalCount }); const truncatedPageCount = pageCount > 2 ? Math.min(pageCount - 2, MAX_TRUNCATED_STEP_COUNT) : 0; const [offsetStartIndex, setOffsetStartIndex] = useState(() => { // Set the offset start index to the page at index 1 since we will have the // first page already visible if (pageIndex === 0) { return 1; } return pageIndex; }); const offsetEndIndex = offsetStartIndex + truncatedPageCount - 1; const hasLeadingTruncation = offsetStartIndex >= 2; const hasTrailingTruncation = pageCount - 1 - offsetEndIndex > 1; return /*#__PURE__*/React__default.createElement(LiveRegion, null, /*#__PURE__*/React__default.createElement(LiveRegionOutlet, null), /*#__PURE__*/React__default.createElement(StyledPagination, { "aria-label": label, className: "TablePagination", id: id }, /*#__PURE__*/React__default.createElement(Range, { pageStart: pageStart, pageEnd: pageEnd, totalCount: totalCount }), /*#__PURE__*/React__default.createElement("ol", { className: "TablePaginationSteps" }, /*#__PURE__*/React__default.createElement(Step, null, /*#__PURE__*/React__default.createElement(Button, { className: "TablePaginationAction", type: "button", "data-has-page": hasPreviousPage ? true : undefined, onClick: () => { if (!hasPreviousPage) { return; } selectPreviousPage(); if (hasLeadingTruncation) { if (pageIndex - 1 < offsetStartIndex + 1) { setOffsetStartIndex(offsetStartIndex - 1); } } } }, hasPreviousPage ? /*#__PURE__*/React__default.createElement(ChevronLeftIcon, null) : null, /*#__PURE__*/React__default.createElement("span", { className: "TablePaginationActionLabel" }, "Previous"), /*#__PURE__*/React__default.createElement(VisuallyHidden, null, "\xA0page"))), pageCount > 0 ? /*#__PURE__*/React__default.createElement(Step, null, /*#__PURE__*/React__default.createElement(Page, { active: pageIndex === 0, onClick: () => { selectPage(0); if (pageCount > 1) { setOffsetStartIndex(1); } } }, 1, hasLeadingTruncation ? /*#__PURE__*/React__default.createElement(VisuallyHidden, null, "\u2026") : null)) : null, pageCount > 2 ? Array.from({ length: truncatedPageCount }).map((_, i) => { if (i === 0 && hasLeadingTruncation) { return /*#__PURE__*/React__default.createElement(TruncationStep, { key: `truncation-${i}` }); } if (i === truncatedPageCount - 1 && hasTrailingTruncation) { return /*#__PURE__*/React__default.createElement(TruncationStep, { key: `truncation-${i}` }); } const page = offsetStartIndex + i; return /*#__PURE__*/React__default.createElement(Step, { key: i }, /*#__PURE__*/React__default.createElement(Page, { active: pageIndex === page, onClick: () => { selectPage(page); } }, page + 1, i === truncatedPageCount - 2 && hasTrailingTruncation ? /*#__PURE__*/React__default.createElement(VisuallyHidden, null, "\u2026") : null)); }) : null, pageCount > 1 ? /*#__PURE__*/React__default.createElement(Step, null, /*#__PURE__*/React__default.createElement(Page, { active: pageIndex === pageCount - 1, onClick: () => { selectPage(pageCount - 1); setOffsetStartIndex(pageCount - 1 - truncatedPageCount); } }, pageCount)) : null, /*#__PURE__*/React__default.createElement(Step, null, /*#__PURE__*/React__default.createElement(Button, { className: "TablePaginationAction", type: "button", "data-has-page": hasNextPage ? true : undefined, onClick: () => { if (!hasNextPage) { return; } selectNextPage(); if (hasTrailingTruncation) { if (pageIndex + 1 > offsetEndIndex - 1) { setOffsetStartIndex(offsetStartIndex + 1); } } } }, /*#__PURE__*/React__default.createElement("span", { className: "TablePaginationActionLabel" }, "Next"), /*#__PURE__*/React__default.createElement(VisuallyHidden, null, "\xA0page"), hasNextPage ? /*#__PURE__*/React__default.createElement(ChevronRightIcon, null) : null))))); } Pagination.displayName = "Pagination"; function Range({ pageStart, pageEnd, totalCount }) { const start = pageStart + 1; const end = pageEnd === totalCount - 1 ? totalCount : pageEnd; return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Message, { value: `Showing ${start} through ${end} of ${totalCount}` }), /*#__PURE__*/React__default.createElement("p", { className: "TablePaginationRange" }, start, /*#__PURE__*/React__default.createElement(VisuallyHidden, { as: "span" }, "\xA0through\xA0"), /*#__PURE__*/React__default.createElement("span", { "aria-hidden": "true" }, "\u2012"), end, " of ", totalCount)); } function TruncationStep() { return /*#__PURE__*/React__default.createElement("li", { "aria-hidden": "true", className: "TablePaginationTruncationStep" }, "\u2026"); } TruncationStep.displayName = "TruncationStep"; function Step({ children }) { return /*#__PURE__*/React__default.createElement("li", { className: "TablePaginationStep" }, children); } Step.displayName = "Step"; function Page({ active, children, onClick }) { return /*#__PURE__*/React__default.createElement(Button, { className: "TablePaginationPage", type: "button", "data-active": active ? true : undefined, "aria-current": active ? true : undefined, onClick: onClick }, /*#__PURE__*/React__default.createElement(VisuallyHidden, null, "Page\xA0"), children); } Page.displayName = "Page"; function usePagination(config) { const { defaultPageIndex, onChange, pageSize, totalCount } = config; const pageCount = Math.ceil(totalCount / pageSize); const [pageIndex, setPageIndex] = useState(() => { if (defaultPageIndex !== undefined) { if (defaultPageIndex >= 0 && defaultPageIndex < pageCount) { return defaultPageIndex; } process.env.NODE_ENV !== "production" ? warning(true, // eslint-disable-next-line github/unescaped-html-literal '<Pagination> expected `defaultPageIndex` to be less than the ' + 'total number of pages. Instead, received a `defaultPageIndex` ' + 'of %s with %s total pages.', defaultPageIndex, pageCount) : void 0; } return 0; }); const pageStart = pageIndex * pageSize; const pageEnd = Math.min(pageIndex * pageSize + pageSize, totalCount - 1); const hasNextPage = pageIndex + 1 < pageCount; const hasPreviousPage = pageIndex > 0; function selectPage(newPageIndex) { if (pageIndex !== newPageIndex) { setPageIndex(newPageIndex); onChange === null || onChange === void 0 ? void 0 : onChange({ pageIndex: newPageIndex }); } } function selectPreviousPage() { if (hasPreviousPage) { selectPage(pageIndex - 1); } } function selectNextPage() { if (hasNextPage) { selectPage(pageIndex + 1); } } return { pageIndex, pageStart, pageEnd, pageCount, hasNextPage, hasPreviousPage, selectPage, selectPreviousPage, selectNextPage }; } export { Pagination };