Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Pagination): use React.forwardRef() #4255

Merged
merged 1 commit into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 55 additions & 51 deletions src/addons/Pagination/Pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,83 @@ import PropTypes from 'prop-types'
import React from 'react'

import {
ModernAutoControlledComponent as Component,
createPaginationItems,
customPropTypes,
getUnhandledProps,
useAutoControlledValue,
} from '../../lib'
import Menu from '../../collections/Menu'
import PaginationItem from './PaginationItem'

/**
* A component to render a pagination.
*/
export default class Pagination extends Component {
getInitialAutoControlledState() {
return { activePage: 1 }
}
const Pagination = React.forwardRef(function (props, ref) {
const {
'aria-label': ariaLabel,
boundaryRange,
disabled,
ellipsisItem,
siblingRange,
totalPages,
} = props
const [activePage, setActivePage] = useAutoControlledValue({
state: props.activePage,
defaultState: props.defaultActivePage,
initialState: 1,
})

handleItemClick = (e, { value: nextActivePage }) => {
const { activePage: prevActivePage } = this.state
const handleItemClick = (e, { value: nextActivePage }) => {
const prevActivePage = activePage

// Heads up! We need the cast to the "number" type there, as `activePage` can be a string
if (+prevActivePage === +nextActivePage) return
if (+prevActivePage === +nextActivePage) {
return
}

this.setState({ activePage: nextActivePage })
_.invoke(this.props, 'onPageChange', e, { ...this.props, activePage: nextActivePage })
setActivePage(nextActivePage)
_.invoke(props, 'onPageChange', e, { ...props, activePage: nextActivePage })
}

handleItemOverrides = (active, type, value) => (predefinedProps) => ({
const handleItemOverrides = (active, type, value) => (predefinedProps) => ({
active,
type,
key: `${type}-${value}`,
onClick: (e, itemProps) => {
_.invoke(predefinedProps, 'onClick', e, itemProps)
if (itemProps.type !== 'ellipsisItem') this.handleItemClick(e, itemProps)

if (itemProps.type !== 'ellipsisItem') {
handleItemClick(e, itemProps)
}
},
})

render() {
const {
'aria-label': ariaLabel,
boundaryRange,
disabled,
ellipsisItem,
siblingRange,
totalPages,
} = this.props
const { activePage } = this.state

const items = createPaginationItems({
activePage,
boundaryRange,
hideEllipsis: _.isNil(ellipsisItem),
siblingRange,
totalPages,
})
const rest = getUnhandledProps(Pagination, this.props)

return (
<Menu {...rest} aria-label={ariaLabel} pagination role='navigation'>
{_.map(items, ({ active, type, value }) =>
PaginationItem.create(this.props[type], {
defaultProps: {
content: value,
disabled,
value,
},
overrideProps: this.handleItemOverrides(active, type, value),
}),
)}
</Menu>
)
}
}

const items = createPaginationItems({
activePage,
boundaryRange,
hideEllipsis: _.isNil(ellipsisItem),
siblingRange,
totalPages,
})
const rest = getUnhandledProps(Pagination, props)

return (
<Menu {...rest} aria-label={ariaLabel} pagination role='navigation' ref={ref}>
{_.map(items, ({ active, type, value }) =>
PaginationItem.create(props[type], {
defaultProps: {
content: value,
disabled,
value,
},
overrideProps: handleItemOverrides(active, type, value),
}),
)}
</Menu>
)
})

Pagination.displayName = 'Pagination'
Pagination.propTypes = {
/** A pagination item can have an aria label. */
'aria-label': PropTypes.string,
Expand Down Expand Up @@ -125,8 +129,6 @@ Pagination.propTypes = {
totalPages: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
}

Pagination.autoControlledProps = ['activePage']

Pagination.defaultProps = {
'aria-label': 'Pagination Navigation',
boundaryRange: 1,
Expand All @@ -152,3 +154,5 @@ Pagination.defaultProps = {
}

Pagination.Item = PaginationItem

export default Pagination
57 changes: 28 additions & 29 deletions src/addons/Pagination/PaginationItem.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,47 @@
import keyboardKey from 'keyboard-key'
import _ from 'lodash'
import PropTypes from 'prop-types'
import { Component } from 'react'
import * as React from 'react'

import { createShorthandFactory } from '../../lib'
import MenuItem from '../../collections/Menu/MenuItem'

/**
* An item of a pagination.
*/
class PaginationItem extends Component {
handleClick = (e) => {
_.invoke(this.props, 'onClick', e, this.props)
}
const PaginationItem = React.forwardRef(function (props, ref) {
const { active, type } = props
const disabled = props.disabled || type === 'ellipsisItem'

handleKeyDown = (e) => {
_.invoke(this.props, 'onKeyDown', e, this.props)
if (keyboardKey.getCode(e) === keyboardKey.Enter) _.invoke(this.props, 'onClick', e, this.props)
const handleClick = (e) => {
_.invoke(props, 'onClick', e, props)
}

handleOverrides = () => ({
onClick: this.handleClick,
onKeyDown: this.handleKeyDown,
})

render() {
const { active, type } = this.props
const disabled = this.props.disabled || type === 'ellipsisItem'
const handleKeyDown = (e) => {
_.invoke(props, 'onKeyDown', e, props)

return MenuItem.create(this.props, {
defaultProps: {
active,
'aria-current': active,
'aria-disabled': disabled,
disabled,
onClick: this.handleClick,
onKeyDown: this.handleKeyDown,
tabIndex: disabled ? -1 : 0,
},
overrideProps: this.handleOverrides,
})
if (keyboardKey.getCode(e) === keyboardKey.Enter) {
_.invoke(props, 'onClick', e, props)
}
}
}

return MenuItem.create(props, {
defaultProps: {
active,
'aria-current': active,
'aria-disabled': disabled,
disabled,
tabIndex: disabled ? -1 : 0,
},
overrideProps: () => ({
onClick: handleClick,
onKeyDown: handleKeyDown,
ref,
}),
})
})

PaginationItem.displayName = 'PaginationItem'
PaginationItem.propTypes = {
/** A pagination item can be active. */
active: PropTypes.bool,
Expand Down
23 changes: 11 additions & 12 deletions test/specs/addons/Pagination/Pagination-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import PaginationItem from 'src/addons/Pagination/PaginationItem'
import * as common from 'test/specs/commonTests'
import { sandbox } from 'test/utils'

const requiredProps = {
totalPages: 0,
}

describe('Pagination', () => {
common.isConformant(Pagination, {
requiredProps: {
totalPages: 0,
},
})
common.isConformant(Pagination, { requiredProps })
common.forwardsRef(Pagination, { requiredProps, tagName: 'div' })
common.hasSubcomponents(Pagination, [PaginationItem])

describe('disabled', () => {
Expand All @@ -24,26 +25,24 @@ describe('Pagination', () => {

describe('onPageChange', () => {
it('is called with (e, data) when clicked on a pagination item', () => {
const event = { target: null }
const onPageChange = sandbox.spy()
const onPageItemClick = sandbox.spy()

mount(
const wrapper = mount(
<Pagination
activePage={1}
onPageChange={onPageChange}
pageItem={{ onClick: onPageItemClick }}
totalPages={3}
/>,
)
.find('PaginationItem')
.at(4)
.simulate('click', event)

wrapper.find('PaginationItem').at(4).simulate('click')

onPageChange.should.have.been.calledOnce()
onPageChange.should.have.been.calledWithMatch(event, { activePage: 3 })
onPageChange.should.have.been.calledWithMatch({ type: 'click' }, { activePage: 3 })
onPageItemClick.should.have.been.calledOnce()
onPageItemClick.should.have.been.calledWithMatch(event, { value: 3 })
onPageItemClick.should.have.been.calledWithMatch({ type: 'click' }, { value: 3 })
})

it('will be omitted if occurred for the same pagination item as the current', () => {
Expand Down
1 change: 1 addition & 0 deletions test/specs/addons/Pagination/PaginationItem-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sandbox } from 'test/utils'

describe('PaginationItem', () => {
common.isConformant(PaginationItem)
common.forwardsRef(PaginationItem, { tagName: 'a' })
common.implementsCreateMethod(PaginationItem)

describe('active', () => {
Expand Down