UNPKG

@carbon/react

Version:

React components for the Carbon Design System

352 lines (350 loc) 13.6 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_usePrefix = require("../../internal/usePrefix.js"); const require_index = require("../IconButton/index.js"); const require_clamp = require("../../internal/clamp.js"); const require_useMatchMedia = require("../../internal/useMatchMedia.js"); let classnames = require("classnames"); classnames = require_runtime.__toESM(classnames); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let react_jsx_runtime = require("react/jsx-runtime"); let _carbon_icons_react = require("@carbon/icons-react"); let _carbon_layout = require("@carbon/layout"); //#region src/components/PaginationNav/PaginationNav.tsx /** * Copyright IBM Corp. 2020, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const translationIds = { "carbon.pagination-nav.next": "carbon.pagination-nav.next", "carbon.pagination-nav.previous": "carbon.pagination-nav.previous", "carbon.pagination-nav.item": "carbon.pagination-nav.item", "carbon.pagination-nav.active": "carbon.pagination-nav.active", "carbon.pagination-nav.of": "carbon.pagination-nav.of" }; const defaultTranslations = { [translationIds["carbon.pagination-nav.next"]]: "Next", [translationIds["carbon.pagination-nav.previous"]]: "Previous", [translationIds["carbon.pagination-nav.item"]]: "Page", [translationIds["carbon.pagination-nav.active"]]: "Active", [translationIds["carbon.pagination-nav.of"]]: "of" }; const defaultTranslateWithId = (messageId) => { return defaultTranslations[messageId]; }; function usePrevious(value) { const ref = (0, react.useRef)(null); (0, react.useEffect)(() => { ref.current = value; }); return ref.current; } function calculateCuts(page, totalItems, itemsDisplayedOnPage, splitPoint = null) { if (itemsDisplayedOnPage >= totalItems) return { front: 0, back: 0 }; const split = splitPoint || Math.ceil(itemsDisplayedOnPage / 2) - 1; let frontHidden = page + 1 - split; let backHidden = totalItems - page - (itemsDisplayedOnPage - split) + 1; if (frontHidden <= 1) { backHidden -= frontHidden <= 0 ? Math.abs(frontHidden) + 1 : 0; frontHidden = 0; } if (backHidden <= 1) { frontHidden -= backHidden <= 0 ? Math.abs(backHidden) + 1 : 0; backHidden = 0; } return { front: frontHidden, back: backHidden }; } function DirectionButton({ direction, label, disabled, onClick, tooltipAlignment = "center", tooltipPosition = "bottom" }) { const prefix = require_usePrefix.usePrefix(); const align = tooltipAlignment === "center" ? tooltipPosition : `${tooltipPosition}-${tooltipAlignment}`; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: `${prefix}--pagination-nav__list-item`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_index.IconButton, { align, disabled, kind: "ghost", label, onClick, children: direction === "forward" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.CaretRight, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.CaretLeft, {}) }) }); } function PaginationItem({ page, isActive, onClick, translateWithId: t = defaultTranslateWithId }) { const prefix = require_usePrefix.usePrefix(); const itemLabel = t("carbon.pagination-nav.item"); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: `${prefix}--pagination-nav__list-item`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: (0, classnames.default)(`${prefix}--pagination-nav__page`, { [`${prefix}--pagination-nav__page--active`]: isActive }), onClick, "data-page": page, "aria-current": isActive ? "page" : void 0, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${prefix}--pagination-nav__accessibility-label`, children: isActive ? `${t("carbon.pagination-nav.active")}, ${itemLabel}` : itemLabel }), page] }) }); } function PaginationOverflow({ fromIndex = NaN, count = NaN, onSelect, disableOverflow, translateWithId: t = defaultTranslateWithId }) { const prefix = require_usePrefix.usePrefix(); if (disableOverflow === true && count > 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: `${prefix}--pagination-nav__list-item`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `${prefix}--pagination-nav__select`, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", { className: `${prefix}--pagination-nav__page ${prefix}--pagination-nav__page--select`, "aria-label": `Select ${t("carbon.pagination-nav.item")} number`, disabled: true }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--pagination-nav__select-icon-wrapper`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.OverflowMenuHorizontal, { className: `${prefix}--pagination-nav__select-icon` }) })] }) }); if (count > 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: `${prefix}--pagination-nav__list-item`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `${prefix}--pagination-nav__select`, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { className: `${prefix}--pagination-nav__page ${prefix}--pagination-nav__page--select`, "aria-label": `Select ${t("carbon.pagination-nav.item")} number`, onChange: (e) => { const index = Number(e.target.value); onSelect?.(index); }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "", hidden: true }), [...Array(count)].map((e, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: (fromIndex + i).toString(), "data-page": fromIndex + i + 1, children: fromIndex + i + 1 }, `overflow-${fromIndex + i}`))] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--pagination-nav__select-icon-wrapper`, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.OverflowMenuHorizontal, { className: `${prefix}--pagination-nav__select-icon` }) })] }) }); if (count === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationItem, { page: fromIndex + 1, translateWithId: t, onClick: () => { onSelect?.(fromIndex); } }); return null; } const PaginationNav = react.default.forwardRef(({ className, onChange = () => {}, totalItems = NaN, disableOverflow, itemsShown = 10, page = 0, loop = false, size = "lg", tooltipAlignment, tooltipPosition, translateWithId: t = defaultTranslateWithId, ...rest }, ref) => { const isSm = require_useMatchMedia.useMatchMedia(`(max-width: ${_carbon_layout.breakpoints.sm.width})`); let numberOfPages; switch (size) { case "md": numberOfPages = itemsShown === 4 ? itemsShown : 5; break; case "sm": numberOfPages = require_clamp.clamp(itemsShown, 4, 7); break; default: numberOfPages = 4; break; } const [currentPage, setCurrentPage] = (0, react.useState)(page); const [itemsDisplayedOnPage, setItemsDisplayedOnPage] = (0, react.useState)(itemsShown >= 4 && !isSm ? itemsShown : numberOfPages); const [cuts, setCuts] = (0, react.useState)(calculateCuts(currentPage, totalItems, itemsDisplayedOnPage)); const prevPage = usePrevious(currentPage); const prefix = require_usePrefix.usePrefix(); const [isOverflowDisabled, setIsOverFlowDisabled] = (0, react.useState)(disableOverflow); function jumpToItem(index) { if (index >= 0 && index < totalItems) { setCurrentPage(index); onChange(index); } } function jumpToNext() { const nextIndex = currentPage + 1; if (nextIndex >= totalItems) { if (loop) jumpToItem(0); } else jumpToItem(nextIndex); } function jumpToPrevious() { const previousIndex = currentPage - 1; if (previousIndex < 0) { if (loop) jumpToItem(totalItems - 1); } else jumpToItem(previousIndex); } function pageWouldBeHidden(page) { const wouldBeHiddenInFront = page >= (itemsDisplayedOnPage <= 4 && page > 1 ? 0 : 1) && page <= cuts.front || page === 0; const wouldBeHiddenInBack = page >= totalItems - cuts.back - 1 && page <= totalItems - 2; return wouldBeHiddenInFront || wouldBeHiddenInBack; } (0, react.useEffect)(() => { setCurrentPage(page); }, [page]); (0, react.useEffect)(() => { const itemsToBeShown = itemsShown >= 4 && !isSm ? itemsShown : numberOfPages; setItemsDisplayedOnPage(Math.max(itemsToBeShown, 4)); setCuts(calculateCuts(currentPage, totalItems, Math.max(itemsToBeShown, 4))); }, [ totalItems, itemsShown, isSm, size ]); (0, react.useEffect)(() => { if (pageWouldBeHidden(currentPage)) if (currentPage - (prevPage || 0) > 0) setCuts(calculateCuts(currentPage, totalItems, itemsDisplayedOnPage, itemsDisplayedOnPage - 3)); else setCuts(calculateCuts(currentPage, totalItems, itemsDisplayedOnPage, itemsDisplayedOnPage > 4 ? 2 : 1)); }, [currentPage]); (0, react.useEffect)(() => { setIsOverFlowDisabled(disableOverflow); }, [disableOverflow]); const classNames = (0, classnames.default)(`${prefix}--pagination-nav`, className, { [`${prefix}--layout--size-${size}`]: size }); const backwardButtonDisabled = !loop && currentPage === 0; const forwardButtonDisabled = !loop && currentPage === totalItems - 1; const startOffset = itemsDisplayedOnPage <= 4 && currentPage > 1 ? 0 : 1; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", { className: classNames, ref, ...rest, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", { className: `${prefix}--pagination-nav__list`, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DirectionButton, { direction: "backward", "aria-label": t("carbon.pagination-nav.previous"), label: t("carbon.pagination-nav.previous"), disabled: backwardButtonDisabled, onClick: jumpToPrevious, tooltipAlignment, tooltipPosition }), (itemsDisplayedOnPage >= 5 || itemsDisplayedOnPage <= 4 && currentPage <= 1) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationItem, { page: 1, translateWithId: t, isActive: currentPage === 0, onClick: () => { jumpToItem(0); } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationOverflow, { fromIndex: startOffset, count: cuts.front, onSelect: jumpToItem, disableOverflow: isOverflowDisabled }), [...Array(totalItems)].map((e, i) => i).slice(startOffset + cuts.front, (1 + cuts.back) * -1).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationItem, { page: item + 1, translateWithId: t, isActive: currentPage === item, onClick: () => { jumpToItem(item); } }, `item-${item}`)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationOverflow, { fromIndex: totalItems - cuts.back - 1, count: cuts.back, onSelect: jumpToItem, disableOverflow: isOverflowDisabled }), totalItems > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationItem, { page: totalItems, translateWithId: t, isActive: currentPage === totalItems - 1, onClick: () => { jumpToItem(totalItems - 1); } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DirectionButton, { direction: "forward", "aria-label": t("carbon.pagination-nav.next"), label: t("carbon.pagination-nav.next"), disabled: forwardButtonDisabled, onClick: jumpToNext, tooltipAlignment, tooltipPosition }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { "aria-live": "polite", "aria-atomic": "true", className: `${prefix}--pagination-nav__accessibility-label`, children: `${t("carbon.pagination-nav.item")} ${currentPage + 1} ${t("carbon.pagination-nav.of")} ${totalItems}` })] }); }); DirectionButton.propTypes = { direction: prop_types.default.oneOf(["forward", "backward"]), disabled: prop_types.default.bool, label: prop_types.default.string, onClick: prop_types.default.func, tooltipAlignment: prop_types.default.oneOf([ "start", "center", "end" ]), tooltipPosition: prop_types.default.oneOf([ "top", "right", "bottom", "left" ]) }; PaginationItem.propTypes = { isActive: prop_types.default.bool, onClick: prop_types.default.func, page: prop_types.default.number, translateWithId: prop_types.default.func }; PaginationOverflow.propTypes = { count: prop_types.default.number, fromIndex: prop_types.default.number, onSelect: prop_types.default.func, translateWithId: prop_types.default.func }; PaginationNav.displayName = "PaginationNav"; PaginationNav.propTypes = { className: prop_types.default.string, disableOverflow: prop_types.default.bool, itemsShown: prop_types.default.number, loop: prop_types.default.bool, onChange: prop_types.default.func, page: prop_types.default.number, size: prop_types.default.oneOf([ "sm", "md", "lg" ]), tooltipAlignment: prop_types.default.oneOf([ "start", "center", "end" ]), tooltipPosition: prop_types.default.oneOf([ "top", "right", "bottom", "left" ]), totalItems: prop_types.default.number, translateWithId: prop_types.default.func }; //#endregion exports.default = PaginationNav;