Files
star-erp/resources/js/Components/shared/Pagination.tsx
2026-03-09 13:48:06 +08:00

110 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from "@inertiajs/react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
interface PaginationProps {
links: {
url: string | null;
label: string;
active: boolean;
}[];
className?: string;
}
export default function Pagination({ links, className }: PaginationProps) {
// 如果只有一頁,不顯示分頁
if (links.length <= 3) return null;
return (
<div className={cn("flex flex-wrap justify-center gap-1", className)}>
{links.map((link, key) => {
// 處理特殊標籤
let label = link.label;
if (label.includes("&laquo;")) label = "Previous";
if (label.includes("&raquo;")) label = "Next";
const isPrevious = label === "Previous";
const isNext = label === "Next";
const activeIndex = links.findIndex(l => l.active);
// Responsive visibility logic:
// Global: Previous, Next, Active are always visible
// Mobile (< sm): Active, +-1, First, Last, and Ellipses
// Tablet (sm < md): Active, +-2, First, Last, and Ellipses
// Desktop (>= md): All standard pages
const isFirst = key === 1;
const isLast = key === links.length - 2;
const isEllipsis = !isPrevious && !isNext && !link.url;
const isMobileVisible =
isPrevious ||
isNext ||
link.active ||
isFirst ||
isLast ||
isEllipsis ||
key === activeIndex - 1 ||
key === activeIndex + 1;
const isTabletVisible =
isMobileVisible ||
key === activeIndex - 2 ||
key === activeIndex + 2;
const baseClasses = cn(
"h-9 items-center justify-center rounded-md border px-3 text-sm",
isMobileVisible ? "flex" : (isTabletVisible ? "hidden sm:flex md:flex" : "hidden md:flex")
);
// 如果是 Previous/Next 但沒有 URL則不渲染或者渲染為 disabled
if ((isPrevious || isNext) && !link.url) {
return (
<div
key={key}
className={cn(
baseClasses,
"border-input bg-transparent text-muted-foreground opacity-50 cursor-not-allowed",
isPrevious || isNext ? "px-2" : ""
)}
>
{isPrevious && <ChevronLeft className="h-4 w-4" />}
{isNext && <ChevronRight className="h-4 w-4" />}
{!isPrevious && !isNext && <span dangerouslySetInnerHTML={{ __html: link.label }} />}
</div>
);
}
return link.url ? (
<Link
key={key}
href={link.url}
preserveScroll
className={cn(
baseClasses,
"transition-colors hover:bg-accent hover:text-accent-foreground",
link.active
? "border-primary bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
: "border-input bg-transparent text-foreground",
isPrevious || isNext ? "px-2" : ""
)}
>
{isPrevious && <ChevronLeft className="h-4 w-4" />}
{isNext && <ChevronRight className="h-4 w-4" />}
{!isPrevious && !isNext && <span dangerouslySetInnerHTML={{ __html: link.label }} />}
</Link>
) : (
<div
key={key}
className={cn(
baseClasses,
"border-input bg-transparent text-foreground"
)}
>
<span dangerouslySetInnerHTML={{ __html: link.label }} />
</div>
);
})}
</div>
);
}