82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import { Link } from '@tanstack/react-router'
|
|
import { cn } from '~/lib/utils'
|
|
|
|
type Props = {
|
|
page: number
|
|
itemsPerPage: number
|
|
totalItems: number
|
|
limit?: number
|
|
buildSearch: (nextPage: number) => Record<string, unknown>
|
|
}
|
|
|
|
export function Pagination({ page, itemsPerPage, totalItems, limit = 3, buildSearch }: Props) {
|
|
const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage))
|
|
if (totalPages === 1) return null
|
|
|
|
const pages: number[] = []
|
|
for (let i = 0; i < totalPages; i++) {
|
|
if (i < limit || i >= totalPages - limit || (i >= page - limit && i < page + limit - 1)) {
|
|
pages.push(i + 1)
|
|
}
|
|
}
|
|
|
|
const prev = Math.max(1, page - 1)
|
|
const next = Math.min(totalPages, page + 1)
|
|
|
|
return (
|
|
<div className="my-6 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
<Link
|
|
to="."
|
|
search={buildSearch(prev)}
|
|
className="inline-flex h-10 items-center justify-center rounded-md border px-3 text-sm hover:bg-accent hover:text-accent-foreground"
|
|
>
|
|
← Previous
|
|
</Link>
|
|
|
|
<div className="hidden gap-1 md:flex">
|
|
{pages.map((p, idx) => (
|
|
<div key={p} className="flex items-center">
|
|
{idx > 0 && p - 1 !== pages[idx - 1] && <span className="mx-2 leading-10">…</span>}
|
|
<Link
|
|
to="."
|
|
search={buildSearch(p)}
|
|
className={cn(
|
|
'inline-flex h-10 min-w-10 items-center justify-center rounded-md border px-3 text-sm hover:bg-accent hover:text-accent-foreground',
|
|
p === page && 'bg-accent font-semibold',
|
|
)}
|
|
>
|
|
{p}
|
|
</Link>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<select
|
|
className="h-10 rounded-md border bg-transparent px-3 text-sm md:hidden"
|
|
value={page}
|
|
onChange={(e) => {
|
|
const target = Number(e.target.value)
|
|
const hash = Object.entries(buildSearch(target))
|
|
.map(([k, v]) => `${k}=${v}`)
|
|
.join('&')
|
|
window.location.search = `?${hash}`
|
|
}}
|
|
>
|
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
|
|
<option key={p} value={p}>
|
|
{p}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
<Link
|
|
to="."
|
|
search={buildSearch(next)}
|
|
className="inline-flex h-10 items-center justify-center rounded-md border px-3 text-sm hover:bg-accent hover:text-accent-foreground"
|
|
>
|
|
Next →
|
|
</Link>
|
|
</div>
|
|
)
|
|
}
|