Ship blog/portfolio routes, shared content components, and mobile-safe typography so the marketing site can deploy cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
3.4 KiB
TypeScript
128 lines
3.4 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
|
||
type PaginationProps = {
|
||
page: number;
|
||
pageSize: number;
|
||
total: number;
|
||
basePath: string;
|
||
};
|
||
|
||
function pageHref(basePath: string, page: number) {
|
||
if (page <= 1) return basePath;
|
||
return `${basePath}?page=${page}`;
|
||
}
|
||
|
||
export function Pagination({
|
||
page,
|
||
pageSize,
|
||
total,
|
||
basePath,
|
||
}: PaginationProps) {
|
||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||
|
||
if (totalPages <= 1) return null;
|
||
|
||
const windowSize = 5;
|
||
let start = Math.max(1, page - Math.floor(windowSize / 2));
|
||
const end = Math.min(totalPages, start + windowSize - 1);
|
||
start = Math.max(1, end - windowSize + 1);
|
||
|
||
const pages = Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||
|
||
const scrollTop = () => {
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
};
|
||
|
||
return (
|
||
<nav
|
||
className="mt-14 flex flex-wrap items-center justify-center gap-2"
|
||
aria-label="صفحهبندی"
|
||
>
|
||
{page > 1 ? (
|
||
<Link
|
||
href={pageHref(basePath, page - 1)}
|
||
onClick={scrollTop}
|
||
scroll
|
||
className="rounded-full border border-line/70 px-4 py-2 text-sm text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||
>
|
||
قبلی
|
||
</Link>
|
||
) : (
|
||
<span className="rounded-full border border-line/40 px-4 py-2 text-sm text-muted/40">
|
||
قبلی
|
||
</span>
|
||
)}
|
||
|
||
{start > 1 ? (
|
||
<>
|
||
<Link
|
||
href={pageHref(basePath, 1)}
|
||
onClick={scrollTop}
|
||
scroll
|
||
className="rounded-full border border-line/70 px-3.5 py-2 text-sm text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||
>
|
||
۱
|
||
</Link>
|
||
{start > 2 ? (
|
||
<span className="px-1 text-muted" aria-hidden>
|
||
…
|
||
</span>
|
||
) : null}
|
||
</>
|
||
) : null}
|
||
|
||
{pages.map((n) => (
|
||
<Link
|
||
key={n}
|
||
href={pageHref(basePath, n)}
|
||
onClick={scrollTop}
|
||
scroll
|
||
aria-current={n === page ? "page" : undefined}
|
||
className={`rounded-full border px-3.5 py-2 text-sm transition ${
|
||
n === page
|
||
? "border-ink bg-ink text-surface"
|
||
: "border-line/70 text-ink-soft hover:border-ink/30 hover:text-ink"
|
||
}`}
|
||
>
|
||
{n.toLocaleString("fa-IR")}
|
||
</Link>
|
||
))}
|
||
|
||
{end < totalPages ? (
|
||
<>
|
||
{end < totalPages - 1 ? (
|
||
<span className="px-1 text-muted" aria-hidden>
|
||
…
|
||
</span>
|
||
) : null}
|
||
<Link
|
||
href={pageHref(basePath, totalPages)}
|
||
onClick={scrollTop}
|
||
scroll
|
||
className="rounded-full border border-line/70 px-3.5 py-2 text-sm text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||
>
|
||
{totalPages.toLocaleString("fa-IR")}
|
||
</Link>
|
||
</>
|
||
) : null}
|
||
|
||
{page < totalPages ? (
|
||
<Link
|
||
href={pageHref(basePath, page + 1)}
|
||
onClick={scrollTop}
|
||
scroll
|
||
className="rounded-full border border-line/70 px-4 py-2 text-sm text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||
>
|
||
بعدی
|
||
</Link>
|
||
) : (
|
||
<span className="rounded-full border border-line/40 px-4 py-2 text-sm text-muted/40">
|
||
بعدی
|
||
</span>
|
||
)}
|
||
</nav>
|
||
);
|
||
}
|