Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/stcom-827-pagination-component' …
Browse files Browse the repository at this point in the history
…into hierarchy-tree-poc
  • Loading branch information
BogdanDenis committed Jun 1, 2021
2 parents 334303f + 69d2868 commit 457e824
Show file tree
Hide file tree
Showing 11 changed files with 391 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export {
export { default as FilterControlGroup } from './lib/FilterControlGroup';
export { default as FilterPaneSearch } from './lib/FilterPaneSearch';
export { default as ExportCsv } from './lib/ExportCsv';
export { default as Pagination } from './lib/Pagination';

/* utilities */
export { default as RootCloseWrapper } from './util/RootCloseWrapper';
Expand Down
76 changes: 76 additions & 0 deletions lib/Pagination/Pagination.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
@import '../variables.css';

/**
* Default styling
*/

.pagination {
display: flex;
list-style: none;
padding: 4px 0;
margin: 0;

&.fillWidth {
width: 100%;
justify-content: space-between;
}

& .paginationItem {
padding: 0 2px;
margin: 0 1px;
}
}

.paginationLink {
composes: button from "../Button/Button.css";
padding: 0 var(--gutter-static-two-thirds, 8px);
margin: 0;

&[aria-disabled=true] {
color: var(--color-text-p2);
transition: opacity ease-in-out 500ms;
pointer-events: none;
}

&.numberLink {
min-width: 1.72rem;
padding: 0 4px;
margin: 0;
}

&:visited {
color: inherit;
}

&::before {
border-radius: 999px;
}

/**
* Button Style: Default
*/

&.default {
background-color: transparent;
border: 1px solid var(--primary);
color: var(--primary);

& :global .stripes__icon {
fill: var(--primary);
}
}

/**
* Button style primary
*/

&.primary {
background-color: var(--primary);
border: 1px solid var(--primary);
color: #fff;

&:hover {
opacity: 0.9;
}
}
}
91 changes: 91 additions & 0 deletions lib/Pagination/Pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import { FormattedMessage } from 'react-intl';
import Icon from '../Icon';
import css from './Pagination.css';

// < # ... # # # ... # >
// |_| |___| |_____| |___| |_|
// OUTER BREAK CENTER BREAK OUTER

const MINIMAL_DISPLAY_THRESHOLD = 6;
const MINIMAL_DISPLAY_COUNT = 3;
const EXTENDED_DISPLAY_COUNT = 6;
const MAX_EXTENDED_OUTER_PAGES = 1;

const propTypes = {
/** Class to be applied to `<nav>` containter */
className: PropTypes.string,
/** Allows you to control the pagination and define the current page. */
currentPage: PropTypes.number,
/** fills width of container, placing previous/next buttons at either end */
fillWidth: PropTypes.bool,
/** The method called to generate the href attribute value for each page. */
hrefBuilder: PropTypes.func.isRequired,
id: PropTypes.string,
/** Set accessible label for `nav` container */
label: PropTypes.string,
/** The method to call when a page is clicked. Exposes the current page object as an argument. */
onPageChange: PropTypes.func,
/** Total number of pages. */
pageCount: PropTypes.number,
/** Whether or not to show the previous and next labels */
showLabels: PropTypes.bool,
};

const Pagination = ({
id,
pageCount,
onPageChange,
hrefBuilder,
fillWidth,
currentPage,
label = 'pagination',
showLabels = true,
...props
}) => {
return (
<nav id={id} data-testid="pagination-component" aria-label={label} data-test-pagination>
<ReactPaginate
pageCount={pageCount}
pageRangeDisplayed={pageCount < MINIMAL_DISPLAY_THRESHOLD ? EXTENDED_DISPLAY_COUNT : MINIMAL_DISPLAY_COUNT}
marginPagesDisplayed={pageCount < MINIMAL_DISPLAY_THRESHOLD ? 0 : MAX_EXTENDED_OUTER_PAGES}
nextLabel={(
<div data-test-pagination-next>
<Icon size="small" icon="caret-right" iconPosition="end">
<FormattedMessage id="stripes-components.next">
{ (text) => <span className={`${showLabels ? '' : 'sr-only'}`}>{text}</span>}
</FormattedMessage>
</Icon>
</div>
)}
previousLabel={(
<div data-test-pagination-previous>
<Icon size="small" icon="caret-left">
<FormattedMessage id="stripes-components.previous">
{ (text) => <span className={`${showLabels ? '' : 'sr-only'}`}>{text}</span>}
</FormattedMessage>
</Icon>
</div>
)}
breakLabel={<Icon icon="ellipsis" aria-label="ellipsis" />}
onPageChange={onPageChange}
pageClassName={css.paginationItem}
containerClassName={`${css.pagination} ${fillWidth ? css.fillWidth : ''}`}
pageLinkClassName={`${css.paginationLink} ${css.numberLink}`}
activeLinkClassName={css.primary}
nextLinkClassName={css.paginationLink}
previousLinkClassName={css.paginationLink}
breakLinkClassName={css.paginationLink}
forcePage={currentPage}
hrefBuilder={hrefBuilder}
{...props}
/>
</nav>
);
};

Pagination.propTypes = propTypes;

export default Pagination;
1 change: 1 addition & 0 deletions lib/Pagination/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Pagination';
53 changes: 53 additions & 0 deletions lib/Pagination/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

# Pagination
The Pagination component is used for nagivation between pages of a list of results.

We use [react-paginate](https://github.com/AdeleD/react-paginate) under the hood, more detail can be found at their we expose their API along with a few useful props.

## Basic Usage

```
// take actions within your app corresponding to the selected page...
const handlePageClick = (page) => {
// page is an object with {selected} property - which gives the currently selected page index.
// console.log(page);
}
// generate the hrefs for links if necessary (return the full string)
const resultsHrefBuilder = (page) => {
// return '#';
}
<Pagination
pageCount={20}
onPageChange={handlePageClick}
hrefBuilder={resultsHrefBuilder}
/>
```

## Display modes based on page count...
For less than 6 pages, 3 page links will be visible between previous and next buttons (carets.)
```
< 1 2 3 >
```

For more than 6 pages, extended mode is activated - it includes collected central links between ellipsis and outer "margin" links.
```
< 1 ... 4 5 6 ... 10 >
```

## Props

Name | type | description | default | required
--- | --- | --- | --- | ---
`id` | string | Applies the 'id' attribute to the outer `<nav>` element | --- | ---
`pageCount`| number | Used to set starting/ending page numbers (1 - pageCount) | --- | required
`onPageChange` | func | --- | function called when page button is clicked | ---
`hrefBuilder` | func | function for generating hrefs for individual buttons. Provides the page object | ---
`fillWidth` | bool | if `true`, pagination will fill its parent container, placing previous and next buttons at either end, with page buttons distributed evenly between | false | ---
`currentPage` | number | manually sets the current page | --- | ---
`label` | string | label for outer `<nav>` element | --- | 'pagination' | ---
`showLabels` | bool | whether or not to display the visible "previous" and "next" labels. | true | ---


39 changes: 39 additions & 0 deletions lib/Pagination/stories/BasicUsage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Pagination Component: Basic Usage
*/

import React from 'react';
import { action } from '@storybook/addon-actions';
import Pagination from '..';

// eslint-disable-next-line
const handlePageClick = (page) => {
action(page);
};

export default () => (
<div>
<h3>20 pages</h3>
<Pagination
pageCount={20}
onPageChange={handlePageClick}
hrefBuilder={() => '#'}
/>
<h3>3 or fewer pages, hidden previous and next labels</h3>
<Pagination
pageCount={3}
onPageChange={handlePageClick}
hrefBuilder={() => '#'}
showLabels={false}
/>
<h3>Fill width of container</h3>
<div style={{ width: '600px', border: '1px solid #ccc' }}>
<Pagination
pageCount={3}
onPageChange={handlePageClick}
hrefBuilder={() => '#'}
fillWidth
/>
</div>
</div>
);
9 changes: 9 additions & 0 deletions lib/Pagination/stories/Pagination.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import withReadme from 'storybook-readme/with-readme';
import readme from '../readme.md';
import BasicUsage from './BasicUsage';

storiesOf('Pagination', module)
.addDecorator(withReadme(readme))
.add('Basic Usage', () => <BasicUsage />);
82 changes: 82 additions & 0 deletions lib/Pagination/tests/Pagination-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import {
beforeEach,
describe,
it,
} from '@bigtest/mocha';

import Pagination from '../Pagination';

import { mountWithContext } from '../../../tests/helpers';
import PaginationInteractor from './interactor';

const clickHandler = sinon.spy();

describe('Pagination', () => {
const pagination = new PaginationInteractor();

describe('rendering', () => {
beforeEach(async () => {
await mountWithContext(
<Pagination
pageCount={20}
onPageChange={clickHandler}
hrefBuilder={() => '#'}
/>
);
});

it('should render pagination links', () => {
expect(pagination.isPresent).to.be.true;
});

it('last number link should display 20', () => {
expect(pagination.lastNumber.number).to.equal('20');
});

it('previous/next buttons display labels', () => {
expect(pagination.nextlink.labelHidden).to.be.false;
expect(pagination.nextlink.labelHidden).to.be.false;
});

describe('clicking the next button', () => {
beforeEach(async () => {
clickHandler.resetHistory;
await pagination.nextlink.click();
});

it('calls the pageChange handler', () => {
expect(clickHandler.calledOnce).to.be.true;
});
});
describe('clicking the previous button', () => {
beforeEach(async () => {
clickHandler.resetHistory;
await pagination.previouslink.click();
});

it('calls the pageChange handler', () => {
expect(clickHandler.calledOnce).to.be.true;
});
});
});

describe('hidden previous/next labels', () => {
beforeEach(async () => {
await mountWithContext(
<Pagination
pageCount={3}
hrefBuilder={() => '#'}
showLabels={false}
/>
);
});

it('previous/next buttons hide labels', () => {
expect(pagination.nextlink.labelHidden).to.be.true;
expect(pagination.nextlink.labelHidden).to.be.true;
});
});
});
Loading

0 comments on commit 457e824

Please sign in to comment.