|
- import { Link, useLocation } from 'react-router-dom'
-
- import type { FC } from 'react'
-
- type Props = { page: number
- totalPages: number
- siblingCount?: number }
-
-
- const range = (start: number, end: number): number[] =>
- [...Array (end - start + 1).keys ()].map (i => start + i)
-
-
- const getPages = (
- page: number,
- total: number,
- siblingCount: number,
- ): (number | '…')[] => {
- if (total <= 1)
- return [1]
-
- const first = 1
- const last = total
-
- const left = Math.max (page - siblingCount, first)
- const right = Math.min (page + siblingCount, last)
-
- const pages: (number | '…')[] = []
-
- pages.push (first)
-
- if (left > first + 1)
- pages.push ('…')
-
- const midStart = Math.max (left, first + 1)
- const midEnd = Math.min (right, last - 1)
- pages.push (...range (midStart, midEnd))
-
- if (right < last - 1)
- pages.push ('…')
-
- if (last !== first)
- pages.push (last)
-
- return pages.filter ((v, i, arr) => i === 0 || v !== arr[i - 1])
- }
-
-
- export default (({ page, totalPages, siblingCount = 4 }) => {
- const location = useLocation ()
-
- const buildTo = (p: number) => {
- const qs = new URLSearchParams (location.search)
- qs.set ('page', String (p))
- return `${ location.pathname }?${ qs.toString () }`
- }
-
- const pages = getPages (page, totalPages, siblingCount)
-
- return (
- <nav className="mt-4 flex justify-center" aria-label="Pagination">
- <div className="flex items-center gap-2">
- {(page > 1)
- ? <Link to={buildTo (page - 1)} aria-label="前のページ"><</Link>
- : <span aria-hidden><</span>}
-
- {pages.map ((p, idx) => (
- (p === '…')
- ? <span key={`dots-${ idx }`}>…</span>
- : ((p === page)
- ? <span key={p} className="font-bold" aria-current="page">{p}</span>
- : <Link key={p} to={buildTo (p)}>{p}</Link>)))}
-
- {(page < totalPages)
- ? <Link to={buildTo (page + 1)} aria-label="次のページ">></Link>
- : <span aria-hidden>></span>}
- </div>
- </nav>)
- }) satisfies FC<Props>
|