import React, { useState, useEffect, ChangeEvent, useRef, useCallback } from 'react'; import { type TStyle, useClassnames } from '../../hooks/use-classnames'; import { Dropdown } from '../dropdown'; import { DropdownOption } from '../dropdown/components/option'; import { IconArrowsChevronBottom } from '../icons/arrows/chevron-bottom'; import { Text } from '../typography/v1/text'; import { InputText } from '../input-text'; import { debounce } from '../../tools/debounce'; import { consoleFormat } from '../../tools/console-format'; import PaginationButtons from './pagination-buttons'; import styles from './index.module.pcss'; const ITEMS_PER_PAGE_PRESETS = [5, 10, 20, 50, 100]; const DEFAULT_DEBOUNCE_TIMEOUT = 300; interface IOption { label: string, value: string | number, isChecked?: boolean, onClick?: (value: string | number) => void } export interface IProps { /** * Задает дополнительные CSS классы для стилизации компонента **/ readonly className?: string | TStyle, /** * Начальная страница по-умолчанию **/ readonly currentPage?: number, /** * Пресеты для выпадающего списка выбора количества записей, дефолт [5, 10, 20, 50, 100] **/ readonly itemsPerPagePreset?: Array, /** * Объект переводов **/ readonly i18n: { label: string, placeholder?: string }, /** * Количество айтемов на странице **/ readonly numberItemsPerPage: number, /** * Количество кнопок справа и слева от выборанной страницы **/ readonly numberOfEdgeButtons?: number, /** * Общее количество айтемов **/ readonly numberOfItems: number, /** * Количество средних кнопок **/ readonly numberOfMiddleButtons?: number, /** * Обработчик на смену страницы **/ readonly onChangePage?: (page: number) => void, /** * Обработчик на смену размера страницы **/ readonly onChangePageSize?: (pageSize: number) => void, /** * Обработчик на смену размера страницы и номера страницы **/ readonly onChangePagination?: ({ pageSize, pageNumber }: { pageSize?: number, pageNumber: number }) => void, /** * Показывать инпут ручного выбора страницы **/ readonly showPageInput?: boolean, /** * Изменить таймаут инпута ручного выбора страницы, дефолт 300 мс **/ readonly pageInputTimeout?: number, /** * Свойство позволяет объединить лейбл пагинации с триггером **/ readonly isTriggerCombined?: boolean, /** * Свойство определяет расположение триггера относительно кнопок **/ readonly direction?: 'row' | 'column', /** * Свойство позволяет скрывать кнопки пагинации в случае, если страница одна **/ readonly isHideButtonsWithOnePage?: boolean } export const Pagination = ({ numberOfEdgeButtons = 4, numberOfMiddleButtons = 3, currentPage = 1, direction = 'row', pageInputTimeout = DEFAULT_DEBOUNCE_TIMEOUT, itemsPerPagePreset = ITEMS_PER_PAGE_PRESETS, i18n: { label = '', placeholder = '' }, ...props }: IProps) => { const cn = useClassnames(styles, props.className); const $input = useRef(null); const [numberOfPages, setNumberOfPages] = useState(0); const [isInputError, setIsInputError] = useState(false); const [numberItemsPerPage, setNumberItemsPerPage] = useState(props.numberItemsPerPage); useEffect(() => { if(props.numberOfItems > 0 && numberItemsPerPage > 0) { setNumberOfPages(Math.ceil(props.numberOfItems / numberItemsPerPage)); } else { setNumberOfPages(0); } }, [props.numberOfItems, props.numberItemsPerPage, numberItemsPerPage]); useEffect(() => { if(props.onChangePage ?? props.onChangePageSize) { consoleFormat('Свойства `onChangePage` и `onChangePageSize` устарели и будут удалены в следующих релизах, используйте `onChangePagination`'); } }, [props.onChangePage, props.onChangePageSize]); const resetInput = () => { setIsInputError(false); if($input.current) { $input.current.value = ''; } }; const onChangePage = (pageNumber: number) => { // TODO Deprecated, убрать в следующих релизах props.onChangePage?.(pageNumber); props.onChangePagination?.({ pageNumber }); resetInput(); }; const onClickPreset = (preset: number) => { setNumberItemsPerPage(preset); resetInput(); // TODO Deprecated, убрать в следующих релизах props.onChangePageSize?.(preset); // TODO Deprecated, убрать в следующих релизах props.onChangePage?.(1); props.onChangePagination?.({ pageSize: preset, pageNumber: 1 }); }; const onSetCurrentPage = debounce((e: ChangeEvent) => { const value = parseInt(e.target.value, 10); // TODO Deprecated, убрать в следующих релизах props.onChangePage?.(!isNaN(value) ? value : 1); props.onChangePagination?.({ pageNumber: !isNaN(value) ? value : 1 }); setIsInputError(value > numberOfPages); }, pageInputTimeout); const options: Array = itemsPerPagePreset.map((preset) => ({ value : String(preset), label : String(preset) })); const elDefaultTrigger = useCallback((isOpen: boolean, isCombined?: boolean) => { return (
{numberItemsPerPage}
); }, [numberItemsPerPage]); const elPaginationTrigger = useCallback((isOpen: boolean) => { if(props.isTriggerCombined) { return (
{label} {elDefaultTrigger(isOpen, true)}
); } return elDefaultTrigger(isOpen); }, [numberItemsPerPage, props.isTriggerCombined, label]); if(props.showPageInput && !placeholder) { return ( Для свойства showPageInput обязательно необходимо указать i18n.placeholder ); } if(props.showPageInput && direction === 'column') { return ( Свойства showPageInput и direction === column несовместимы ); } const isShowButtons = props.isHideButtonsWithOnePage ? numberOfPages > 1 : true; return (
{!props.isTriggerCombined && label} { return options.map((option, index) => ( { onClose(); onClickPreset(option.value as number); }} className={cn('pagination__option')} > {option.label} )); }} />
{props.showPageInput ? ( ) : null} {isShowButtons ? ( ) : null}
); };