terra-paginator
Version:
Paginator to be used for data sets of known and unknown size. Provides first, last, previous, next, and paged functionality.
211 lines (190 loc) • 7.13 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
import ResponsiveElement from 'terra-responsive-element';
import 'terra-base/lib/baseStyles';
import styles from './Paginator.module.scss';
import { calculatePages, KEYCODES } from './_paginationUtils';
const cx = classNames.bind(styles);
const propTypes = {
/**
* Function to be executed when a navigation element is selected.
*/
onPageChange: PropTypes.func.isRequired,
/**
* The active/selected page.
*/
selectedPage: PropTypes.number.isRequired,
/**
* Total number of all items being paginated.
*/
totalCount: PropTypes.number.isRequired,
/**
* Total number of items per page.
*/
itemCountPerPage: PropTypes.number.isRequired,
};
class ProgressivePaginator extends React.Component {
constructor(props) {
super(props);
this.handlePageChange = this.handlePageChange.bind(this);
this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
this.defaultProgressivePaginator = this.defaultProgressivePaginator.bind(this);
this.reducedProgressivePaginator = this.reducedProgressivePaginator.bind(this);
}
handlePageChange(index) {
return (event) => {
event.preventDefault();
this.props.onPageChange(index);
};
}
handleOnKeyDown(index) {
return (event) => {
if (event.nativeEvent.keyCode === KEYCODES.ENTER || event.nativeEvent.keyCode === KEYCODES.SPACE) {
event.preventDefault();
if (Number.isNaN(Number(index))) {
this.props.onPageChange(event.target.text.trim().toLowerCase());
return false;
}
this.props.onPageChange(index);
}
return false;
};
}
// TODO: Resolve lint issues - https://github.com/cerner/terra-core/issues/1689
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/anchor-is-valid, jsx-a11y/no-noninteractive-tabindex */
defaultProgressivePaginator() {
const totalPages = calculatePages(this.props.totalCount, this.props.itemCountPerPage);
const { selectedPage } = this.props;
const previousPageIndex = selectedPage === 1 ? 1 : selectedPage - 1;
const nextPageIndex = selectedPage === totalPages ? totalPages : selectedPage + 1;
return (
<div className={cx(['paginator', 'progressive'])} role="navigation" aria-label="pagination">
<div>
Page
{' '}
{selectedPage}
{' '}
of
{' '}
{totalPages}
</div>
<div>
<a
aria-disabled={selectedPage === 1}
aria-label="first"
className={cx(['nav-link', selectedPage === 1 ? 'is-disabled' : null])}
tabIndex={selectedPage === 1 ? null : '0'}
onClick={this.handlePageChange(1)}
onKeyDown={this.handleOnKeyDown(1)}
>
First
</a>
<a
aria-disabled={selectedPage === 1}
aria-label="previous"
className={cx(['nav-link', 'previous', selectedPage === 1 ? 'is-disabled' : null])}
tabIndex={selectedPage === 1 ? null : '0'}
onClick={this.handlePageChange(previousPageIndex)}
onKeyDown={this.handleOnKeyDown(previousPageIndex)}
>
<span className={cx('icon')} />
Previous
</a>
<a
aria-disabled={selectedPage === totalPages}
aria-label="next"
className={cx(['nav-link', 'next', selectedPage === totalPages ? 'is-disabled' : null])}
tabIndex={selectedPage === totalPages ? null : '0'}
onClick={this.handlePageChange(nextPageIndex)}
onKeyDown={this.handleOnKeyDown(nextPageIndex)}
>
Next
<span className={cx('icon')} />
</a>
<a
aria-disabled={selectedPage === totalPages}
aria-label="last"
className={cx(['nav-link', selectedPage === totalPages ? 'is-disabled' : null])}
tabIndex={selectedPage === totalPages ? null : '0'}
onClick={this.handlePageChange(totalPages)}
onKeyDown={this.handleOnKeyDown(totalPages)}
>
Last
</a>
</div>
</div>
);
}
reducedProgressivePaginator() {
const totalPages = calculatePages(this.props.totalCount, this.props.itemCountPerPage);
const { selectedPage } = this.props;
const previousPageIndex = selectedPage === 1 ? 1 : selectedPage - 1;
const nextPageIndex = selectedPage === totalPages ? totalPages : selectedPage + 1;
return (
<div className={cx(['paginator'])} role="navigation" aria-label="pagination">
<div>
<a
aria-disabled={selectedPage === 1}
aria-label="first"
className={cx(['nav-link', selectedPage === 1 ? 'is-disabled' : null])}
tabIndex={selectedPage === 1 ? null : '0'}
onClick={this.handlePageChange(1)}
onKeyDown={this.handleOnKeyDown(1)}
>
First
</a>
<a
aria-disabled={selectedPage === 1}
aria-label="previous"
className={cx(['nav-link', 'previous', 'icon-only', selectedPage === 1 ? 'is-disabled' : null])}
tabIndex={selectedPage === 1 ? null : '0'}
onClick={this.handlePageChange(previousPageIndex)}
onKeyDown={this.handleOnKeyDown(previousPageIndex)}
>
<span className={cx('visually-hidden')}>Previous</span>
<span className={cx('icon')} />
</a>
</div>
<div>
Page
{' '}
{selectedPage}
{' '}
of
{' '}
{totalPages}
</div>
<div>
<a
aria-disabled={selectedPage === totalPages}
aria-label="next"
className={cx(['nav-link', 'next', 'icon-only', selectedPage === totalPages ? 'is-disabled' : null])}
tabIndex={selectedPage === totalPages ? null : '0'}
onClick={this.handlePageChange(nextPageIndex)}
onKeyDown={this.handleOnKeyDown(nextPageIndex)}
>
<span className={cx('visually-hidden')}>Next</span>
<span className={cx('icon')} />
</a>
<a
aria-disabled={selectedPage === totalPages}
aria-label="last"
className={cx(['nav-link', selectedPage === totalPages ? 'is-disabled' : null])}
tabIndex={selectedPage === totalPages ? null : '0'}
onClick={this.handlePageChange(totalPages)}
onKeyDown={this.handleOnKeyDown(totalPages)}
>
Last
</a>
</div>
</div>
);
}
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/anchor-is-valid, jsx-a11y/no-noninteractive-tabindex */
render() {
return <ResponsiveElement defaultElement={this.reducedProgressivePaginator()} tiny={this.defaultProgressivePaginator()} />;
}
}
ProgressivePaginator.propTypes = propTypes;
export default ProgressivePaginator;