Pagination

Page controls for navigating long lists of paged content.

Code

// Pagination.tsx
import React from 'react'
import { PaginationProvider } from './PaginationContext'

interface PaginationProps {
    activePage: number
    totalPages: number
    maxButtons?: number
    onPageChange: (page:number) => void
    children: React.ReactNode
}

const Pagination = ({activePage, totalPages, maxButtons = 5, onPageChange, children}: PaginationProps) => {
  return (
    <nav role="navigation" aria-label="pagination" className="flex flex-row items-center justify-center w-full">
      <PaginationProvider
        activePage={activePage}
        totalPages={totalPages}
        maxButtons={maxButtons}
        onPageChange={onPageChange}
      >
        {children}
      </PaginationProvider>
    </nav>
  )
}

export default Pagination

// PaginationContent.tsx
import { usePagination } from "./PaginationContext"
import { Ellipsis } from "lucide-react"

const PaginationContent = () => {

    const {pagesToRender, activePage, onPageChange} = usePagination()

  return (
    <ul className = "flex flex-row items-center gap-1">
        {pagesToRender.map((item, index) => {
            const isActive = item === activePage;
            
            if (typeof item === 'string'){
                return (
                    <li key = {`ellipsis-${index}`}><Ellipsis size={`16px`} className="text-gray-800"/></li>
                )
            }
            return (
                <li key = {item}>
                    <button
                    className = {`text-sm rounded-md h-7 w-7 text-gray-800 hover:bg-gray-100 ${
                                isActive ? " border rounded-md font-medium border-gray-300" : ""}`}
                    aria-current = {isActive ? "page" : undefined}
                    onClick = {() => onPageChange(item)}
                    >
                        {item}
                    </button>
                </li>
            )
        })}
    </ul>
  )
}

export default PaginationContent

// PaginationPrevious.tsx
import React from 'react'
import { ChevronLeft } from 'lucide-react'
import { usePagination } from './PaginationContext'

const PaginationPrevious = () => {

  const {activePage, onPageChange} = usePagination();

  const isDisabled = activePage === 1;

  return (
    <button
    disabled = {isDisabled}
    className = {`text-sm font-medium rounded-md h-7 w-7 mr-2 flex justify-center items-center ${
                isDisabled ? "text-gray-300 cursor-not-allowed" : "text-gray-500 hover:bg-gray-100"}`}
    onClick = {() =>{
            if(isDisabled) {
                return
            }
            else {
                onPageChange(activePage - 1)
            }
        }
    }
    >
        <ChevronLeft size = {'16px'} className = {`text-sm rounded-md h-4 w-4 ${
                isDisabled ? "text-gray-400 cursor-not-allowed" : "text-gray-500 hover:bg-gray-100"}`}/>
    </button>
  )
}

export default PaginationPrevious

// PaginationNext.tsx
import React from 'react'
import { usePagination } from './PaginationContext'
import { ChevronRight } from 'lucide-react'

const PaginationNext = () => {

  const {totalPages, activePage, onPageChange} = usePagination();

  const isDisabled = activePage === totalPages

  return (
    <button
    disabled = {isDisabled}
    className = {`text-sm font-medium rounded-md h-7 w-7 flex justify-center items-center ml-2 ${
                isDisabled ? "text-gray-300 cursor-not-allowed" : "text-gray-500 hover:bg-gray-100"}`}
    onClick = {() =>{
            if(isDisabled) {
                return
            }
            else {
                onPageChange(activePage + 1)
            }
        }
    }
    >
        <ChevronRight size = {'16px'} className = {`text-sm rounded-md h-4 w-4 ${
                isDisabled ? "text-gray-400 cursor-not-allowed" : "text-gray-500 hover:bg-gray-100"}`}/>
    </button>
  )
}

export default PaginationNext