215 lines
5.2 KiB
TypeScript
215 lines
5.2 KiB
TypeScript
import * as React from "react"
|
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
|
|
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
|
<nav
|
|
role="navigation"
|
|
aria-label="pagination"
|
|
className={cn("flex justify-end p-3", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
Pagination.displayName = "Pagination"
|
|
|
|
const PaginationContent = React.forwardRef<
|
|
HTMLUListElement,
|
|
React.ComponentProps<"ul">
|
|
>(({ className, ...props }, ref) => (
|
|
<ul
|
|
ref={ref}
|
|
className={cn("flex flex-row items-center gap-2", className)}
|
|
{...props}
|
|
/>
|
|
))
|
|
PaginationContent.displayName = "PaginationContent"
|
|
|
|
const PaginationItem = React.forwardRef<
|
|
HTMLLIElement,
|
|
React.ComponentProps<"li">
|
|
>(({ className, ...props }, ref) => (
|
|
<li ref={ref} className={cn("", className)} {...props} />
|
|
))
|
|
PaginationItem.displayName = "PaginationItem"
|
|
|
|
type PaginationLinkProps = {
|
|
isActive?: boolean
|
|
} & Pick<ButtonProps, "size"> &
|
|
React.ComponentProps<"a">
|
|
|
|
const PaginationLink = ({
|
|
className,
|
|
isActive,
|
|
size = "icon",
|
|
...props
|
|
}: PaginationLinkProps) => (
|
|
<a
|
|
aria-current={isActive ? "page" : undefined}
|
|
className={cn(
|
|
buttonVariants({
|
|
variant: isActive ? "outline" : "ghost",
|
|
size,
|
|
}),
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
PaginationLink.displayName = "PaginationLink"
|
|
|
|
const PaginationPrevious = ({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
<PaginationLink
|
|
aria-label="上一页"
|
|
size="default"
|
|
className={cn("gap-1 pl-2.5", className)}
|
|
{...props}
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
<span>上一页</span>
|
|
</PaginationLink>
|
|
)
|
|
PaginationPrevious.displayName = "PaginationPrevious"
|
|
|
|
const PaginationNext = ({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
<PaginationLink
|
|
aria-label="下一页"
|
|
size="default"
|
|
className={cn("gap-1 pr-2.5", className)}
|
|
{...props}
|
|
>
|
|
<span>下一页</span>
|
|
<ChevronRight className="h-4 w-4" />
|
|
</PaginationLink>
|
|
)
|
|
PaginationNext.displayName = "PaginationNext"
|
|
|
|
const PaginationEllipsis = ({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<"span">) => (
|
|
<span
|
|
aria-hidden
|
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
|
{...props}
|
|
>
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
<span className="sr-only">更多页面</span>
|
|
</span>
|
|
)
|
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
|
|
|
interface DataTablePaginationProps {
|
|
pageIndex: number
|
|
pageSize: number
|
|
pageCount: number
|
|
onPageChange: (page: number) => void
|
|
}
|
|
|
|
const DataTablePagination: React.FC<DataTablePaginationProps> = ({
|
|
pageIndex,
|
|
pageCount,
|
|
onPageChange,
|
|
}) => {
|
|
const renderPageNumbers = () => {
|
|
const pages = [];
|
|
const maxVisiblePages = 5;
|
|
const halfMaxVisiblePages = Math.floor(maxVisiblePages / 2);
|
|
|
|
let startPage = Math.max(1, pageIndex - halfMaxVisiblePages);
|
|
let endPage = Math.min(pageCount, startPage + maxVisiblePages - 1);
|
|
|
|
if (endPage - startPage + 1 < maxVisiblePages) {
|
|
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
|
}
|
|
|
|
// 添加第一页
|
|
if (startPage > 1) {
|
|
pages.push(
|
|
<PaginationItem key={1}>
|
|
<PaginationLink onClick={() => onPageChange(1)}>1</PaginationLink>
|
|
</PaginationItem>
|
|
);
|
|
if (startPage > 2) {
|
|
pages.push(
|
|
<PaginationItem key="start-ellipsis">
|
|
<PaginationEllipsis />
|
|
</PaginationItem>
|
|
);
|
|
}
|
|
}
|
|
|
|
// 添加中间的页码
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
pages.push(
|
|
<PaginationItem key={i}>
|
|
<PaginationLink
|
|
isActive={i === pageIndex}
|
|
onClick={() => onPageChange(i)}
|
|
>
|
|
{i}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
);
|
|
}
|
|
|
|
// 添加最后一页
|
|
if (endPage < pageCount) {
|
|
if (endPage < pageCount - 1) {
|
|
pages.push(
|
|
<PaginationItem key="end-ellipsis">
|
|
<PaginationEllipsis />
|
|
</PaginationItem>
|
|
);
|
|
}
|
|
pages.push(
|
|
<PaginationItem key={pageCount}>
|
|
<PaginationLink onClick={() => onPageChange(pageCount)}>
|
|
{pageCount}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
);
|
|
}
|
|
|
|
return pages;
|
|
};
|
|
|
|
return (
|
|
<Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious
|
|
onClick={() => onPageChange(Math.max(1, pageIndex - 1))}
|
|
className={cn(pageIndex <= 1 && "pointer-events-none opacity-50")}
|
|
/>
|
|
</PaginationItem>
|
|
{renderPageNumbers()}
|
|
<PaginationItem>
|
|
<PaginationNext
|
|
onClick={() => onPageChange(Math.min(pageCount, pageIndex + 1))}
|
|
className={cn(pageIndex >= pageCount && "pointer-events-none opacity-50")}
|
|
/>
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination>
|
|
);
|
|
};
|
|
|
|
export {
|
|
Pagination,
|
|
PaginationContent,
|
|
PaginationLink,
|
|
PaginationItem,
|
|
PaginationPrevious,
|
|
PaginationNext,
|
|
PaginationEllipsis,
|
|
DataTablePagination,
|
|
}
|