diff --git a/src/components/PaginationRow/PaginationRow.js b/src/components/PaginationRow/PaginationRow.js
new file mode 100644
index 00000000000..a7a9327f720
--- /dev/null
+++ b/src/components/PaginationRow/PaginationRow.js
@@ -0,0 +1,223 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+
+import { MenuItem, DropdownButton, Icon } from '../../index';
+
+const ArrowIcon = props => {
+ const name = `angle-${props.name}`;
+ return ;
+};
+
+ArrowIcon.propTypes = {
+ name: PropTypes.oneOf(['left', 'double-left', 'right', 'double-right'])
+};
+
+class PaginationRow extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.initPagination(props);
+ this.state = {
+ pageChangeValue: Number(props.currentPage)
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.currentPage !== nextProps.currentPage) {
+ this.setState({ pageChangeValue: Number(nextProps.currentPage) });
+ }
+
+ this.initPagination(nextProps);
+ }
+
+ initPagination(props) {
+ this.perPage = Number(props.perPage);
+ this.totalCount = Number(props.totalCount);
+ this.currentPage = Number(props.currentPage);
+ }
+
+ msg(key) {
+ return this.props.messages[key] || PaginationRow.defaultMessages[key];
+ }
+
+ totalPages() {
+ return Math.ceil(this.props.totalCount / this.perPage);
+ }
+
+ setPageRelative(diff) {
+ this.setPage(Number(this.props.currentPage) + diff);
+ }
+
+ setPage(page) {
+ if (page !== '') {
+ this.props.onPageSet(Number(page));
+ } else {
+ console.error("Page can't be blank");
+ }
+ }
+
+ handlePageChange(e) {
+ this.setState({ pageChangeValue: e.target.value });
+ }
+
+ handleFormSubmit(e) {
+ this.setPage(this.state.pageChangeValue);
+ e.preventDefault();
+ }
+
+ renderPerPageDropdown() {
+ const { perPageOptions, onPerPageSet } = this.props;
+
+ return (
+
+ {perPageOptions.map(opt => {
+ return (
+
+ );
+ })}
+
+ );
+ }
+
+ render() {
+ const perPageDropdown = this.renderPerPageDropdown();
+
+ const displayedRangeStart = (this.currentPage - 1) * this.perPage + 1;
+ const displayedRangeEnd = Math.min(
+ displayedRangeStart + this.perPage - 1,
+ this.totalCount
+ );
+ const displayedRange = `${displayedRangeStart}-${displayedRangeEnd}`;
+
+ const backButtonsClass = this.currentPage === 1 ? 'disabled' : '';
+ const nextButtonsClass =
+ this.currentPage * this.perPage >= this.totalCount ? 'disabled' : '';
+
+ const totalPages = this.totalPages();
+
+ const classes = ClassNames(this.props.className, 'clearfix');
+
+ return (
+
+ );
+ }
+}
+
+PaginationRow.propTypes = {
+ /** Options for the per page dropdown */
+ perPageOptions: PropTypes.array,
+ /** Current per page setting */
+ perPage: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
+ /** Total number of items to paginate */
+ totalCount: PropTypes.number.isRequired,
+ /** Index of page that is currently shown, starting from 1 */
+ currentPage: PropTypes.number.isRequired,
+ /** A callback triggered when the per page dropdown value is selected */
+ onPerPageSet: PropTypes.func,
+ /** A callback triggered when a page is switched */
+ onPageSet: PropTypes.func,
+ /** Strings in the component, see PaginationRow.defaultMessages for details */
+ messages: PropTypes.object,
+ /** Class name for the form element */
+ className: PropTypes.string
+};
+
+PaginationRow.defaultProps = {
+ perPageOptions: [],
+ onPageSet: p => {},
+ onPerPageSet: pp => {},
+ messages: {},
+ className: 'content-view-pf-pagination'
+};
+
+PaginationRow.defaultMessages = {
+ firstPage: 'First Page',
+ previousPage: 'Previous Page',
+ nextPage: 'Next Page',
+ lastPage: 'Last Page',
+ perPage: 'per page',
+ of: 'of'
+};
+
+export default PaginationRow;
diff --git a/src/components/PaginationRow/PaginationRow.stories.js b/src/components/PaginationRow/PaginationRow.stories.js
new file mode 100644
index 00000000000..a445bf0e852
--- /dev/null
+++ b/src/components/PaginationRow/PaginationRow.stories.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import { withKnobs, select, text } from '@storybook/addon-knobs';
+import { defaultTemplate } from '../../../storybook/decorators/storyTemplates';
+import { PaginationRow } from './index';
+
+const stories = storiesOf('PaginationRow', module);
+
+stories.addDecorator(
+ defaultTemplate({
+ title: 'Pagination Row',
+ documentationLink:
+ 'http://www.patternfly.org/pattern-library/navigation/pagination/'
+ })
+);
+stories.addDecorator(withKnobs);
+stories.addWithInfo('Basic example', '', () => {
+ const page = select('Page', [1, 3, 8], 1);
+ const totalCount = select('Total items', [75, 80, 81], 75);
+
+ return (
+
+ );
+});
+
+stories.addWithInfo('With translations', '', () => {
+ var messages = {};
+ for (var key in PaginationRow.defaultMessages) {
+ messages[key] = text(key, PaginationRow.defaultMessages[key]);
+ }
+
+ return (
+
+ );
+});
diff --git a/src/components/PaginationRow/PaginationRow.test.js b/src/components/PaginationRow/PaginationRow.test.js
new file mode 100644
index 00000000000..799a00e1304
--- /dev/null
+++ b/src/components/PaginationRow/PaginationRow.test.js
@@ -0,0 +1,48 @@
+/* eslint-env jest */
+
+import React from 'react';
+import renderer from 'react-test-renderer';
+
+import PaginationRow from './PaginationRow';
+
+test('PaginationRow renders properly the first page', () => {
+ const component = renderer.create(
+
+ );
+
+ let tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
+
+test('PaginationRow renders properly a middle page', () => {
+ const component = renderer.create(
+
+ );
+
+ let tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
+
+test('PaginationRow renders properly the last page', () => {
+ const component = renderer.create(
+
+ );
+
+ let tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/PaginationRow/__snapshots__/PaginationRow.test.js.snap b/src/components/PaginationRow/__snapshots__/PaginationRow.test.js.snap
new file mode 100644
index 00000000000..49b491948f9
--- /dev/null
+++ b/src/components/PaginationRow/__snapshots__/PaginationRow.test.js.snap
@@ -0,0 +1,574 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PaginationRow renders properly a middle page 1`] = `
+
+`;
+
+exports[`PaginationRow renders properly the first page 1`] = `
+
+`;
+
+exports[`PaginationRow renders properly the last page 1`] = `
+
+`;
diff --git a/src/components/PaginationRow/index.js b/src/components/PaginationRow/index.js
new file mode 100644
index 00000000000..4ff16c8013a
--- /dev/null
+++ b/src/components/PaginationRow/index.js
@@ -0,0 +1 @@
+export { default as PaginationRow } from './PaginationRow';