File size: 4,076 Bytes
f1dd159 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import { ChevronsLeft, ChevronsRight, ChevronLeft, ChevronRight } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/shared/lib/cn";
const PAGE_SIZE_OPTIONS = [20, 100, 500, 1000, 2000] as const;
export function Pagination({ page, pageSize, total, onPageChange, onPageSizeChange, className }: { page: number; pageSize: number; total: number; onPageChange: (page: number) => void; onPageSizeChange?: (pageSize: number) => void; className?: string }) {
const { t } = useTranslation();
const pages = Math.max(1, Math.ceil(total / pageSize));
return (
<div className={cn("flex w-full flex-wrap items-center justify-between gap-3", className)}>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="size-8" disabled={page <= 1} onClick={() => onPageChange(1)} aria-label={t("common.firstPage")}><ChevronsLeft /></Button>
<Button variant="ghost" size="icon" className="size-8" disabled={page <= 1} onClick={() => onPageChange(page - 1)} aria-label={t("common.previousPage")}><ChevronLeft /></Button>
<span className="min-w-20 px-2 text-center text-xs text-muted-foreground">{t("common.pageOf", { page, pages })}</span>
<Button variant="ghost" size="icon" className="size-8" disabled={page >= pages} onClick={() => onPageChange(page + 1)} aria-label={t("common.nextPage")}><ChevronRight /></Button>
<Button variant="ghost" size="icon" className="size-8" disabled={page >= pages} onClick={() => onPageChange(pages)} aria-label={t("common.lastPage")}><ChevronsRight /></Button>
</div>
{onPageSizeChange ? <PageSizeSelector pageSize={pageSize} onChange={onPageSizeChange} /> : <span />}
</div>
);
}
export function CursorPagination({ page, pageSize, hasMore, disabled = false, onFirstPage, onPreviousPage, onNextPage, onPageSizeChange, className }: { page: number; pageSize: number; hasMore: boolean; disabled?: boolean; onFirstPage: () => void; onPreviousPage: () => void; onNextPage: () => void; onPageSizeChange: (pageSize: number) => void; className?: string }) {
const { t } = useTranslation();
return (
<div className={cn("flex w-full flex-wrap items-center justify-between gap-3", className)}>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="size-8" disabled={disabled || page <= 1} onClick={onFirstPage} aria-label={t("common.firstPage")}><ChevronsLeft /></Button>
<Button variant="ghost" size="icon" className="size-8" disabled={disabled || page <= 1} onClick={onPreviousPage} aria-label={t("common.previousPage")}><ChevronLeft /></Button>
<span className="min-w-20 px-2 text-center text-xs text-muted-foreground">{t("audits.cursorPage", { page })}</span>
<Button variant="ghost" size="icon" className="size-8" disabled={disabled || !hasMore} onClick={onNextPage} aria-label={t("common.nextPage")}><ChevronRight /></Button>
</div>
<PageSizeSelector pageSize={pageSize} disabled={disabled} onChange={onPageSizeChange} />
</div>
);
}
function PageSizeSelector({ pageSize, disabled = false, onChange }: { pageSize: number; disabled?: boolean; onChange: (pageSize: number) => void }) {
const { t } = useTranslation();
return (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>{t("common.perPage")}</span>
<Select value={String(pageSize)} disabled={disabled} onValueChange={(value) => onChange(Number(value))}>
<SelectTrigger className="h-8 w-[76px] rounded-md bg-secondary px-3 text-xs shadow-none" aria-label={t("common.perPage")}><SelectValue /></SelectTrigger>
<SelectContent align="end">
{PAGE_SIZE_OPTIONS.map((value) => <SelectItem key={value} value={String(value)}>{value}</SelectItem>)}
</SelectContent>
</Select>
<span>{t("common.rows")}</span>
</div>
);
}
|