mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
import { motion } from "framer-motion";
|
||
|
||
interface Props {
|
||
page: number; // 1-based
|
||
pageCount: number;
|
||
onPage: (page: number) => void;
|
||
}
|
||
|
||
/** Build a windowed page list with ellipses, e.g. 1 … 4 5 6 … 12 */
|
||
function pageItems(page: number, count: number): (number | "…")[] {
|
||
if (count <= 7) return Array.from({ length: count }, (_, i) => i + 1);
|
||
const items: (number | "…")[] = [1];
|
||
const start = Math.max(2, page - 1);
|
||
const end = Math.min(count - 1, page + 1);
|
||
if (start > 2) items.push("…");
|
||
for (let i = start; i <= end; i++) items.push(i);
|
||
if (end < count - 1) items.push("…");
|
||
items.push(count);
|
||
return items;
|
||
}
|
||
|
||
export function Pagination({ page, pageCount, onPage }: Props) {
|
||
if (pageCount <= 1) return null;
|
||
const items = pageItems(page, pageCount);
|
||
|
||
return (
|
||
<div className="mt-6 flex items-center justify-center gap-1.5">
|
||
<button
|
||
onClick={() => onPage(page - 1)}
|
||
disabled={page === 1}
|
||
className="focusable grid h-9 w-9 place-items-center rounded-xl border-[1.5px] border-line bg-surface text-ink transition-opacity hover:bg-surface-2 disabled:opacity-30"
|
||
aria-label="Previous page"
|
||
>
|
||
‹
|
||
</button>
|
||
|
||
{items.map((it, i) =>
|
||
it === "…" ? (
|
||
<span key={`e${i}`} className="px-1 text-sm text-muted">
|
||
…
|
||
</span>
|
||
) : (
|
||
<button
|
||
key={it}
|
||
onClick={() => onPage(it)}
|
||
aria-current={it === page}
|
||
className="focusable relative grid h-9 min-w-9 place-items-center rounded-xl px-2 text-sm font-semibold"
|
||
>
|
||
{it === page && (
|
||
<motion.span
|
||
layoutId="page-pill"
|
||
transition={{ type: "spring", stiffness: 480, damping: 32 }}
|
||
className="absolute inset-0 rounded-xl bg-accent"
|
||
/>
|
||
)}
|
||
<span
|
||
className={`relative z-10 ${
|
||
it === page ? "text-accent-ink" : "text-muted hover:text-ink"
|
||
}`}
|
||
>
|
||
{it}
|
||
</span>
|
||
</button>
|
||
),
|
||
)}
|
||
|
||
<button
|
||
onClick={() => onPage(page + 1)}
|
||
disabled={page === pageCount}
|
||
className="focusable grid h-9 w-9 place-items-center rounded-xl border-[1.5px] border-line bg-surface text-ink transition-opacity hover:bg-surface-2 disabled:opacity-30"
|
||
aria-label="Next page"
|
||
>
|
||
›
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|