@retailmenot/anchor
Version:
A React UI Library by RetailMeNot
135 lines (134 loc) • 7.35 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
// VENDOR
import * as React from 'react';
import classNames from 'classnames';
import styled from '@xstyled/styled-components';
import { space as spaceStyles } from '@xstyled/system';
// ANCHOR
import { Goto } from './Goto';
import { Button as AnchorButton, } from '../Button/Button.component';
import { ChevronLeft, ChevronRight, Ellipses } from '../Icon';
import { Typography } from '../Typography';
import { useUpdateEffect } from '../utils/useUpdateEffect/useUpdateEffect';
// Constrain a value between a min and max.
// Used primarily here to keep the current page
// value between the first page and last page
const constrain = (min, value, max) => Math.min(Math.max(value, min), max);
const Button = (_a) => {
var { variant = 'minimal', size = 'sm' } = _a, props = __rest(_a, ["variant", "size"]);
return React.createElement(AnchorButton, Object.assign({ variant: variant, size: size }, props));
};
const StyledPagination = styled('div') `
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
${spaceStyles}
`;
const AffixWrapper = styled('div') `
display: inline;
margin: 0 1rem;
`;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return Object.assign(Object.assign({}, state), { current: Math.min(state.current + 1, action.total) });
case 'decrement':
return Object.assign(Object.assign({}, state), { current: Math.max(state.current - 1, 1) });
case 'setCurrent':
return Object.assign(Object.assign({}, state), { current: constrain(1, action.payload, action.total) });
case 'totalChanged':
// This will make it so that if the current page is now beyond the
// new page total then the current page changes to the new last page.
// It might be that we want to revert to the first page instead.
return Object.assign(Object.assign({}, state), { current: constrain(1, state.current, action.total) });
default:
throw new Error(`Unrecognized reducer action type: ${action && action.type}`);
}
}
// We could improve this component by adding ways to show the total number of results,
// allowing "Next" and "Prev" text instead of arrows, and having the Ellipses buttons
// dropdown a Goto or decrement/increment the current page by 5, for instance.
export const Pagination = (_a) => {
var { className, prefix, suffix, children, totalPages, totalResults, pageSize = 10, current: controlledCurrent, showGoto, showArrows = true, size = 'sm', onChange, variant = 'expanded' } = _a, props = __rest(_a, ["className", "prefix", "suffix", "children", "totalPages", "totalResults", "pageSize", "current", "showGoto", "showArrows", "size", "onChange", "variant"]);
const [state, dispatch] = React.useReducer(reducer, {
current: controlledCurrent || 1,
});
// If the current page is controlled we want to
// prefer that over internal state
const current = controlledCurrent || state.current;
// Prefer the totalPages prop over calculating from
// the totalResults/pageSize
const total = totalPages || (totalResults && Math.ceil(totalResults / pageSize)) || 1;
useUpdateEffect(() => {
dispatch({ type: 'totalChanged', total });
}, [total]);
useUpdateEffect(() => {
if (onChange) {
onChange(state.current);
}
}, [state.current]);
const pageButton = ({ page, slot }) => (React.createElement(Button, { className: current === page ? 'active' : undefined, size: size, key: `page${page}:${slot}`, onClick: () => dispatch({ type: 'setCurrent', payload: page, total }), variant: current === page ? 'filled' : undefined, prefix: React.createElement(React.Fragment, null, page) }));
// Seven is enough to show the first and last page, the current page
// and pages on either side, and potential ellipses in between. If the
// page count is 7 or less, we can just display a button for each
// page.
const numButtonSlots = 7;
const iconSize = size === 'sm' ? 'lg' : undefined;
const renderProps = {
current,
totalPages: total,
totalResults,
pageSize,
range: [
(current - 1) * pageSize + 1,
Math.min(pageSize * current, totalResults || Infinity),
],
};
return (React.createElement(React.Fragment, null,
React.createElement(StyledPagination, Object.assign({ className: classNames('anchor-pagination', className) }, props),
prefix && React.createElement(AffixWrapper, null, prefix(renderProps)),
showArrows && (React.createElement(Button, { size: size, disabled: current <= 1, prefix: React.createElement(ChevronLeft, { scale: iconSize }), onClick: () => dispatch({ type: 'decrement', total }) })),
variant === 'minimal' && (React.createElement(Typography, { scale: 20, margin: "0 0.5rem" },
current,
" / ",
total)),
variant === 'expanded' &&
(total <= numButtonSlots ? (Array.from(Array(total)).map((_, i) => pageButton({ page: i + 1, slot: i }))) : (React.createElement(React.Fragment, null,
pageButton({ page: 1, slot: 1 }),
constrain(2, current - 2, total - 5) === 2 ? (pageButton({ page: 2, slot: 2 })) : (React.createElement(Button, { size: size, prefix: React.createElement(Ellipses, null) })),
pageButton({
page: constrain(3, current - 1, total - 4),
slot: 3,
}),
pageButton({
page: constrain(4, current, total - 3),
slot: 4,
}),
pageButton({
page: constrain(5, current + 1, total - 2),
slot: 5,
}),
constrain(6, current + 2, total - 1) ===
total - 1 ? (pageButton({ page: total - 1, slot: 6 })) : (React.createElement(Button, { size: size, prefix: React.createElement(Ellipses, null) })),
pageButton({ page: total, slot: 7 })))),
showArrows && (React.createElement(Button, { size: size, disabled: current === total, prefix: React.createElement(ChevronRight, { scale: iconSize }), onClick: () => dispatch({ type: 'increment', total }) })),
suffix && React.createElement(AffixWrapper, null, suffix(renderProps)),
showGoto && (React.createElement(Goto, { onSubmit: (page) => dispatch({
type: 'setCurrent',
payload: page,
total,
}) }))),
children && children(renderProps)));
};
//# sourceMappingURL=Pagination.component.js.map