Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119<script module lang="ts"> import { tv } from "tailwind-variants";
export const pagination = tv({ base: "flex items-center gap-1" });
type Item = number | "start-ellipsis" | "end-ellipsis";
function range(start: number, end: number): number[] { return Array.from({ length: Math.max(end - start + 1, 0) }, (_, i) => start + i); }
// Windowing per MUI's usePagination: always show the boundary pages // (first/last) plus a sibling window around the current page, collapsing // any gap into a single ellipsis. function getPageItems(current: number, count: number): Item[] { const siblingCount = 1; const boundaryCount = 1;
const startPages = range(1, Math.min(boundaryCount, count)); const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count);
const siblingsStart = Math.max( Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2 ); const siblingsEnd = Math.min( Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : count - 1 );
return [ ...startPages, ...(siblingsStart > boundaryCount + 2 ? (["start-ellipsis"] as const) : boundaryCount + 1 < count - boundaryCount ? [boundaryCount + 1] : []), ...range(siblingsStart, siblingsEnd), ...(siblingsEnd < count - boundaryCount - 1 ? (["end-ellipsis"] as const) : count - boundaryCount > boundaryCount ? [count - boundaryCount] : []), ...endPages ]; }</script>
<script lang="ts"> import ChevronLeft from "$icon/chevron-left"; import ChevronRight from "$icon/chevron-right"; import Button from "./Button.svelte";
interface Props { page: number; total: number; onchange?: (page: number) => void; class?: string; labels?: boolean; }
let { page = $bindable(1), total, onchange, class: className, labels }: Props = $props();
const count = $derived(Math.max(total, 1)); const items = $derived(getPageItems(Math.min(Math.max(page, 1), count), count));
function goTo(target: number) { const next = Math.min(Math.max(target, 1), count); if (next === page) return; page = next; onchange?.(next); }</script>
<nav aria-label="Pagination" class={pagination({ class: className })}> <Button variant="ghost" icon={ChevronLeft} disabled={page <= 1} aria-label="Previous page" onclick={() => goTo(page - 1)} > <span class="hidden sm:inline">{labels ? "Previous" : ""}</span> </Button>
{#each items as item, i (typeof item === "number" ? item : `${item}-${i}`)} {#if typeof item === "number"} <Button variant={item === page ? "default" : "ghost"} aria-current={item === page ? "page" : undefined} aria-label={`Page ${item}`} onclick={() => goTo(item)} > {item} </Button> {:else} <span class="pointer-events-none flex size-8 items-center justify-center text-foreground-subtle select-none" aria-hidden="true" > … </span> {/if} {/each}
<Button variant="ghost" icon={ChevronRight} iconSide="right" disabled={page >= count} aria-label="Next page" onclick={() => goTo(page + 1)} > <span class="hidden sm:inline">{labels ? "Next" : ""}</span> </Button></nav>