Created
August 26, 2020 05:58
-
-
Save prashnts/8959ff4ab1d825de2d3f881ff163a25b to your computer and use it in GitHub Desktop.
Blueprintjs Pagination component example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { Button, Intent } from '@blueprintjs/core' | |
const CELL_COUNT = 7 | |
const CELL_MID_LEN = ~~(CELL_COUNT / 2) | |
class Pagination extends Component { | |
// This component implements pagination buttons such as these: | |
// ┌───┬───┬───┬───┬───┐ | |
// │ < │ 1 │ 2 │ 3 │ > │ | |
// └───┴───┴───┴───┴───┘ | |
// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐ | |
// │ < │ 1 │ 2 │ 3 │ 4 │...│ 9 │10 │ > │ | |
// └───┴───┴───┴───┴───┴───┴───┴───┴───┘ | |
// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐ | |
// │ < │ 1 │...│ 4 │ 5 │ 6 │...│10 │ > │ | |
// └───┴───┴───┴───┴───┴───┴───┴───┴───┘ | |
// | |
// To implement the logic, consider a n page count in an Array struct. | |
// => [1, 2, 3, ..., n-1, n] | |
// Let p be current page. | |
// We have two possible ellipsis positions at 2 (a), and n-1 (b). We need to | |
// find if either of these ellipsis positions should be shown as ellipsis or | |
// as numbers. | |
// We see that if n <= CELL_COUNT, {a, b} := {false, false} | |
// In other cases, if p < (CELL_COUNT / 2), {a, b} := {false, true} | |
// and if p > (CELL_COUNT / 2), {a, b} := {true, false}. | |
constructor (props) { | |
super(props) | |
this.state = { | |
totalCount: props.totalCount, | |
current: props.current || 1, | |
} | |
this.paginate = (nr) => props.onPaginate(nr) | |
} | |
componentWillReceiveProps (props) { | |
this.state = { | |
totalCount: props.totalCount, | |
current: props.current || 1, | |
} | |
} | |
getPagingLayout () { | |
const totalCount = this.state.totalCount, current = this.state.current | |
let pages = [] | |
if (totalCount > CELL_COUNT) { | |
// Fill in first and last positions | |
pages[0] = { nr: 1 } | |
pages[1] = { nr: 2 } | |
pages[CELL_COUNT - 2] = { nr: totalCount - 1 } | |
pages[CELL_COUNT - 1] = { nr: totalCount } | |
if (current <= CELL_MID_LEN) { | |
// b ellipse is enabled and the rest of the array is filled | |
pages[CELL_COUNT - 2].ellipsis = true | |
for (let i = 2; i < CELL_COUNT - 2; i++) { | |
pages[i] = { nr: i + 1 } | |
} | |
} else if ((totalCount - current) < CELL_MID_LEN) { | |
// a ellipsis is enabled and the later part of array is filled | |
pages[1].ellipsis = true | |
for (let i = 2; i < CELL_COUNT - 2; i++) { | |
pages[i] = { nr: totalCount - CELL_COUNT + i + 1 } | |
} | |
} else { | |
// both a and b ellipsis are enabled | |
pages[1].ellipsis = true | |
pages[CELL_COUNT - 2].ellipsis = true | |
// Current selected is put in centre | |
pages[CELL_MID_LEN] = { nr: current } | |
// Fill next and prev to mid point | |
// CELL_COUNT - 5 := n{MID, FIRST, SECOND, LAST, SECONDLAST} | |
for (let i = 1; i < CELL_COUNT - 5; i++) { | |
pages[CELL_MID_LEN + i] = { nr: current + i } | |
pages[CELL_MID_LEN - i] = { nr: current - i } | |
} | |
} | |
} else { | |
for (let i = 0; i < totalCount; i++) { | |
pages[i] = { nr: i + 1, ellipsis: false } | |
} | |
} | |
pages.forEach(p => { | |
if (p.nr === this.state.current) { | |
p.active = true | |
} | |
}) | |
return pages | |
} | |
render () { | |
const ltEnable = this.state.current > 1 | |
const rtEnable = this.state.current < this.state.totalCount | |
const pages = this.getPagingLayout() | |
return ( | |
<div className='pt-button-group'> | |
<Button iconName='chevron-left' | |
disabled={!ltEnable} | |
onClick={() => this.paginate(this.state.current - 1)}/> | |
{pages.map(p => | |
<Button text={p.ellipsis ? '...' : p.nr} | |
key={p.nr} | |
disabled={p.ellipsis} | |
intent={p.active ? Intent.PRIMARY : Intent.DEFAULT} | |
onClick={() => this.paginate(p.nr)}/> | |
)} | |
<Button iconName='chevron-right' | |
disabled={!rtEnable} | |
onClick={() => this.paginate(this.state.current + 1)}/> | |
</div> | |
) | |
} | |
} | |
export default Pagination |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable */ | |
import React from 'react' | |
import sinon from 'sinon' | |
import { mount } from 'enzyme' | |
import Pagination from '.' | |
describe('<Pagination />', () => { | |
it('renders correct number of buttons', () => { | |
const wrapper = mount(<Pagination totalCount={2} onPaginate={() => null} />) | |
expect(wrapper.find('.pt-button')).to.have.length(4) | |
}) | |
it('re-renders on prop change', () => { | |
const wrapper = mount(<Pagination totalCount={2} onPaginate={() => null} />) | |
expect(wrapper.find('.pt-button')).to.have.length(4) | |
// change totalCount to 3 | |
wrapper.setProps({ totalCount: 4 }) | |
expect(wrapper.find('.pt-button')).to.have.length(6) | |
}) | |
it('caps button count to CELL_COUNT', () => { | |
const wrapper = mount(<Pagination totalCount={200} onPaginate={() => null} />) | |
expect(wrapper.find('.pt-button')).to.have.length(9) | |
}) | |
it('ellipsises buttons when totalCount is large', () => { | |
const wrapper = mount(<Pagination totalCount={20} onPaginate={() => null} />) | |
expect(wrapper.find('.pt-button[disabled]').last().text()).to.equal('...') | |
}) | |
it('ellipsises correct side when active page is near the edge', () => { | |
const wrapper = mount(<Pagination totalCount={20} onPaginate={() => null} />) | |
expect(wrapper.find('.pt-button').get(2).innerText).to.equal('2') | |
expect(wrapper.find('.pt-button').get(6).innerText).to.equal('...') | |
wrapper.setProps({ current: 18 }) | |
expect(wrapper.find('.pt-button').get(2).innerText).to.equal('...') | |
expect(wrapper.find('.pt-button').get(6).innerText).to.equal('19') | |
wrapper.setProps({ current: 3 }) | |
expect(wrapper.find('.pt-button').get(2).innerText).to.equal('2') | |
expect(wrapper.find('.pt-button').get(6).innerText).to.equal('...') | |
wrapper.setProps({ current: 10 }) | |
expect(wrapper.find('.pt-button').get(2).innerText).to.equal('...') | |
expect(wrapper.find('.pt-button').get(6).innerText).to.equal('...') | |
}) | |
it('handles clicks correctly', () => { | |
const onPaginate = sinon.spy() | |
const wrapper = mount(<Pagination totalCount={20} onPaginate={onPaginate} />) | |
wrapper.find('.pt-button').last().simulate('click') | |
expect(onPaginate).to.have.property('callCount', 1) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Check out palantir/blueprint#1029