mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): add a pagination component with keyboard shortcuts
This commit is contained in:
89
ui/src/Components/Pagination/index.js
Normal file
89
ui/src/Components/Pagination/index.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { HotKeys } from "react-hotkeys";
|
||||
|
||||
import Pagination from "react-js-pagination";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft";
|
||||
import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight";
|
||||
import { faAngleDoubleLeft } from "@fortawesome/free-solid-svg-icons/faAngleDoubleLeft";
|
||||
import { faAngleDoubleRight } from "@fortawesome/free-solid-svg-icons/faAngleDoubleRight";
|
||||
|
||||
class PageSelect extends Component {
|
||||
static propTypes = {
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
activePage: PropTypes.number.isRequired,
|
||||
maxPerPage: PropTypes.number.isRequired,
|
||||
totalItemsCount: PropTypes.number.isRequired,
|
||||
setPageCallback: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.HotKeysRef = React.createRef();
|
||||
}
|
||||
|
||||
onPageUp = () => {
|
||||
const { setPageCallback, activePage, totalPages } = this.props;
|
||||
setPageCallback(Math.min(activePage + 1, totalPages));
|
||||
};
|
||||
|
||||
onPageDown = () => {
|
||||
const { setPageCallback, activePage } = this.props;
|
||||
setPageCallback(Math.max(activePage - 1, 1));
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.HotKeysRef.current.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
totalItemsCount,
|
||||
maxPerPage,
|
||||
activePage,
|
||||
setPageCallback
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys
|
||||
className="components-pagination"
|
||||
innerRef={this.HotKeysRef}
|
||||
keyMap={{
|
||||
onArrowLeft: "ArrowLeft",
|
||||
onArrowRight: "ArrowRight"
|
||||
}}
|
||||
handlers={{
|
||||
onArrowLeft: this.onPageDown,
|
||||
onArrowRight: this.onPageUp
|
||||
}}
|
||||
>
|
||||
{totalItemsCount > maxPerPage ? (
|
||||
<div className="mt-3">
|
||||
<Pagination
|
||||
activePage={activePage}
|
||||
itemsCountPerPage={maxPerPage}
|
||||
totalItemsCount={totalItemsCount}
|
||||
pageRangeDisplayed={5}
|
||||
onChange={setPageCallback}
|
||||
hideFirstLastPages={totalItemsCount / maxPerPage < 20}
|
||||
innerClass="pagination justify-content-center"
|
||||
itemClass="page-item"
|
||||
linkClass="page-link"
|
||||
activeClass="active"
|
||||
activeLinkClass="font-weight-bold"
|
||||
prevPageText={<FontAwesomeIcon icon={faAngleLeft} />}
|
||||
nextPageText={<FontAwesomeIcon icon={faAngleRight} />}
|
||||
firstPageText={<FontAwesomeIcon icon={faAngleDoubleLeft} />}
|
||||
lastPageText={<FontAwesomeIcon icon={faAngleDoubleRight} />}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { PageSelect };
|
||||
51
ui/src/Components/Pagination/index.test.js
Normal file
51
ui/src/Components/Pagination/index.test.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { PressKey } from "__mocks__/KeyPress";
|
||||
import { PageSelect } from ".";
|
||||
|
||||
describe("<PageSelect />", () => {
|
||||
it("calls setPageCallback on arrow key press", () => {
|
||||
const setPageCallback = jest.fn();
|
||||
|
||||
const tree = mount(
|
||||
<PageSelect
|
||||
totalPages={4}
|
||||
activePage={1}
|
||||
maxPerPage={5}
|
||||
totalItemsCount={17}
|
||||
setPageCallback={setPageCallback}
|
||||
/>
|
||||
);
|
||||
tree.simulate("focus");
|
||||
|
||||
setPageCallback.mockImplementation(val =>
|
||||
tree.setProps({ activePage: val })
|
||||
);
|
||||
|
||||
PressKey(tree, "ArrowRight", 39);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(2);
|
||||
|
||||
PressKey(tree, "ArrowRight", 39);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(3);
|
||||
|
||||
PressKey(tree, "ArrowRight", 39);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(4);
|
||||
|
||||
PressKey(tree, "ArrowRight", 39);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(4);
|
||||
|
||||
PressKey(tree, "ArrowLeft", 37);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(3);
|
||||
|
||||
PressKey(tree, "ArrowLeft", 37);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(2);
|
||||
|
||||
PressKey(tree, "ArrowLeft", 37);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(1);
|
||||
|
||||
PressKey(tree, "ArrowLeft", 37);
|
||||
expect(setPageCallback).toHaveBeenLastCalledWith(1);
|
||||
});
|
||||
});
|
||||
3
ui/src/Styles/Components/Pagination.scss
Normal file
3
ui/src/Styles/Components/Pagination.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.components-pagination:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -122,6 +122,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/MountFade";
|
||||
@import "Styles/Components/NavBarSlide";
|
||||
@import "Styles/Components/SilenceModal";
|
||||
@import "Styles/Components/Pagination";
|
||||
|
||||
.badge,
|
||||
.modal-content {
|
||||
|
||||
@@ -105,6 +105,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/MountFade";
|
||||
@import "Styles/Components/NavBarSlide";
|
||||
@import "Styles/Components/SilenceModal";
|
||||
@import "Styles/Components/Pagination";
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
|
||||
6
ui/src/__mocks__/KeyPress.js
Normal file
6
ui/src/__mocks__/KeyPress.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const PressKey = (tree, key, code) => {
|
||||
tree.simulate("keyDown", { key: key, keyCode: code, which: code });
|
||||
tree.simulate("keyUp", { key: key, keyCode: code, which: code });
|
||||
};
|
||||
|
||||
export { PressKey };
|
||||
Reference in New Issue
Block a user