ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

94 lines
2.3 KiB

  1. import { useLocation } from 'react-router-dom'
  2. import PrefetchLink from '@/components/PrefetchLink'
  3. import type { FC } from 'react'
  4. type Props = { page: number
  5. totalPages: number
  6. siblingCount?: number }
  7. const range = (start: number, end: number): number[] =>
  8. [...Array (end - start + 1).keys ()].map (i => start + i)
  9. const getPages = (
  10. page: number,
  11. total: number,
  12. siblingCount: number,
  13. ): (number | '…')[] => {
  14. if (total <= 1)
  15. return [1]
  16. const first = 1
  17. const last = total
  18. const left = Math.max (page - siblingCount, first)
  19. const right = Math.min (page + siblingCount, last)
  20. const pages: (number | '…')[] = []
  21. pages.push (first)
  22. if (left > first + 1)
  23. pages.push ('…')
  24. const midStart = Math.max (left, first + 1)
  25. const midEnd = Math.min (right, last - 1)
  26. pages.push (...range (midStart, midEnd))
  27. if (right < last - 1)
  28. pages.push ('…')
  29. if (last !== first)
  30. pages.push (last)
  31. return pages.filter ((v, i, arr) => i === 0 || v !== arr[i - 1])
  32. }
  33. export default (({ page, totalPages, siblingCount = 2 }) => {
  34. const location = useLocation ()
  35. const buildTo = (p: number) => {
  36. const qs = new URLSearchParams (location.search)
  37. qs.set ('page', String (p))
  38. return `${ location.pathname }?${ qs.toString () }`
  39. }
  40. const pages = getPages (page, totalPages, siblingCount)
  41. return (
  42. <nav className="mt-4 flex justify-center" aria-label="Pagination">
  43. <div className="flex items-center gap-2">
  44. {(page > 1)
  45. ? (
  46. <PrefetchLink
  47. className="p-2"
  48. to={buildTo (page - 1)}
  49. aria-label="前のページ">
  50. &lt;
  51. </PrefetchLink>)
  52. : <span className="p-2" aria-hidden>&lt;</span>}
  53. {pages.map ((p, idx) => (
  54. (p === '…')
  55. ? <span key={`dots-${ idx }`} className="p-2">…</span>
  56. : ((p === page)
  57. ? <span key={p} className="font-bold p-2" aria-current="page">{p}</span>
  58. : <PrefetchLink key={p} className="p-2" to={buildTo (p)}>{p}</PrefetchLink>)))}
  59. {(page < totalPages)
  60. ? (
  61. <PrefetchLink
  62. className="p-2"
  63. to={buildTo (page + 1)}
  64. aria-label="次のページ">
  65. &gt;
  66. </PrefetchLink>)
  67. : <span className="p-2" aria-hidden>&gt;</span>}
  68. </div>
  69. </nav>)
  70. }) satisfies FC<Props>