grommet
Version:
focus on the essential experience
236 lines (231 loc) • 10.4 kB
JavaScript
var _excluded = ["a11yTitle", "aria-label", "numberItems", "numberEdgePages", "numberMiddlePages", "onChange", "messages", "page", "size", "step", "stepOptions", "summary"];
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import React, { forwardRef, useContext, useEffect, useState } from 'react';
import styled from 'styled-components';
import { DataContext } from '../../contexts/DataContext';
import { Box } from '../Box';
import { Nav } from '../Nav';
import { PageControl } from './PageControl';
import { PaginationStep } from './PaginationStep';
import { PaginationSummary } from './PaginationSummary';
import { PaginationPropTypes } from './propTypes';
import { useThemeValue } from '../../utils/useThemeValue';
var StyledPaginationContainer = styled(Box).withConfig({
displayName: "Pagination__StyledPaginationContainer",
componentId: "sc-rnlw6m-0"
})(["", ""], function (props) {
return props.theme.pagination.container && props.theme.pagination.container.extend;
});
var getPageIndices = function getPageIndices(begin, end) {
var indices = [];
for (var i = begin; i <= end; i += 1) {
indices.push(i);
}
return indices;
};
var Pagination = /*#__PURE__*/forwardRef(function (_ref, ref) {
var _ref2, _theme$pagination$con;
var a11yTitle = _ref.a11yTitle,
ariaLabel = _ref['aria-label'],
numberItems = _ref.numberItems,
_ref$numberEdgePages = _ref.numberEdgePages,
numberEdgePages = _ref$numberEdgePages === void 0 ? 1 : _ref$numberEdgePages,
_ref$numberMiddlePage = _ref.numberMiddlePages,
numberMiddlePagesProp = _ref$numberMiddlePage === void 0 ? 3 : _ref$numberMiddlePage,
onChange = _ref.onChange,
messages = _ref.messages,
pageProp = _ref.page,
size = _ref.size,
stepProp = _ref.step,
stepOptions = _ref.stepOptions,
summary = _ref.summary,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useThemeValue = useThemeValue(),
theme = _useThemeValue.theme,
passThemeFlag = _useThemeValue.passThemeFlag;
var _useContext = useContext(DataContext),
onView = _useContext.onView,
filteredTotal = _useContext.filteredTotal,
view = _useContext.view;
var _useState = useState(stepProp || (view == null ? void 0 : view.step) || 10),
step = _useState[0],
setStep = _useState[1];
var total = (_ref2 = numberItems != null ? numberItems : filteredTotal) != null ? _ref2 : 0;
var page = pageProp || (view == null ? void 0 : view.page) || 1;
/* Calculate total number pages */
var totalPages = Math.ceil(total / step);
var _useState2 = useState(Math.min(page, totalPages) || 1),
activePage = _useState2[0],
setActivePage = _useState2[1];
useEffect(function () {
if (stepProp) setStep(stepProp);
}, [stepProp]);
useEffect(function () {
setActivePage(page);
var pageEvent = new Event('pagechange');
window.dispatchEvent(pageEvent);
}, [page]);
useEffect(function () {
// if we are getting the step or page from outside the view,
// update the Data's view in case it needs to filter.
if (onView && ((view == null ? void 0 : view.step) !== step || (view == null ? void 0 : view.page) !== page)) onView(_extends({}, view, {
page: page,
step: step
}));
}, [onView, page, step, view]);
/* Define page indices to display */
var beginPages = getPageIndices(1, Math.min(numberEdgePages, totalPages));
var endPages = getPageIndices(Math.max(totalPages - numberEdgePages + 1, numberEdgePages + 1), totalPages);
var numberMiddlePages;
if (numberMiddlePagesProp < 1) {
numberMiddlePages = 1;
console.warn(// eslint-disable-next-line max-len
"Property \"numberMiddlePages\" should not be < 1. One middle page button will be shown. Set \"numberMiddlePages\" >= 1 to remove this warning.");
} else numberMiddlePages = numberMiddlePagesProp;
var startingMiddlePages;
// odd
if (numberMiddlePages % 2) startingMiddlePages = Math.min(activePage - Math.floor(numberMiddlePages / 2), totalPages - numberEdgePages - numberMiddlePages);
// even, cannot split equally around active page
// let extra page appear on middlePagesEnd instead
else startingMiddlePages = Math.min(activePage - Math.floor(numberMiddlePages / 2) + 1, totalPages - numberEdgePages - numberMiddlePages);
var middlePagesBegin = Math.max(startingMiddlePages, numberEdgePages + 2);
var middlePagesEnd = Math.min(Math.max(activePage + Math.floor(numberMiddlePages / 2), numberEdgePages + numberMiddlePages + 1), endPages.length > 0 ? endPages[0] - 2 : totalPages - 1);
var middlePages = getPageIndices(middlePagesBegin, middlePagesEnd);
var beginFlex = [];
if (middlePagesBegin > numberEdgePages + 2) beginFlex = ['more-prev'];else if (numberEdgePages + 1 < totalPages - numberEdgePages) beginFlex = [numberEdgePages + 1];
var endFlex = [];
if (middlePagesEnd < totalPages - numberEdgePages - 1) endFlex = ['more-next'];else if (totalPages - numberEdgePages > numberEdgePages) endFlex = [totalPages - numberEdgePages];
var getItemIndices = function getItemIndices(nextPage) {
var startIndex = step * (nextPage - 1);
var endIndex = startIndex + step;
return {
startIndex: startIndex,
endIndex: endIndex
};
};
var handleClick = function handleClick(event, nextPage) {
setActivePage(nextPage);
if (onView) onView(_extends({}, view, {
page: nextPage
}));
if (onChange) {
event.persist();
var adjustedEvent = event;
adjustedEvent.page = nextPage;
// for controlled use cases, provide user with info on
// what range of indices should be displayed given the active page
var _getItemIndices = getItemIndices(nextPage),
startIndex = _getItemIndices.startIndex,
endIndex = _getItemIndices.endIndex;
adjustedEvent.startIndex = startIndex;
adjustedEvent.endIndex = endIndex;
onChange(adjustedEvent);
}
};
var NextIcon = theme.pagination.icons.next;
var PreviousIcon = theme.pagination.icons.previous;
var iconColor = theme.pagination.icons.color;
var navProps = {
next: {
// https://a11y-style-guide.com/style-guide/section-navigation.html#kssref-navigation-pagination
'aria-disabled': activePage === totalPages ? 'true' : undefined,
disabled: activePage === totalPages || !total,
icon: /*#__PURE__*/React.createElement(NextIcon, {
color: iconColor
}),
onClick: function onClick(event) {
var nextPage = activePage + 1;
handleClick(event, nextPage);
},
label: undefined
},
previous: {
'aria-disabled': activePage === 1 ? 'true' : undefined,
disabled: activePage === 1 || !total,
icon: /*#__PURE__*/React.createElement(PreviousIcon, {
color: iconColor
}),
onClick: function onClick(event) {
var previousPage = activePage - 1;
handleClick(event, previousPage);
},
label: undefined
}
};
var controls = ['previous'].concat(beginPages, beginFlex, middlePages, endFlex, endPages, ['next']);
/* Set props for each page index. Each page index should display a
* clickable index, control, or placeholder (e.g. ellipsis) indicating
* more pages are available.
*/
controls = controls.map(function (control) {
return _extends({
active: control === activePage,
a11yTitle: typeof control === 'number' ? "Go to page " + control : "Go to " + control + " page",
// https://a11y-style-guide.com/style-guide/section-navigation.html#kssref-navigation-pagination
// https://www.w3.org/TR/wai-aria-1.1/#aria-current
'aria-current': control === activePage ? 'page' : undefined,
control: control,
onClick: function onClick(event) {
handleClick(event, control);
},
separator: control === 'more-prev' || control === 'more-next'
}, navProps[control]);
});
var paginationControls = /*#__PURE__*/React.createElement(Nav, {
a11yTitle: ariaLabel || a11yTitle || 'Pagination Navigation',
ref: ref
}, /*#__PURE__*/React.createElement(Box, _extends({
as: "ul"
}, theme.pagination.controls, {
cssGap: true
}), controls.map(function (control, index) {
return (
/*#__PURE__*/
/* Using index as key (as opposed to a unique id) seems to
* help React prioritize rendering the updated controls as
* desired. Whereas, using a unique id resulted in rendering
* the active control with an undesired lag. */
// eslint-disable-next-line react/no-array-index-key
React.createElement(PageControl, _extends({
key: index,
size: size
}, control))
);
})));
// for backwards compatibility
if (!summary && !stepOptions) return /*#__PURE__*/React.createElement(StyledPaginationContainer, _extends({
flex: false
}, _extends({}, theme.pagination.container, {
gap: undefined
}), passThemeFlag, rest), paginationControls);
return /*#__PURE__*/React.createElement(StyledPaginationContainer, _extends({
direction: "row",
align: "center",
wrap: true,
flex: false
}, theme.pagination.container, passThemeFlag, rest), /*#__PURE__*/React.createElement(Box, {
flex: "grow"
}, summary && /*#__PURE__*/React.createElement(PaginationSummary, {
messages: messages,
page: activePage,
step: step,
numberItems: total
})), /*#__PURE__*/React.createElement(Box, {
align: "center",
direction: "row",
gap: (_theme$pagination$con = theme.pagination.container) == null ? void 0 : _theme$pagination$con.gap,
wrap: true
}, stepOptions && /*#__PURE__*/React.createElement(PaginationStep, {
messages: messages,
options: Array.isArray(stepOptions) ? stepOptions : undefined,
step: step,
onChange: function onChange(_ref3) {
var value = _ref3.value;
return setStep(value);
}
}), paginationControls));
});
Pagination.displayName = 'Pagination';
Pagination.propTypes = PaginationPropTypes;
export { Pagination };