最近由于在 nextui 和 shadcn/ui 做切换,遇到些问题,由于 shadcn/ui 灵活性高,导致没有 pagination 动态配置功能,所以基于以下链接封装了pagination组件 AwsomePagination。
Implementing Pagination in shadcn UI: A Complete Guide
首先
以上链接的 buildLink
是用的query的自适应,而对于我的需求都是用 params的形式去设计,所以调整为新的函数
tsx
const buildLink = useCallback((newPage: number) => {
return `${parentPath}/${newPage}`;
}, []);
我的component基本运用于服务端组件,所以不需要任何依赖,parentPath则为 分页页面的父路由。
然后
renderPageNumbers也做了一些调整
tsx
const renderPageNumbers = useCallback(() => {
const items: ReactNode[] = [];
if (totalPageCount <= maxVisiblePages) {
for (let i = 1; i <= totalPageCount; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href={buildLink(i)} isActive={page === i}>
{i}
</PaginationLink>
</PaginationItem>
);
}
} else {
items.push(
<PaginationItem key={1}>
<PaginationLink href={buildLink(1)} isActive={page === 1}>
1
</PaginationLink>
</PaginationItem>
);
if (page > 3) {
items.push(
<PaginationItem key="ellipsis-start">
<PaginationLink href={buildLink(page - 2)}>
<NavigationDots type="left" />
</PaginationLink>
</PaginationItem>
);
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPageCount - 1, page + 1);
for (let i = start; i <= end; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href={buildLink(i)} isActive={page === i}>
{i}
</PaginationLink>
</PaginationItem>
);
}
if (page < totalPageCount - 2) {
items.push(
<PaginationItem key="ellipsis-end">
<PaginationLink href={buildLink(page + 2)}>
<NavigationDots type="right" />
</PaginationLink>
</PaginationItem>
);
}
items.push(
<PaginationItem key={totalPageCount}>
<PaginationLink href={buildLink(totalPageCount)} isActive={page === totalPageCount}>
{totalPageCount}
</PaginationLink>
</PaginationItem>
);
}
return items;
}, []);
这里原文章用 pagination的 dots组件展示样式,并没有jump功能,这里实现了 hover 变 jump 功能。指定 buildLink 去 实现对应跳转
tsx
export function NavigationDots({ type }: { type: "left" | "right" }) {
const wrapperRef = useRef<HTMLDivElement>(null);
const isHovered = useHover(wrapperRef);
const hoverDom = useMemo(() => {
if (type === "left") {
return <Icon icon={"tabler:arrow-badge-left-filled"} />;
}
return <Icon icon={"tabler:arrow-badge-right-filled"} />;
}, [type]);
return (
<div className="cursor-pointer" ref={wrapperRef}>
{isHovered ? hoverDom : <Icon icon={"tabler:dots"} />}
</div>
);
}
这里用到的是 iconify 和 ahooks 去实现 ,hover 图标切换,点击 jump 对应页面
也就是这样用:
tsx
<PaginationItem key="ellipsis-end">
<PaginationLink href={buildLink(page + 2)}>
<NavigationDots type="right" />
</PaginationLink>
</PaginationItem>
最后
使用:
tsx
<AwsomePagination parentPath="/home" pageSize={6} page={1} totalCount={total} />
源码:
tsx
"use client";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useHover } from "ahooks";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { type ReactNode, useCallback, useMemo, useRef } from "react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from "../ui/pagination";
export interface AwsomePaginationProps {
totalCount: number;
pageSize: number;
page: number;
maxVisiblePages?: number;
parentPath: string;
}
export function NavigationDots({ type }: { type: "left" | "right" }) {
const wrapperRef = useRef<HTMLDivElement>(null);
const isHovered = useHover(wrapperRef);
const hoverDom = useMemo(() => {
if (type === "left") {
return <Icon icon={"tabler:arrow-badge-left-filled"} />;
}
return <Icon icon={"tabler:arrow-badge-right-filled"} />;
}, [type]);
return (
<div className="cursor-pointer" ref={wrapperRef}>
{isHovered ? hoverDom : <Icon icon={"tabler:dots"} />}
</div>
);
}
export function AwsomePagination({
pageSize,
totalCount,
page,
maxVisiblePages = 5,
parentPath
}: AwsomePaginationProps) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const totalPageCount = Math.ceil(totalCount / pageSize);
const buildLink = useCallback((newPage: number) => {
return `${parentPath}/${newPage}`;
}, []);
const renderPageNumbers = useCallback(() => {
const items: ReactNode[] = [];
if (totalPageCount <= maxVisiblePages) {
for (let i = 1; i <= totalPageCount; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href={buildLink(i)} isActive={page === i}>
{i}
</PaginationLink>
</PaginationItem>
);
}
} else {
items.push(
<PaginationItem key={1}>
<PaginationLink href={buildLink(1)} isActive={page === 1}>
1
</PaginationLink>
</PaginationItem>
);
if (page > 3) {
items.push(
<PaginationItem key="ellipsis-start">
<PaginationLink href={buildLink(page - 2)}>
<NavigationDots type="left" />
</PaginationLink>
</PaginationItem>
);
}
const start = Math.max(2, page - 1);
const end = Math.min(totalPageCount - 1, page + 1);
for (let i = start; i <= end; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href={buildLink(i)} isActive={page === i}>
{i}
</PaginationLink>
</PaginationItem>
);
}
if (page < totalPageCount - 2) {
items.push(
<PaginationItem key="ellipsis-end">
<PaginationLink href={buildLink(page + 2)}>
<NavigationDots type="right" />
</PaginationLink>
</PaginationItem>
);
}
items.push(
<PaginationItem key={totalPageCount}>
<PaginationLink href={buildLink(totalPageCount)} isActive={page === totalPageCount}>
{totalPageCount}
</PaginationLink>
</PaginationItem>
);
}
return items;
}, []);
return (
<div className="flex flex-col md:flex-row h-9 overflow-y-hidden items-center gap-3 w-full">
<Pagination>
<PaginationContent className="max-sm:gap-0">
<PaginationItem>
<PaginationPrevious
href={buildLink(Math.max(page - 1, 1))}
aria-disabled={page === 1}
tabIndex={page === 1 ? -1 : undefined}
className={page === 1 ? "pointer-events-none opacity-50" : undefined}
/>
</PaginationItem>
{renderPageNumbers()}
<PaginationItem>
<PaginationNext
href={buildLink(Math.min(page + 1, totalPageCount))}
aria-disabled={page === totalPageCount}
tabIndex={page === totalPageCount ? -1 : undefined}
className={page === totalPageCount ? "pointer-events-none opacity-50" : undefined}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}