UNPKG

@retailmenot/anchor

Version:

A React UI Library by RetailMeNot

135 lines (134 loc) 7.35 kB
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