Skip to content

Commit

Permalink
chore(Pagination): use React.forwardRef() (#4255)
Browse files Browse the repository at this point in the history
  • Loading branch information
layershifter committed Dec 13, 2022
1 parent 73b8582 commit ed3fee7
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 92 deletions.
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

0 comments on commit ed3fee7

Please sign in to comment.