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'))