ぼざクリタグ広場 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.
 
 
 
 
 
 

128 lines
2.8 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 = 3 }) => {
  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. <>
  47. <PrefetchLink
  48. className="md:hidden p-2"
  49. to={buildTo (1)}
  50. aria-label="最初のページ">
  51. |&lt;
  52. </PrefetchLink>
  53. <PrefetchLink
  54. className="p-2"
  55. to={buildTo (page - 1)}
  56. aria-label="前のページ">
  57. &lt;
  58. </PrefetchLink>
  59. </>)
  60. : (
  61. <>
  62. <span className="md:hidden p-2" aria-hidden>
  63. |&lt;
  64. </span>
  65. <span className="p-2" aria-hidden>
  66. &lt;
  67. </span>
  68. </>)}
  69. {pages.map ((p, idx) => (
  70. (p === '…')
  71. ? <span key={`dots-${ idx }`} className="hidden md:block p-2">…</span>
  72. : ((p === page)
  73. ? <span key={p} className="font-bold p-2" aria-current="page">{p}</span>
  74. : (
  75. <PrefetchLink
  76. key={p}
  77. className="hidden md:block p-2"
  78. to={buildTo (p)}>
  79. {p}
  80. </PrefetchLink>))))}
  81. {(page < totalPages)
  82. ? (
  83. <>
  84. <PrefetchLink
  85. className="p-2"
  86. to={buildTo (page + 1)}
  87. aria-label="次のページ">
  88. &gt;
  89. </PrefetchLink>
  90. <PrefetchLink
  91. className="md:hidden p-2"
  92. to={buildTo (totalPages)}
  93. aria-label="最後のページ">
  94. &gt;|
  95. </PrefetchLink>
  96. </>)
  97. : (
  98. <>
  99. <span className="p-2" aria-hidden>&gt;</span>
  100. <span className="md:hidden p-2" aria-hidden>&gt;|</span>
  101. </>)}
  102. </div>
  103. </nav>)
  104. }) satisfies FC<Props>