@zendeskgarden/react-pagination
Version:
Components relating to pagination in the Garden Design System
560 lines (530 loc) • 23 kB
JavaScript
/**
* 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.
*/
;
var React = require('react');
var PropTypes = require('prop-types');
var containerUtilities = require('@zendeskgarden/container-utilities');
var reactTheming = require('@zendeskgarden/react-theming');
var styled = require('styled-components');
var polished = require('polished');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespace(React);
var PropTypes__default = /*#__PURE__*/_interopDefault(PropTypes);
var styled__default = /*#__PURE__*/_interopDefault(styled);
const COMPONENT_ID$8 = 'pagination.list';
const colorStyles$2 = _ref => {
let {
theme
} = _ref;
return styled.css(["color:", ";"], reactTheming.getColor({
variable: 'foreground.subtle',
theme
}));
};
const StyledList = styled__default.default.ul.attrs({
'data-garden-id': COMPONENT_ID$8,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledList",
componentId: "sc-1uz2jxo-0"
})(["direction:", ";display:flex;justify-content:center;margin:0;padding:0;list-style:none;white-space:nowrap;", " &:focus{outline:none;}", ";"], p => p.theme.rtl && 'rtl', colorStyles$2, reactTheming.componentStyles);
const COMPONENT_ID$7 = 'pagination.list_item';
const StyledListItem = styled__default.default.li.attrs({
'data-garden-id': COMPONENT_ID$7,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledListItem",
componentId: "sc-16j4sju-0"
})(["box-sizing:border-box;margin-left:", ";user-select:none;&", "{margin-left:0;}", ";"], props => `${props.theme.space.base}px`, props => props.theme.rtl ? ':last-of-type' : ':first-of-type', reactTheming.componentStyles);
const COMPONENT_ID$6 = 'pagination.page';
const colorStyles$1 = _ref => {
let {
theme
} = _ref;
const disabledColor = reactTheming.getColor({
variable: 'foreground.disabled',
theme
});
const defaultColor = reactTheming.getColor({
variable: 'foreground.subtle',
theme
});
const hoverForegroundColor = reactTheming.getColor({
variable: 'foreground.subtle',
light: {
offset: 100
},
dark: {
offset: -100
},
theme
});
const hoverBackgroundColor = reactTheming.getColor({
variable: 'background.primaryEmphasis',
transparency: theme.opacity[100],
dark: {
offset: -100
},
theme
});
const activeForegroundColor = reactTheming.getColor({
variable: 'foreground.subtle',
light: {
offset: 200
},
dark: {
offset: -200
},
theme
});
const activeBackgroundColor = reactTheming.getColor({
variable: 'background.primaryEmphasis',
transparency: theme.opacity[200],
dark: {
offset: -100
},
theme
});
const currentForegroundColor = activeForegroundColor;
const currentBackgroundColor = hoverBackgroundColor;
const currentHoverBackgroundColor = activeBackgroundColor;
const currentActiveBackgroundColor = reactTheming.getColor({
variable: 'background.primaryEmphasis',
transparency: theme.opacity[300],
dark: {
offset: -100
},
theme
});
return styled.css(["border:none;background:transparent;color:", ";&:hover{background-color:", ";color:", ";}", " &:active,&:focus-visible:active{background-color:", ";color:", ";}&[aria-current='page']{background-color:", ";color:", ";}&[aria-current='page']:hover{background-color:", ";}&[aria-current='page']:active{background-color:", ";}&:disabled,&[aria-disabled='true']{background-color:transparent;color:", ";}"], defaultColor, hoverBackgroundColor, hoverForegroundColor, reactTheming.focusStyles({
theme,
inset: true
}), activeBackgroundColor, activeForegroundColor, currentBackgroundColor, currentForegroundColor, currentHoverBackgroundColor, currentActiveBackgroundColor, disabledColor);
};
const sizeStyles$2 = props => {
const fontSize = props.theme.fontSizes.md;
const height = `${props.theme.space.base * 8}px`;
const lineHeight = reactTheming.getLineHeight(height, fontSize);
const padding = `${props.theme.space.base * 1.5}px`;
return styled.css(["padding:0 ", ";height:", ";line-height:", ";font-size:", ";"], padding, height, lineHeight, fontSize);
};
const StyledPageBase = styled__default.default.button.attrs({
'data-garden-id': COMPONENT_ID$6,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledPageBase",
componentId: "sc-ttwj4u-0"
})(["box-sizing:border-box;display:inline-block;transition:box-shadow 0.1s ease-in-out,background-color 0.25s ease-in-out,color 0.25s ease-in-out;visibility:", ";border-radius:", ";cursor:pointer;overflow:hidden;text-align:center;text-overflow:ellipsis;font-family:inherit;user-select:none;", ";&[aria-current='page']{font-weight:", ";}&::-moz-focus-inner{border:0;}&:disabled,[aria-disabled='true']{cursor:default;}", ";", ";"], props => props.hidden && 'hidden', props => props.theme.borderRadii.md, props => sizeStyles$2(props), props => props.theme.fontWeights.semibold, props => colorStyles$1(props), reactTheming.componentStyles);
const COMPONENT_ID$5 = 'pagination.page';
const sizeStyles$1 = props => {
const height = props.theme.space.base * 8;
return styled.css(["min-width:", "px;max-width:", "px;&[aria-current='true']{max-width:none;}"], height, height * 2);
};
const StyledPage = styled__default.default(StyledPageBase).attrs({
'data-garden-id': COMPONENT_ID$5,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledPage",
componentId: "sc-sxjfwy-0"
})(["", ";&[aria-current=\"true\"]{font-weight:", ";}", ";"], props => sizeStyles$1(props), props => props.theme.fontWeights.semibold, reactTheming.componentStyles);
const COMPONENT_ID$4 = 'cursor_pagination';
const StyledCursorPagination = styled__default.default.nav.attrs({
'data-garden-id': COMPONENT_ID$4,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledCursorPagination",
componentId: "sc-qmfecg-0"
})(["display:flex;justify-content:center;", ";"], reactTheming.componentStyles);
const COMPONENT_ID$3 = 'cursor_pagination.cursor';
const StyledCursor = styled__default.default(StyledPageBase).attrs({
'data-garden-id': COMPONENT_ID$3,
'data-garden-version': '9.7.2',
as: 'button'
}).withConfig({
displayName: "StyledCursor",
componentId: "sc-507ee-0"
})(["display:flex;align-items:center;border:none;background:transparent;padding:", ";overflow:visible;&:not(", "-of-type){margin-right:", "px;}", ";"], props => `0px ${props.theme.space.base * 2}px`, props => props.theme.rtl ? ':first' : ':last', props => props.theme.space.base, reactTheming.componentStyles);
const marginStyles = props => {
const {
$type,
theme
} = props;
const margin = theme.space.base;
if (theme.rtl) {
return styled.css(["margin-", ":", "px;"], $type === 'last' || $type === 'next' ? 'right' : 'left', margin);
}
return styled.css(["margin-", ":", "px;"], $type === 'first' || $type === 'previous' ? 'right' : 'left', margin);
};
const StyledIcon = styled__default.default(reactTheming.StyledBaseIcon).withConfig({
displayName: "StyledIcon",
componentId: "sc-2vzk6e-0"
})(["", " transform:", ";"], marginStyles, props => props.theme.rtl && 'rotate(180deg)');
const COMPONENT_ID$2 = 'pagination.gap';
const sizeStyles = props => {
const shift = 2;
const fontSize = polished.math(`${props.theme.fontSizes.md} + ${shift}`);
const height = `${props.theme.space.base * 8}px`;
const lineHeight = reactTheming.getLineHeight(height, fontSize);
const padding = `${props.theme.space.base * 1.5}px`;
return styled.css(["padding:0 ", ";min-width:", ";max-width:", ";height:", ";line-height:", ";font-size:", ";"], padding, height, polished.math(`${height} * 2`), height, lineHeight, fontSize);
};
const colorStyles = _ref => {
let {
theme
} = _ref;
return styled.css(["color:", ";"], reactTheming.getColor({
variable: 'foreground.subtle',
theme
}));
};
const StyledGapListItem = styled__default.default(StyledListItem).attrs({
'data-garden-id': COMPONENT_ID$2,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledGapListItem",
componentId: "sc-10wd0iz-0"
})(["display:inline-block;text-align:center;", ";", " &:hover{color:inherit;}", ";"], sizeStyles, colorStyles, reactTheming.componentStyles);
const COMPONENT_ID$1 = 'pagination.navigation';
const StyledNavigation = styled__default.default(StyledPage).attrs({
'data-garden-id': COMPONENT_ID$1,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledNavigation",
componentId: "sc-1lpl8pp-0"
})(["display:flex;align-items:center;justify-content:center;", ";"], reactTheming.componentStyles);
const COMPONENT_ID = 'pagination.pagination_view';
const StyledNav = styled__default.default.nav.attrs({
'data-garden-id': COMPONENT_ID,
'data-garden-version': '9.7.2'
}).withConfig({
displayName: "StyledNav",
componentId: "sc-ppnpkw-0"
})(["", ";"], reactTheming.componentStyles);
var _path$3;
function _extends$3() { return _extends$3 = 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$3.apply(null, arguments); }
var SvgChevronLeftStroke = function SvgChevronLeftStroke(props) {
return /*#__PURE__*/React__namespace.createElement("svg", _extends$3({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
focusable: "false",
viewBox: "0 0 16 16",
"aria-hidden": "true"
}, props), _path$3 || (_path$3 = /*#__PURE__*/React__namespace.createElement("path", {
fill: "currentColor",
d: "M10.39 12.688a.5.5 0 01-.718.69l-.062-.066-4-5a.5.5 0 01-.054-.542l.054-.082 4-5a.5.5 0 01.83.55l-.05.074L6.641 8l3.75 4.688z"
})));
};
var _path$2;
function _extends$2() { return _extends$2 = 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$2.apply(null, arguments); }
var SvgChevronRightStroke = function SvgChevronRightStroke(props) {
return /*#__PURE__*/React__namespace.createElement("svg", _extends$2({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
focusable: "false",
viewBox: "0 0 16 16",
"aria-hidden": "true"
}, props), _path$2 || (_path$2 = /*#__PURE__*/React__namespace.createElement("path", {
fill: "currentColor",
d: "M5.61 3.312a.5.5 0 01.718-.69l.062.066 4 5a.5.5 0 01.054.542l-.054.082-4 5a.5.5 0 01-.83-.55l.05-.074L9.359 8l-3.75-4.688z"
})));
};
const PreviousComponent$1 = React.forwardRef((props, ref) => {
const ariaLabel = reactTheming.useText(PreviousComponent$1, props, 'aria-label', 'Previous page');
const theme = React.useContext(styled.ThemeContext);
return React__namespace.default.createElement(StyledNavigation, Object.assign({
type: "button"
}, props, {
"aria-label": ariaLabel,
ref: ref
}), theme.rtl ? React__namespace.default.createElement(SvgChevronRightStroke, null) : React__namespace.default.createElement(SvgChevronLeftStroke, null));
});
PreviousComponent$1.displayName = 'Pagination.Previous';
const Previous$1 = PreviousComponent$1;
const NextComponent$1 = React.forwardRef((props, ref) => {
const ariaLabel = reactTheming.useText(NextComponent$1, props, 'aria-label', 'Next page');
const theme = React.useContext(styled.ThemeContext);
return React__namespace.default.createElement(StyledNavigation, Object.assign({
type: "button"
}, props, {
"aria-label": ariaLabel,
ref: ref
}), theme.rtl ? React__namespace.default.createElement(SvgChevronLeftStroke, null) : React__namespace.default.createElement(SvgChevronRightStroke, null));
});
NextComponent$1.displayName = 'Pagination.Next';
const Next$1 = NextComponent$1;
const PageComponent = React.forwardRef((props, ref) => {
const ariaLabel = reactTheming.useText(PageComponent, props, 'aria-label', `Page ${props.children}`);
return React__namespace.default.createElement(StyledPage, Object.assign({
type: "button"
}, props, {
"aria-label": ariaLabel,
ref: ref
}));
});
PageComponent.displayName = 'Pagination.Page';
const Page = PageComponent;
const GapComponent = React.forwardRef((props, ref) => {
const ariaLabel = reactTheming.useText(GapComponent, props, 'aria-label', 'Ellipsis indicating non-visible pages');
return React__namespace.default.createElement(StyledGapListItem, Object.assign({}, props, {
"aria-label": ariaLabel,
ref: ref
}), "\u2026");
});
GapComponent.displayName = 'Pagination.Gap';
const Gap = GapComponent;
const PREVIOUS_KEY = 'previous';
const NEXT_KEY = 'next';
const OffsetPagination = React.forwardRef((_ref, ref) => {
let {
currentPage: controlledCurrentPage,
totalPages,
pagePadding,
pageGap,
onChange,
'aria-label': ariaLabel,
labels,
...otherProps
} = _ref;
const [focusedItem, setFocusedItem] = React.useState();
const [internalCurrentPage, setInternalCurrentPage] = React.useState(1);
const navigationLabel = reactTheming.useText(OffsetPagination, {
'aria-label': ariaLabel
}, 'aria-label', 'Pagination');
const currentPage = containerUtilities.getControlledValue(controlledCurrentPage, internalCurrentPage);
const handleFocus = React.useCallback(item => {
setFocusedItem(item);
}, []);
const handleSelect = React.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__namespace.default.createElement(StyledListItem, null, React__namespace.default.createElement(Previous$1, {
hidden: isFirstPageSelected,
onFocus: () => handleFocus('previous'),
onClick: () => handleSelect('previous'),
"aria-label": labels?.previous
}));
};
const renderNextPage = () => {
const isLastPageSelected = currentPage === totalPages;
return React__namespace.default.createElement(StyledListItem, null, React__namespace.default.createElement(Next$1, {
hidden: isLastPageSelected,
onFocus: () => handleFocus('next'),
onClick: () => handleSelect('next'),
"aria-label": labels?.next
}));
};
const createGap = pageIndex => React__namespace.default.createElement(Gap, {
key: `gap-${pageIndex}`,
"aria-label": labels?.gap
});
const createPage = pageIndex => {
return React__namespace.default.createElement(StyledListItem, {
key: pageIndex
}, React__namespace.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__namespace.default.createElement(StyledNav, {
"aria-label": navigationLabel
}, React__namespace.default.createElement(StyledList, Object.assign({}, otherProps, {
ref: ref
}), renderPreviousPage(), totalPages > 0 && renderPages(), renderNextPage()));
});
OffsetPagination.propTypes = {
currentPage: PropTypes__default.default.number.isRequired,
totalPages: PropTypes__default.default.number.isRequired,
pagePadding: PropTypes__default.default.number,
pageGap: PropTypes__default.default.number,
onChange: PropTypes__default.default.func,
labels: PropTypes__default.default.any
};
OffsetPagination.defaultProps = {
pagePadding: 2,
pageGap: 2
};
OffsetPagination.displayName = 'OffsetPagination';
var _path$1;
function _extends$1() { return _extends$1 = 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$1.apply(null, arguments); }
var SvgChevronDoubleLeftStroke = function SvgChevronDoubleLeftStroke(props) {
return /*#__PURE__*/React__namespace.createElement("svg", _extends$1({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
focusable: "false",
viewBox: "0 0 16 16",
"aria-hidden": "true"
}, props), _path$1 || (_path$1 = /*#__PURE__*/React__namespace.createElement("path", {
fill: "currentColor",
d: "M7.812 13.39a.5.5 0 01-.64-.012l-.062-.066-4-5a.5.5 0 01-.054-.542l.054-.082 4-5a.5.5 0 01.83.55l-.05.074L4.141 8l3.75 4.688a.5.5 0 01-.079.702zm5 0a.5.5 0 01-.64-.012l-.062-.066-4-5a.5.5 0 01-.054-.542l.054-.082 4-5a.5.5 0 01.83.55l-.05.074L9.141 8l3.75 4.688a.5.5 0 01-.079.702z"
})));
};
const FirstComponent = React.forwardRef((_ref, ref) => {
let {
children,
...other
} = _ref;
return React__namespace.default.createElement(StyledCursor, Object.assign({
ref: ref
}, other), React__namespace.default.createElement(StyledIcon, {
$type: "first"
}, React__namespace.default.createElement(SvgChevronDoubleLeftStroke, null)), React__namespace.default.createElement("span", null, children));
});
FirstComponent.displayName = 'CursorPagination.First';
const First = FirstComponent;
const NextComponent = React.forwardRef((_ref, ref) => {
let {
children,
...other
} = _ref;
return React__namespace.default.createElement(StyledCursor, Object.assign({
ref: ref,
as: "button"
}, other), React__namespace.default.createElement("span", null, children), React__namespace.default.createElement(StyledIcon, {
$type: "next"
}, React__namespace.default.createElement(SvgChevronRightStroke, null)));
});
NextComponent.displayName = 'CursorPagination.Next';
const Next = NextComponent;
const PreviousComponent = React.forwardRef((_ref, ref) => {
let {
children,
...other
} = _ref;
return React__namespace.default.createElement(StyledCursor, Object.assign({
ref: ref,
as: "button"
}, other), React__namespace.default.createElement(StyledIcon, {
$type: "previous"
}, React__namespace.default.createElement(SvgChevronLeftStroke, null)), React__namespace.default.createElement("span", null, children));
});
PreviousComponent.displayName = 'CursorPagination.Previous';
const Previous = PreviousComponent;
var _path;
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); }
var SvgChevronDoubleRightStroke = function SvgChevronDoubleRightStroke(props) {
return /*#__PURE__*/React__namespace.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
focusable: "false",
viewBox: "0 0 16 16",
"aria-hidden": "true"
}, props), _path || (_path = /*#__PURE__*/React__namespace.createElement("path", {
fill: "currentColor",
d: "M8.188 2.61a.5.5 0 01.64.013l.062.065 4 5a.5.5 0 01.054.542l-.054.082-4 5a.5.5 0 01-.83-.55l.05-.074L11.859 8l-3.75-4.688a.5.5 0 01.079-.702zm-5 0a.5.5 0 01.64.013l.062.065 4 5a.5.5 0 01.054.542l-.054.082-4 5a.5.5 0 01-.83-.55l.05-.074L6.859 8l-3.75-4.688a.5.5 0 01.079-.702z"
})));
};
const LastComponent = React.forwardRef((_ref, ref) => {
let {
children,
...other
} = _ref;
return React__namespace.default.createElement(StyledCursor, Object.assign({
ref: ref,
as: "button"
}, other), React__namespace.default.createElement("span", null, children), React__namespace.default.createElement(StyledIcon, {
$type: "last"
}, React__namespace.default.createElement(SvgChevronDoubleRightStroke, null)));
});
LastComponent.displayName = 'CursorPagination.Last';
const Last = LastComponent;
const CursorPaginationComponent = React.forwardRef((props, ref) => React__namespace.default.createElement(StyledCursorPagination, Object.assign({
ref: ref
}, props)));
CursorPaginationComponent.displayName = 'CursorPagination';
const CursorPagination = CursorPaginationComponent;
CursorPagination.First = First;
CursorPagination.Next = Next;
CursorPagination.Previous = Previous;
CursorPagination.Last = Last;
exports.CursorPagination = CursorPagination;
exports.OffsetPagination = OffsetPagination;