@carbon/react
Version:
React components for the Carbon Design System
358 lines (346 loc) • 13.3 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var iconsReact = require('@carbon/icons-react');
var cx = require('classnames');
var PropTypes = require('prop-types');
var React = require('react');
var Select = require('../Select/Select.js');
require('../Select/Select.Skeleton.js');
var SelectItem = require('../SelectItem/SelectItem.js');
var useId = require('../../internal/useId.js');
var usePrefix = require('../../internal/usePrefix.js');
var index = require('../IconButton/index.js');
var isEqual = require('react-fast-compare');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual);
var _CaretLeft, _CaretRight;
function mapPageSizesToObject(sizes) {
return typeof sizes[0] === 'object' && sizes[0] !== null ? sizes : sizes.map(size => ({
text: size,
value: size
}));
}
function renderSelectItems(total) {
let counter = 1;
const itemArr = [];
while (counter <= total) {
itemArr.push(/*#__PURE__*/React__default["default"].createElement(SelectItem["default"], {
key: counter,
value: counter,
text: String(counter)
}));
counter++;
}
return itemArr;
}
function getPageSize(pageSizes, pageSize) {
if (pageSize) {
const hasSize = pageSizes.find(size => {
return pageSize === size.value;
});
if (hasSize) {
return pageSize;
}
}
return pageSizes[0].value;
}
const Pagination = /*#__PURE__*/React__default["default"].forwardRef(function Pagination({
backwardText = 'Previous page',
className: customClassName = '',
disabled = false,
forwardText = 'Next page',
id,
isLastPage = false,
itemText = (min, max) => `${min}–${max} items`,
itemRangeText = (min, max, total) => `${min}–${max} of ${total} items`,
itemsPerPageText = 'Items per page:',
onChange,
pageNumberText: _pageNumberText = 'Page Number',
pageRangeText = (_current, total) => `of ${total} ${total === 1 ? 'page' : 'pages'}`,
page: controlledPage = 1,
pageInputDisabled,
pageSize: controlledPageSize,
pageSizeInputDisabled,
pageSizes: controlledPageSizes,
pageText = page => `page ${page}`,
pagesUnknown = false,
size = 'md',
totalItems,
...rest
}, ref) {
const prefix = usePrefix.usePrefix();
const inputId = useId.useFallbackId(id?.toString());
const backBtnRef = React.useRef(null);
const forwardBtnRef = React.useRef(null);
const [pageSizes, setPageSizes] = React.useState(() => {
return mapPageSizesToObject(controlledPageSizes);
});
const [prevPageSizes, setPrevPageSizes] = React.useState(controlledPageSizes);
const [page, setPage] = React.useState(controlledPage);
const [prevControlledPage, setPrevControlledPage] = React.useState(controlledPage);
const [pageSize, setPageSize] = React.useState(() => {
return getPageSize(pageSizes, controlledPageSize);
});
const [prevControlledPageSize, setPrevControlledPageSize] = React.useState(controlledPageSize);
const className = cx__default["default"]({
[`${prefix}--pagination`]: true,
[`${prefix}--pagination--${size}`]: size,
[customClassName]: !!customClassName
});
const totalPages = totalItems ? Math.max(Math.ceil(totalItems / pageSize), 1) : 1;
const backButtonDisabled = disabled || page === 1;
const backButtonClasses = cx__default["default"]({
[`${prefix}--pagination__button`]: true,
[`${prefix}--pagination__button--backward`]: true,
[`${prefix}--pagination__button--no-index`]: backButtonDisabled
});
const forwardButtonDisabled = disabled || page === totalPages && !pagesUnknown;
const forwardButtonClasses = cx__default["default"]({
[`${prefix}--pagination__button`]: true,
[`${prefix}--pagination__button--forward`]: true,
[`${prefix}--pagination__button--no-index`]: forwardButtonDisabled
});
const selectItems = renderSelectItems(totalPages);
// Sync state with props
if (controlledPage !== prevControlledPage) {
setPage(controlledPage);
setPrevControlledPage(controlledPage);
}
if (controlledPageSize !== prevControlledPageSize) {
setPageSize(getPageSize(pageSizes, controlledPageSize));
setPrevControlledPageSize(controlledPageSize);
}
if (!isEqual__default["default"](controlledPageSizes, prevPageSizes)) {
const pageSizes = mapPageSizesToObject(controlledPageSizes);
const hasPageSize = pageSizes.find(size => {
return size.value === pageSize;
});
// Reset page to 1 if the current pageSize is not included in the new page
// sizes
if (!hasPageSize) {
setPage(1);
}
setPageSizes(pageSizes);
setPrevPageSizes(controlledPageSizes);
}
function handleSizeChange(event) {
const pageSize = Number(event.target.value);
const changes = {
pageSize,
page: 1
};
setPage(changes.page);
setPageSize(changes.pageSize);
if (onChange) {
onChange(changes);
}
}
function handlePageInputChange(event) {
const page = Number(event.target.value);
if (page > 0 && totalItems && page <= Math.max(Math.ceil(totalItems / pageSize), 1)) {
setPage(page);
if (onChange) {
onChange({
page,
pageSize
});
}
}
}
function incrementPage() {
const nextPage = page + 1;
setPage(nextPage);
// when the increment button reaches the last page,
// the icon button becomes disabled and the focus shifts to `main`
// this presents an a11y problem for keyboard & screen reader users
// instead, we want the focus to shift to the other pagination btn
if (nextPage === totalPages && backBtnRef?.current) {
backBtnRef.current.focus();
}
if (onChange) {
onChange({
page: nextPage,
pageSize,
ref: backBtnRef
});
}
}
function decrementPage() {
const nextPage = page - 1;
setPage(nextPage);
// when the decrement button reaches the first page,
// the icon button becomes disabled and the focus shifts to `main`
// this presents an a11y problem for keyboard & screen reader users
// instead, we want the focus to shift to the other pagination btn
if (nextPage === 1 && forwardBtnRef?.current) {
forwardBtnRef.current.focus();
}
if (onChange) {
onChange({
page: nextPage,
pageSize,
ref: forwardBtnRef
});
}
}
return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({
className: className,
ref: ref
}, rest), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--pagination__left`
}, /*#__PURE__*/React__default["default"].createElement("label", {
id: `${prefix}-pagination-select-${inputId}-count-label`,
className: `${prefix}--pagination__text`,
htmlFor: `${prefix}-pagination-select-${inputId}`
}, itemsPerPageText), /*#__PURE__*/React__default["default"].createElement(Select["default"], {
id: `${prefix}-pagination-select-${inputId}`,
className: `${prefix}--select__item-count`,
labelText: "",
hideLabel: true,
noLabel: true,
inline: true,
onChange: handleSizeChange,
disabled: pageSizeInputDisabled || disabled,
value: pageSize
}, pageSizes.map(sizeObj => /*#__PURE__*/React__default["default"].createElement(SelectItem["default"], {
key: sizeObj.value,
value: sizeObj.value,
text: String(sizeObj.text)
}))), /*#__PURE__*/React__default["default"].createElement("span", {
className: `${prefix}--pagination__text ${prefix}--pagination__items-count`
}, pagesUnknown || !totalItems ? totalItems === 0 ? itemRangeText(0, 0, 0) : itemText(pageSize * (page - 1) + 1, page * pageSize) : itemRangeText(Math.min(pageSize * (page - 1) + 1, totalItems), Math.min(page * pageSize, totalItems), totalItems))), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--pagination__right`
}, pagesUnknown ? /*#__PURE__*/React__default["default"].createElement("span", {
className: `${prefix}--pagination__text ${prefix}--pagination__page-text ${prefix}--pagination__unknown-pages-text`
}, pageText(page)) : /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Select["default"], {
id: `${prefix}-pagination-select-${inputId}-right`,
className: `${prefix}--select__page-number`,
labelText: `Page of ${totalPages} pages`,
inline: true,
hideLabel: true,
onChange: handlePageInputChange,
value: page,
disabled: pageInputDisabled || disabled
}, selectItems), /*#__PURE__*/React__default["default"].createElement("span", {
className: `${prefix}--pagination__text`
}, pageRangeText(page, totalPages))), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--pagination__control-buttons`
}, /*#__PURE__*/React__default["default"].createElement(index.IconButton, {
align: "top",
disabled: backButtonDisabled,
kind: "ghost",
className: backButtonClasses,
label: backwardText,
"aria-label": backwardText,
onClick: decrementPage,
ref: backBtnRef
}, _CaretLeft || (_CaretLeft = /*#__PURE__*/React__default["default"].createElement(iconsReact.CaretLeft, null))), /*#__PURE__*/React__default["default"].createElement(index.IconButton, {
align: "top-end",
disabled: forwardButtonDisabled || isLastPage,
kind: "ghost",
className: forwardButtonClasses,
label: forwardText,
"aria-label": forwardText,
onClick: incrementPage,
ref: forwardBtnRef
}, _CaretRight || (_CaretRight = /*#__PURE__*/React__default["default"].createElement(iconsReact.CaretRight, null))))));
});
Pagination.propTypes = {
/**
* The description for the backward icon.
*/
backwardText: PropTypes__default["default"].string,
/**
* The CSS class names.
*/
className: PropTypes__default["default"].string,
/**
* `true` if the backward/forward buttons, as well as the page select elements, should be disabled.
*/
disabled: PropTypes__default["default"].bool,
/**
* The description for the forward icon.
*/
forwardText: PropTypes__default["default"].string,
/**
* The unique ID of this component instance.
*/
id: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
// TODO: remove when v9 is deprecated
/**
* `true` if the current page should be the last page.
*/
isLastPage: PropTypes__default["default"].bool,
/**
* The function returning a translatable text showing where the current page is,
* in a manner of the range of items.
*/
itemRangeText: PropTypes__default["default"].func,
/**
* A variant of `itemRangeText`, used if the total number of items is unknown.
*/
itemText: PropTypes__default["default"].func,
/**
* The translatable text indicating the number of items per page.
*/
itemsPerPageText: PropTypes__default["default"].string,
/**
* The callback function called when the current page changes.
*/
onChange: PropTypes__default["default"].func,
/**
* The current page.
*/
page: PropTypes__default["default"].number,
/**
* `true` if the select box to change the page should be disabled.
*/
pageInputDisabled: PropTypes__default["default"].bool,
pageNumberText: PropTypes__default["default"].string,
/**
* A function returning PII showing where the current page is.
*/
pageRangeText: PropTypes__default["default"].func,
/**
* The number dictating how many items a page contains.
*/
pageSize: PropTypes__default["default"].number,
/**
* `true` if the select box to change the items per page should be disabled.
*/
pageSizeInputDisabled: PropTypes__default["default"].bool,
/**
* The choices for `pageSize`.
*/
pageSizes: PropTypes__default["default"].oneOfType([PropTypes__default["default"].arrayOf(PropTypes__default["default"].number.isRequired), PropTypes__default["default"].arrayOf(PropTypes__default["default"].shape({
text: PropTypes__default["default"].string.isRequired,
value: PropTypes__default["default"].number.isRequired
}).isRequired)]).isRequired,
/**
* The translatable text showing the current page.
*/
pageText: PropTypes__default["default"].func,
/**
* `true` if the total number of items is unknown.
*/
pagesUnknown: PropTypes__default["default"].bool,
/**
* Specify the size of the Pagination.
*/
size: PropTypes__default["default"].oneOf(['sm', 'md', 'lg']),
/**
* The total number of items.
*/
totalItems: PropTypes__default["default"].number
};
exports["default"] = Pagination;