UNPKG

kitten-components

Version:
348 lines (306 loc) 9.02 kB
import React, { Component } from 'react' import Radium from 'radium' import PropTypes from 'prop-types' import { Text as TextBase } from 'kitten/components/typography/text' import { ArrowIcon as ArrowIconBase } from 'kitten/components/icons/arrow-icon' import { ScreenConfig } from 'kitten/constants/screen-config' import COLORS from 'kitten/constants/colors-config' import { parseHtml } from 'kitten/helpers/utils/parser' import { mediaQueries } from 'kitten/hoc/media-queries' const Text = Radium(TextBase) const ArrowIcon = Radium(ArrowIconBase) // Returns an array with the given bounds const range = (start, end) => Array(end - start + 1) .fill() .map((_, index) => start + index) // Returns an array of size `availableSlots` with page number integers // and breaks "…" (represented as nulls). export function pages(min, max, currentPage, availableSlots) { // 1, 2, 3 if (max - min < availableSlots) { return range(min, max) } // 1, 2, 3, …, 42 if (currentPage - min + 1 < availableSlots - 2) { return [...range(min, min - 1 + availableSlots - 2), null, max] } // 1, …, 40, 41, 42 if (max - currentPage < availableSlots - 2) { return [min, null, ...range(max + 1 - (availableSlots - 2), max)] } // 1, …, 21, …, 42 const sides = Math.floor((availableSlots - 4) / 2) return [ min, null, ...range(currentPage - sides, currentPage + sides), null, max, ] } class PaginationBase extends Component { static propTypes = { prevButtonLabel: PropTypes.string, nextButtonLabel: PropTypes.string, goToPageLabel: PropTypes.func, goToPageHref: PropTypes.func, onPageClick: PropTypes.func, totalPages: PropTypes.number, currentPage: PropTypes.number, 'aria-label': PropTypes.string, } static defaultProps = { prevButtonLabel: 'Previous page', nextButtonLabel: 'Next page', goToPageLabel: n => `Go to page ${n}`, goToPageHref: n => `#${n}`, onPageClick: () => {}, currentPage: 1, totalPages: 1, 'aria-label': 'Pagination navigation', } render() { const { totalPages, currentPage } = this.props const size = this.props.viewportIsTabletOrLess ? 5 : 7 const pageNumbers = pages(1, totalPages, currentPage, size) return ( <nav role="navigation" aria-label={this.props['aria-label']}> <ul style={styles.group}> {this.renderArrowButton('left')} {pageNumbers.map(this.renderPage)} {this.renderArrowButton('right')} </ul> </nav> ) } preventClickDefault = e => e.preventDefault() pageClickHandler = number => event => this.props.onPageClick(number, event) renderPage = (number, index) => { if (!number) return this.renderSpacer(index) const isActive = number === this.props.currentPage const tag = isActive ? 'span' : 'a' const href = isActive ? null : this.props.goToPageHref(number) const styleButtonIcon = [ styles.group.list.buttonIcon, isActive && styles.group.list.buttonIcon.isActive, ] return ( <li style={styles.group.list} key={`page-${number}`}> <Text tag={tag} weight="regular" size="tiny" href={href} key={`link-${number}`} style={styleButtonIcon} aria-label={this.props.goToPageLabel(number)} onClick={isActive ? null : this.pageClickHandler(number)} > {number} </Text> </li> ) } renderSpacer(index) { return ( <li key={`spacer-${index}`} style={styles.group.list.points}> {'…'} </li> ) } renderArrowButton(direction) { const { prevButtonLabel, nextButtonLabel, currentPage, totalPages, } = this.props const buttonLabel = direction == 'left' ? parseHtml(prevButtonLabel) : parseHtml(nextButtonLabel) const isDisabled = direction == 'left' ? currentPage == 1 : currentPage == totalPages const linkIsHovered = Radium.getState( this.state, `link-${direction}`, ':hover', ) const linkIsFocused = Radium.getState( this.state, `link-${direction}`, ':focus', ) const linkIsActived = Radium.getState( this.state, `link-${direction}`, ':active', ) const styleList = [ direction == 'left' && styles.group.list.left, direction == 'right' && styles.group.list.right, ] const styleButtonIcon = [ styles.group.list.buttonIcon, isDisabled && styles.group.list.buttonIcon.isDisabled, ] const styleSvg = [ styles.group.list.buttonIcon.svg, linkIsHovered && !isDisabled && styles.group.list.buttonIcon.svg.hover, linkIsFocused && !isDisabled && styles.group.list.buttonIcon.svg.focus, linkIsActived && !isDisabled && styles.group.list.buttonIcon.svg.active, ] const number = direction == 'left' ? currentPage == 1 ? 1 : currentPage - 1 : currentPage == totalPages ? totalPages : currentPage + 1 return ( <li style={styleList}> <a href={this.props.goToPageHref(number)} key={`link-${direction}`} style={styleButtonIcon} aria-label={buttonLabel} title={buttonLabel} tabIndex={isDisabled ? -1 : null} onClick={ isDisabled ? this.preventClickDefault : this.pageClickHandler(number) } > <ArrowIcon direction={direction} disabled={isDisabled} style={styleSvg} /> </a> </li> ) } } const linkHoveredAndFocused = { color: COLORS.primary1, borderColor: COLORS.primary1, backgroundColor: COLORS.background1, } const disabledPseudoClass = { color: COLORS.background1, borderColor: COLORS.line2, backgroundColor: COLORS.line2, } const isActivedPseudoClass = { color: COLORS.background1, borderColor: COLORS.primary1, backgroundColor: COLORS.primary1, } const styles = { group: { display: 'inline-flex', padding: 0, list: { listStyle: 'none', marginRight: 0, [`@media (min-width: ${ScreenConfig['S'].min}px)`]: { marginRight: '8px', marginLeft: '8px', }, lastChild: { marginRight: 0, }, left: { marginRight: '30px', listStyle: 'none', [`@media (min-width: ${ScreenConfig['S'].min}px)`]: { marginRight: '22px', }, }, right: { marginLeft: '30px', listStyle: 'none', [`@media (min-width: ${ScreenConfig['S'].min}px)`]: { marginLeft: '22px', }, }, points: { listStyle: 'none', textDecoration: 'none', textAlign: 'center', alignSelf: 'center', width: '40px', [`@media (min-width: ${ScreenConfig['S'].min}px)`]: { marginLeft: '8px', marginRight: '8px', width: '50px', }, }, buttonIcon: { display: 'flex', justifyContent: 'center', alignItems: 'center', boxSizing: 'border-box', cursor: 'pointer', width: '40px', height: '40px', borderRadius: 0, borderWidth: 0, borderStyle: 'solid', textDecoration: 'none', outline: 'none', color: COLORS.font1, borderColor: COLORS.line1, backgroundColor: COLORS.background1, ':hover': linkHoveredAndFocused, ':focus': linkHoveredAndFocused, ':active': isActivedPseudoClass, [`@media (min-width: ${ScreenConfig['S'].min}px)`]: { width: '50px', height: '50px', borderWidth: '2px', }, isActive: { cursor: 'auto', color: COLORS.background1, borderColor: COLORS.primary1, backgroundColor: COLORS.primary1, ':hover': isActivedPseudoClass, ':focus': isActivedPseudoClass, ':active': isActivedPseudoClass, }, isDisabled: { color: COLORS.background1, borderColor: COLORS.line2, backgroundColor: COLORS.line2, cursor: 'not-allowed', ':hover': disabledPseudoClass, ':focus': disabledPseudoClass, ':active': disabledPseudoClass, }, svg: { alignSelf: 'center', margin: '0', padding: '0', width: '6px', height: '6px', pointerEvents: 'none', hover: { fill: COLORS.primary1, }, focus: { fill: COLORS.primary1, }, active: { fill: COLORS.background1, }, isDisabled: { fill: COLORS.background1, }, }, }, }, }, } export const Pagination = mediaQueries(Radium(PaginationBase), { viewportIsTabletOrLess: true, })