diff --git a/components/ButtonGroup/styles.js b/components/ButtonGroup/styles.js index 002ee8d9cd5..f450c644833 100644 --- a/components/ButtonGroup/styles.js +++ b/components/ButtonGroup/styles.js @@ -1,5 +1,7 @@ export default { root: { + display: 'inline-block', + '& Button': { margin: '0', '&:first-child': { diff --git a/components/Pagination/Pagination.example.jsx b/components/Pagination/Pagination.example.jsx new file mode 100644 index 00000000000..049005e6989 --- /dev/null +++ b/components/Pagination/Pagination.example.jsx @@ -0,0 +1,67 @@ +/* global alert */ + +import React from 'react' +import { storiesOf } from '@storybook/react' + +import StoryTeller from '../../.storybook/StoryTeller' +import Pagination from './' +import Spacer from '../Spacer' + +const stories = storiesOf('Pagination', module) + +const teller = new StoryTeller( + 'Pagination', + 'Component which allows navigating long data lists' +) +const chapter = teller.addChapter() + +chapter + .addSection('Pagination', 'variations', () => ( +
+ { + alert('Page change...' + page) + }} + totalPages={10} + /> + + { + alert('Page change...' + page) + }} + totalPages={10} + /> + + { + alert('Page change...' + page) + }} + totalPages={10} + /> + + { + alert('Page change...' + page) + }} + totalPages={10} + /> +
+ )) + .addSection('Pagination', 'disabled', () => ( +
+ { + alert('Page change...' + page) + }} + totalPages={10} + /> +
+ )) + +stories.addWithChapters('Pagination', teller.toStory()) diff --git a/components/Pagination/Pagination.jsx b/components/Pagination/Pagination.jsx new file mode 100644 index 00000000000..64c15a5bd7d --- /dev/null +++ b/components/Pagination/Pagination.jsx @@ -0,0 +1,125 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import Button from '../Button' +import { getRange, ELLIPSIS, FIRST_PAGE, ONE_PAGE } from './range-utils' + +const NAVIGATION = { + FIRST: 'FIRST', + LAST: 'LAST', + PREVIOUS: 'PREVIOUS', + NEXT: 'NEXT' +} +const SIBLING_COUNT = 3 + +class Pagination extends React.PureComponent { + static propTypes = { + activePage: PropTypes.number.isRequired, + disabled: PropTypes.bool, + onPageChange: PropTypes.func.isRequired, + totalPages: PropTypes.number.isRequired + } + + static defaultProps = { + disabled: false + } + + handleChange (navigation) { + const { onPageChange, totalPages, activePage } = this.props + let page + + switch (navigation) { + case NAVIGATION.FIRST: + page = FIRST_PAGE + break + case NAVIGATION.PREVIOUS: + page = activePage - ONE_PAGE + break + case NAVIGATION.NEXT: + page = activePage + ONE_PAGE + break + case NAVIGATION.LAST: + page = totalPages + break + default: + page = navigation + } + + onPageChange(page) + } + + isFirstActive = activePage => activePage === 1 + isLastActive = (activePage, totalPages) => activePage === totalPages + + renderRange () { + const { totalPages, activePage, disabled } = this.props + + const range = getRange({ + activePage, + totalPages, + siblingCount: SIBLING_COUNT + }) + + return range.map((pageItemLabel, index) => { + return ( + + ) + }) + } + + render () { + const { activePage, totalPages, disabled } = this.props + const isFirstActive = this.isFirstActive(activePage) + const isLastActive = this.isLastActive(activePage, totalPages) + + return ( + + + + + {this.renderRange()} + + + + + ) + } +} + +export default Pagination diff --git a/components/Pagination/__snapshots__/test.jsx.snap b/components/Pagination/__snapshots__/test.jsx.snap new file mode 100644 index 00000000000..149808506d0 --- /dev/null +++ b/components/Pagination/__snapshots__/test.jsx.snap @@ -0,0 +1,187 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders default 1`] = ` +
+
+ + + + + +
+
+`; + +exports[`renders disabled 1`] = ` +
+
+ + + + + +
+
+`; diff --git a/components/Pagination/index.js b/components/Pagination/index.js new file mode 100644 index 00000000000..40ac52f2ef4 --- /dev/null +++ b/components/Pagination/index.js @@ -0,0 +1 @@ +export { default } from './Pagination' diff --git a/components/Pagination/range-utils/index.js b/components/Pagination/range-utils/index.js new file mode 100644 index 00000000000..0f3af45ab74 --- /dev/null +++ b/components/Pagination/range-utils/index.js @@ -0,0 +1 @@ +export { getRange, ELLIPSIS, FIRST_PAGE, ONE_PAGE } from './range-utils' diff --git a/components/Pagination/range-utils/range-utils.js b/components/Pagination/range-utils/range-utils.js new file mode 100644 index 00000000000..eab3454f00f --- /dev/null +++ b/components/Pagination/range-utils/range-utils.js @@ -0,0 +1,54 @@ +export const FIRST_PAGE = 1 +export const ONE_PAGE = 1 +export const ELLIPSIS = '...' + +const addEllipsis = (siblings, page) => { + const lastSibling = siblings[siblings.length - 1] + + if (lastSibling && lastSibling !== page) { + siblings.push(ELLIPSIS) + } +} + +const getPreviousSiblings = (activePage, siblingCount) => { + const previousSiblings = [] + const estimatedLastSibling = activePage - siblingCount + let pageNumber = activePage - ONE_PAGE + + for ( + ; + pageNumber >= estimatedLastSibling && pageNumber >= FIRST_PAGE; + pageNumber-- + ) { + previousSiblings.push(pageNumber) + } + + addEllipsis(previousSiblings, FIRST_PAGE) + + return previousSiblings.reverse() +} + +const getNextSiblings = (activePage, siblingCount, totalPages) => { + const nextSiblings = [] + const estimatedLastSibling = activePage + siblingCount + let pageNumber = activePage + ONE_PAGE + + for ( + ; + pageNumber <= estimatedLastSibling && pageNumber <= totalPages; + pageNumber++ + ) { + nextSiblings.push(pageNumber) + } + + addEllipsis(nextSiblings, totalPages) + + return nextSiblings +} + +export const getRange = ({ activePage, totalPages, siblingCount }) => { + const previousSiblings = getPreviousSiblings(activePage, siblingCount) + const nextSiblings = getNextSiblings(activePage, siblingCount, totalPages) + + return [...previousSiblings, activePage, ...nextSiblings] +} diff --git a/components/Pagination/range-utils/test.js b/components/Pagination/range-utils/test.js new file mode 100644 index 00000000000..f6f719af444 --- /dev/null +++ b/components/Pagination/range-utils/test.js @@ -0,0 +1,53 @@ +import { getRange, ELLIPSIS } from './range-utils' + +describe('getRange({ activePage, totalPages, siblingCount })', () => { + test('returns proper range 1', () => { + const range = getRange({ + activePage: 5, + totalPages: 10, + siblingCount: 2 + }) + + expect(range).toEqual([ELLIPSIS, 3, 4, 5, 6, 7, ELLIPSIS]) + }) + + test('returns proper range 2', () => { + const range = getRange({ + activePage: 3, + totalPages: 5, + siblingCount: 3 + }) + + expect(range).toEqual([1, 2, 3, 4, 5]) + }) + + test('returns proper range 3', () => { + const range = getRange({ + activePage: 1, + totalPages: 1, + siblingCount: 3 + }) + + expect(range).toEqual([1]) + }) + + test('returns proper range 4', () => { + const range = getRange({ + activePage: 3, + totalPages: 3, + siblingCount: 3 + }) + + expect(range).toEqual([1, 2, 3]) + }) + + test('returns proper range 5', () => { + const range = getRange({ + activePage: 10, + totalPages: 10, + siblingCount: 3 + }) + + expect(range).toEqual([ELLIPSIS, 7, 8, 9, 10]) + }) +}) diff --git a/components/Pagination/test.jsx b/components/Pagination/test.jsx new file mode 100644 index 00000000000..5f5589cce74 --- /dev/null +++ b/components/Pagination/test.jsx @@ -0,0 +1,30 @@ +import React from 'react' +/* eslint-disable no-unused-vars */ +import { render, fireEvent, cleanup } from 'react-testing-library' + +import Pagination from './index' + +const renderPagination = (children, props = {}) => { + return render({children}) +} + +afterEach(cleanup) + +test('renders default', () => { + const { container } = renderPagination({ + activePage: 5, + totalPages: 20 + }) + + expect(container).toMatchSnapshot() +}) + +test('renders disabled', () => { + const { container } = renderPagination({ + activePage: 5, + totalPages: 20, + disabled: true + }) + + expect(container).toMatchSnapshot() +}) diff --git a/components/Pagination/visual.test.jsx b/components/Pagination/visual.test.jsx new file mode 100644 index 00000000000..016c4835498 --- /dev/null +++ b/components/Pagination/visual.test.jsx @@ -0,0 +1,3 @@ +import { assertVisuals } from '../../puppeteer' + +test('Pagination', assertVisuals('Pagination', 'Pagination'))