'use client'; import classnames from 'classnames'; import React from 'react'; import MdIconChevronBackward from '../icons-material/MdIconChevronBackward'; import MdIconChevronForward from '../icons-material/MdIconChevronForward'; const MAX_VISIBLE = 5; interface Labels { previous?: string; next?: string; } export interface MdPaginationProps { totalPages: number; currentPage: number; onPageChange?: (page: number) => void; labels?: Labels; compact?: boolean; asChild?: boolean; renderLink?: (page: number, children: React.ReactNode) => React.ReactElement; className?: string; } export const MdPagination: React.FunctionComponent = ({ totalPages, currentPage: currentPageProp, onPageChange, labels = {}, compact = false, asChild = false, renderLink, className, }: MdPaginationProps) => { const defaultLabels: Required = { previous: 'Forrige', next: 'Neste', }; const mergedLabels: Required = { ...defaultLabels, ...labels }; if (!Number.isFinite(totalPages) || !Number.isFinite(currentPageProp)) { return null; } const currentPage = Math.max(1, Math.min(currentPageProp, totalPages)); const classNames = classnames( 'md-pagination', { 'md-pagination--compact': compact, }, className, ); const handlePageChange = (page: number) => { if (page < 1 || page > totalPages || page === currentPage) return; onPageChange?.(page); }; if (totalPages <= 1) return null; const isPreviousDisabled = currentPage === 1; const isNextDisabled = currentPage === totalPages; return ( ); }; export default MdPagination; interface PageButtonProps { page: number; isActive: boolean; asChild: boolean; renderLink?: (page: number, children: React.ReactNode) => React.ReactElement; onPageChange: (page: number) => void; } const PageButton = ({ page, isActive, asChild, renderLink, onPageChange }: PageButtonProps) => { const pageClassName = classnames('md-pagination__page', { 'md-pagination__page--active': isActive, }); const content = page; if (isActive) { return ( {content} ); } if (asChild && renderLink) { const linkElement = renderLink(page, content); const existingOnClick = linkElement.props?.onClick; return React.cloneElement(linkElement, { className: classnames(pageClassName, linkElement.props?.className), 'aria-label': `Side ${page}`, onClick: (event: React.MouseEvent) => { if (typeof existingOnClick === 'function') { existingOnClick(event); } if (!event.defaultPrevented) { onPageChange(page); } }, }); } return ( ); }; interface NavButtonProps { direction: 'previous' | 'next'; disabled: boolean; targetPage: number; label: string; asChild: boolean; renderLink?: (page: number, children: React.ReactNode) => React.ReactElement; onPageChange: (page: number) => void; } const NavButton = ({ direction, disabled, targetPage, label, asChild, renderLink, onPageChange }: NavButtonProps) => { const isPrevious = direction === 'previous'; const Icon = isPrevious ? MdIconChevronBackward : MdIconChevronForward; const content = ( <> {isPrevious && ( )} {label} {!isPrevious && ( )} ); if (disabled) { return ( {content} ); } if (asChild && renderLink) { const linkElement = renderLink(targetPage, content); const existingOnClick = linkElement.props?.onClick; return React.cloneElement(linkElement, { className: classnames('md-pagination__nav', linkElement.props?.className), 'aria-label': label, onClick: (event: React.MouseEvent) => { if (typeof existingOnClick === 'function') { existingOnClick(event); } if (!event.defaultPrevented) { onPageChange(targetPage); } }, }); } return ( ); }; const getPageNumbers = (currentPage: number, totalPages: number): (number | 'ellipsis')[] => { const pages: (number | 'ellipsis')[] = []; if (totalPages <= MAX_VISIBLE + 2) { return Array.from({ length: totalPages }, (_, i) => { return i + 1; }); } pages.push(1); if (currentPage <= 3) { for (let i = 2; i <= MAX_VISIBLE; i++) pages.push(i); pages.push('ellipsis', totalPages); return pages; } if (currentPage >= totalPages - 2) { pages.push('ellipsis'); for (let i = totalPages - MAX_VISIBLE + 1; i <= totalPages; i++) pages.push(i); return pages; } pages.push('ellipsis', currentPage - 1, currentPage, currentPage + 1, 'ellipsis', totalPages); return pages; }; const getCompactPageNumbers = (currentPage: number, totalPages: number): number[] => { if (totalPages <= 3) { return Array.from({ length: totalPages }, (_, i) => { return i + 1; }); } if (currentPage === 1) { return [1, 2, 3]; } if (currentPage === totalPages) { return [totalPages - 2, totalPages - 1, totalPages]; } return [currentPage - 1, currentPage, currentPage + 1]; };