| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 | /* eslint-disable max-len *//* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */import React from 'react';import classNames from 'classnames';import PropTypes from 'prop-types';import { FixedSizeList as List } from 'react-window';import { noop } from 'lodash';import PaginationFoundation, {    AdapterPageList,    KeyDownHandler,    PageList, PaginationAdapter} from '@douyinfe/semi-foundation/pagination/foundation';import { cssClasses, numbers } from '@douyinfe/semi-foundation/pagination/constants';import '@douyinfe/semi-foundation/pagination/pagination.scss';import { numbers as popoverNumbers } from '@douyinfe/semi-foundation/popover/constants';import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';import warning from '@douyinfe/semi-foundation/utils/warning';import ConfigContext, { ContextValue } from '../configProvider/context';import LocaleConsumer from '../locale/localeConsumer';import { Locale } from '../locale/interface';import Select from '../select/index';import InputNumber from '../inputNumber/index';import BaseComponent from '../_base/baseComponent';import Popover from '../popover/index';import { Position } from '../tooltip';const prefixCls = cssClasses.PREFIX;const { Option } = Select;export interface PaginationProps {    total?: number;    showTotal?: boolean;    pageSize?: number;    pageSizeOpts?: Array<number>;    size?: 'small' | 'default';    currentPage?: number;    defaultCurrentPage?: number;    onPageChange?: (currentPage: number) => void;    onPageSizeChange?: (newPageSize: number) => void;    onChange?: (currentPage: number, pageSize: number) => void;    prevText?: React.ReactNode;    nextText?: React.ReactNode;    showSizeChanger?: boolean;    showQuickJumper?: boolean;    popoverZIndex?: number;    popoverPosition?: PopoverPosition;    style?: React.CSSProperties;    className?: string;    hideOnSinglePage?: boolean;    hoverShowPageSelect?: boolean;}export interface PaginationState {    total: number;    showTotal: boolean;    currentPage: number;    pageSize: number;    pageList: PageList;    prevDisabled: boolean;    quickJumpPage: string | number;    nextDisabled: boolean;    restLeftPageList: number[];    restRightPageList: number[];}export type PaginationLocale = Locale['Pagination'];export type PopoverPosition = Position;export { PageList };export default class Pagination extends BaseComponent<PaginationProps, PaginationState> {    static contextType = ConfigContext;    static propTypes = {        total: PropTypes.number,        showTotal: PropTypes.bool,        pageSize: PropTypes.number,        pageSizeOpts: PropTypes.array,        size: PropTypes.string,        currentPage: PropTypes.number,        defaultCurrentPage: PropTypes.number,        onPageChange: PropTypes.func,        onPageSizeChange: PropTypes.func,        onChange: PropTypes.func,        prevText: PropTypes.node,        nextText: PropTypes.node,        showSizeChanger: PropTypes.bool,        popoverZIndex: PropTypes.number,        popoverPosition: PropTypes.string,        style: PropTypes.object,        className: PropTypes.string,        hideOnSinglePage: PropTypes.bool,        hoverShowPageSelect: PropTypes.bool,        showQuickJumper: PropTypes.bool,    };    static defaultProps = {        total: 1,        popoverZIndex: popoverNumbers.DEFAULT_Z_INDEX,        showTotal: false,        pageSize: null as null,        pageSizeOpts: numbers.PAGE_SIZE_OPTION,        defaultCurrentPage: 1,        size: 'default',        onPageChange: noop,        onPageSizeChange: noop,        onChange: noop,        showSizeChanger: false,        className: '',        hideOnSinglePage: false,        showQuickJumper: false,    };    constructor(props: PaginationProps) {        super(props);        this.state = {            total: props.total,            showTotal: props.showTotal,            currentPage: props.currentPage || props.defaultCurrentPage,            pageSize: props.pageSize || props.pageSizeOpts[0] || numbers.DEFAULT_PAGE_SIZE, // Use pageSize first, use the first of pageSizeOpts when not, use the default value when none            pageList: [],            prevDisabled: false,            nextDisabled: false,            restLeftPageList: [],            restRightPageList: [],            quickJumpPage: '',        };        this.foundation = new PaginationFoundation(this.adapter);        this.renderDefaultPage = this.renderDefaultPage.bind(this);        this.renderSmallPage = this.renderSmallPage.bind(this);        warning(            Boolean(props.showSizeChanger && props.hideOnSinglePage),            '[Semi Pagination] You should not use showSizeChanger and hideOnSinglePage in ths same time. At this time, hideOnSinglePage no longer takes effect, otherwise there may be a problem that the switch entry disappears'        );    }    context: ContextValue;    get adapter(): PaginationAdapter<PaginationProps, PaginationState> {        return {            ...super.adapter,            setPageList: (pageListState: AdapterPageList) => {                const { pageList, restLeftPageList, restRightPageList } = pageListState;                this.setState({ pageList, restLeftPageList, restRightPageList });            },            setDisabled: (prevIsDisabled: boolean, nextIsDisabled: boolean) => {                this.setState({ prevDisabled: prevIsDisabled, nextDisabled: nextIsDisabled });            },            updateTotal: (total: number) => this.setState({ total }),            updatePageSize: (pageSize: number) => this.setState({ pageSize }),            updateQuickJumpPage: (quickJumpPage: string | number) => this.setState({ quickJumpPage }),            // updateRestPageList: () => {},            setCurrentPage: (pageIndex: number) => {                this.setState({ currentPage: pageIndex });            },            registerKeyDownHandler: (handler: KeyDownHandler) => {                document.addEventListener('keydown', handler);            },            unregisterKeyDownHandler: (handler: KeyDownHandler) => {                document.removeEventListener('keydown', handler);            },            notifyPageChange: (pageIndex: number) => {                this.props.onPageChange(pageIndex);            },            notifyPageSizeChange: (pageSize: number) => {                this.props.onPageSizeChange(pageSize);            },            notifyChange: (pageIndex: number, pageSize: number) => {                this.props.onChange(pageIndex, pageSize);            }        };    }    componentDidMount() {        this.foundation.init();    }    componentWillUnmount() {        this.foundation.destroy();    }    componentDidUpdate(prevProps: PaginationProps) {        const pagerProps = {            currentPage: this.props.currentPage,            total: this.props.total,            pageSize: this.props.pageSize,        };        let pagerHasChanged = false;        if (prevProps.currentPage !== this.props.currentPage) {            pagerHasChanged = true;            // this.foundation.updatePage(this.props.currentPage);        }        if (prevProps.total !== this.props.total) {            pagerHasChanged = true;        }        if (prevProps.pageSize !== this.props.pageSize) {            pagerHasChanged = true;        }        if (pagerHasChanged) {            this.foundation.updatePage(pagerProps.currentPage, pagerProps.total, pagerProps.pageSize);        }    }    renderPrevBtn() {        const { prevText } = this.props;        const { prevDisabled } = this.state;        const preClassName = classNames({            [`${prefixCls}-item`]: true,            [`${prefixCls}-prev`]: true,            [`${prefixCls}-item-disabled`]: prevDisabled,        });        return (            <li                role="button"                aria-disabled={prevDisabled ? true : false}                aria-label="Previous"                onClick={e => !prevDisabled && this.foundation.goPrev(e)}                className={preClassName}                x-semi-prop="prevText"            >                {prevText || <IconChevronLeft size="large" />}            </li>        );    }    renderNextBtn() {        const { nextText } = this.props;        const { nextDisabled } = this.state;        const nextClassName = classNames({            [`${prefixCls}-item`]: true,            [`${prefixCls}-item-disabled`]: nextDisabled,            [`${prefixCls}-next`]: true,        });        return (            <li                role="button"                aria-disabled={nextDisabled ? true : false}                aria-label="Next"                onClick={e => !nextDisabled && this.foundation.goNext(e)}                className={nextClassName}                x-semi-prop="prevText"            >                {nextText || <IconChevronRight size="large" />}            </li>        );    }    renderPageSizeSwitch(locale: PaginationLocale) {        // rtl modify the default position        const { direction } = this.context;        const defaultPopoverPosition = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';        const { showSizeChanger, popoverPosition = defaultPopoverPosition } = this.props;        const { pageSize } = this.state;        const switchCls = classNames(`${prefixCls}-switch`);        if (!showSizeChanger) {            return null;        }        const pageSizeText = locale.pageSize;        const newPageSizeOpts = this.foundation.pageSizeInOpts();        const options = newPageSizeOpts.map((size: number) => (            <Option value={size} key={size}>                <span>                    {`${size} `}                    {pageSizeText}                </span>            </Option>        ));        return (            <div className={switchCls}>                <Select                    aria-label="Page size selector"                    onChange={newPageSize => this.foundation.changePageSize(newPageSize)}                    value={pageSize}                    key={pageSizeText}                    position={popoverPosition || 'bottomRight'}                    clickToHide                    dropdownClassName={`${prefixCls}-select-dropdown`}                >                    {options}                </Select>            </div>        );    }    renderQuickJump(locale: PaginationLocale) {        const { showQuickJumper } = this.props;        const { quickJumpPage, total, pageSize } = this.state;        if (!showQuickJumper) {            return null;        }        const totalPageNum = this.foundation._getTotalPageNumber(total, pageSize);        const isDisabled = totalPageNum === 1;        const quickJumpCls = classNames({            [`${prefixCls}-quickjump`]: true,            [`${prefixCls}-quickjump-disabled`]: isDisabled        });        return (            <div className={quickJumpCls}>                <span>{locale.jumpTo}</span>                <InputNumber                    value={quickJumpPage}                    className={`${prefixCls}-quickjump-input-number`}                    hideButtons                    disabled={isDisabled}                    onBlur={(e: React.FocusEvent) => this.foundation.handleQuickJumpBlur()}                    onEnterPress={(e: React.KeyboardEvent) => this.foundation.handleQuickJumpEnterPress((e.target as any).value)}                    onChange={(v: string | number) => this.foundation.handleQuickJumpNumberChange(v)}                />                <span>{locale.page}</span>            </div>        );    }    renderPageList() {        const {            pageList,            currentPage,            restLeftPageList,            restRightPageList,        } = this.state;        const { popoverPosition, popoverZIndex } = this.props;        return pageList.map((page, i) => {            const pageListClassName = classNames(`${prefixCls}-item`, {                [`${prefixCls}-item-active`]: currentPage === page,                // [`${prefixCls}-item-rest-opening`]: (i < 3 && isLeftRestHover && page ==='...') || (i > 3 && isRightRestHover && page === '...')            });            const pageEl = (                <li                    key={`${page}${i}`}                    onClick={() => this.foundation.goPage(page, i)}                    className={pageListClassName}                    aria-label={page === '...' ? 'More' : `Page ${page}`}                    aria-current={currentPage === page ? "page" : false}                >                    {page}                </li>            );            if (page === '...') {                let content;                i < 3 ? (content = restLeftPageList) : (content = restRightPageList);                return (                    <Popover                        trigger="hover"                        // onVisibleChange={visible=>this.handleRestHover(visible, i < 3 ? 'left' : 'right')}                        content={this.renderRestPageList(content)}                        key={`${page}${i}`}                        position={popoverPosition}                        zIndex={popoverZIndex}                    >                        {pageEl}                    </Popover>                );            }            return pageEl;        });    }    renderRestPageList(restList: ('...' | number)[]) {        // The number of pages may be tens of thousands, here is virtualized with the help of react-window        const { direction } = this.context;        const className = classNames(`${prefixCls}-rest-item`);        const count = restList.length;        const row = (item: { index: number; style: React.CSSProperties }) => {            const { index, style } = item;            const page = restList[index];            return (                <div                    role="listitem"                    key={`${page}${index}`}                    className={className}                    onClick={() => this.foundation.goPage(page, index)}                    style={style}                    aria-label={`${page}`}                >                    {page}                </div>            );        };        const itemHeight = 32;        const listHeight = count >= 5 ? itemHeight * 5 : itemHeight * count;        return (            // @ts-ignore skip type check cause react-window not update with @types/react 18            <List                className={`${prefixCls}-rest-list`}                itemData={restList}                itemSize={itemHeight}                width={78}                itemCount={count}                height={listHeight}                style={{ direction }}            >                {row}            </List>        );    }    renderSmallPage(locale: PaginationLocale) {        const { className, style, hideOnSinglePage, hoverShowPageSelect, showSizeChanger } = this.props;        const paginationCls = classNames(`${prefixCls}-small`, prefixCls, className);        const { currentPage, total, pageSize } = this.state;        const totalPageNum = Math.ceil(total / pageSize);        if (totalPageNum < 2 && hideOnSinglePage && !showSizeChanger) {            return null;        }        const pageNumbers = Array.from({ length: Math.ceil(total / pageSize) }, (v, i) => i + 1);        const pageList = this.renderRestPageList(pageNumbers);        const page = (<div className={`${prefixCls}-item ${prefixCls}-item-small`}>{currentPage}/{totalPageNum} </div>);        return (            <div className={paginationCls} style={style}>                {this.renderPrevBtn()}                {                    hoverShowPageSelect ? (                        <Popover                            content={pageList}                        >                            {page}                        </Popover>                    ) : page                }                {this.renderNextBtn()}                {this.renderQuickJump(locale)}            </div>        );    }    renderDefaultPage(locale: PaginationLocale) {        const { total, pageSize } = this.state;        const { showTotal, className, style, hideOnSinglePage, showSizeChanger } = this.props;        const paginationCls = classNames(className, `${prefixCls}`);        const showTotalCls = `${prefixCls}-total`;        const totalPageNum = Math.ceil(total / pageSize);        if (totalPageNum < 2 && hideOnSinglePage && !showSizeChanger) {            return null;        }        return (            <ul className={paginationCls} style={style}>                {showTotal ? (                    <span className={showTotalCls}>                        {locale.total}                        {` ${Math.ceil(total / pageSize)} `}                        {locale.page}                    </span>                ) : null}                {this.renderPrevBtn()}                {this.renderPageList()}                {this.renderNextBtn()}                {this.renderPageSizeSwitch(locale)}                {this.renderQuickJump(locale)}            </ul>        );    }    render() {        const { size } = this.props;        return (            <LocaleConsumer componentName="Pagination">                {                    (locale: PaginationLocale) => (                        size === 'small' ? this.renderSmallPage(locale) : this.renderDefaultPage(locale)                    )                }            </LocaleConsumer>        );    }}
 |