Search

Ctrl + K

Pagination

This document outlines the steps to create Pagination component styled withTailwind CSS and using some npm dependency libraries.

Prerequisites

  • Node.js and npm installed on your machine.
  • Tailwind CSS installed in your project.
  • CVA(class-variance-authority) is a utility for managing CSS class names based on various conditions.
  • clsx is a tiny utility for constructing className strings conditionally.
Step 1: Create Pagination component
1// pagination.component.tsx
2import clsx from "clsx";
3import { ChevronLeftIcon, ChevronRightIcon, EllipsisIcon } from "lucide-react";
4import { useMemo } from "react";
5
6export interface PaginationProps {
7  total: number;
8  current: number;
9  siblings?: number;
10  showControls?: boolean;
11  variant?: "solid" | "separated" | "outline";
12  className?: string;
13  cellClassName?: string;
14  onChange: (val: number) => void;
15}
16
17const Pagination: React.FC<PaginationProps> = ({
18  total,
19  current,
20  siblings = 2,
21  variant = "solid",
22  showControls,
23  className,
24  cellClassName,
25  onChange,
26}) => {
27  const cells = useMemo(() => {
28    const maxSequentialPages = siblings * 2 + 1;
29
30    const paginationList: Array<string | number> = [];
31
32    if (showControls) paginationList.push("<");
33
34    if (total < maxSequentialPages) {
35      Array(total).forEach((_, index) => paginationList.push(index + 1));
36    } else {
37      let start = Math.max(1, current - siblings);
38      let end = Math.min(total, current + siblings);
39
40      if (current <= siblings + 1) end = maxSequentialPages;
41
42      if (current >= total - siblings) start = total - maxSequentialPages + 1;
43
44      if (start > 1) paginationList.push(1);
45      if (start > 2) paginationList.push("...");
46
47      for (let i = start; i <= end; i++) paginationList.push(i);
48
49      if (end < total - 1) paginationList.push("...");
50      if (end < total) paginationList.push(total);
51    }
52
53    if (showControls) paginationList.push(">");
54
55    return paginationList;
56  }, [siblings, showControls, current, total]);
57
58  const onPrev = () => {
59    if (current <= 1) return;
60    onChange(current - 1);
61  };
62
63  const onNext = () => {
64    if (current >= total) return;
65    onChange(current + 1);
66  };
67
68  const handleClick = (item: string | number) => {
69    if (typeof item === "number") {
70      onChange(item);
71    } else if (item === "<") {
72      onPrev();
73    } else if (item === ">") {
74      onNext();
75    }
76  };
77
78  return (
79    <div
80      className={clsx(
81        "flex h-10 w-fit",
82        {
83          "rounded-lg bg-neutral-100 dark:bg-neutral-700": variant === "solid",
84          "gap-2": variant === "separated",
85        },
86        className,
87      )}
88    >
89      {cells.map((item, index) => (
90        <div
91          key={index}
92          className={clsx(
93            "flex min-w-10 items-center justify-center px-1",
94            { "cursor-pointer": item !== "..." },
95            {
96              "border-l-[1px] border-neutral-200 dark:border-neutral-600":
97                variant === "solid" &&
98                index !== 0 &&
99                item !== current &&
100                item !== current + 1,
101            },
102            {
103              "rounded-lg bg-blue-500 text-white": item === current,
104              "rounded-lg bg-neutral-100 text-black dark:bg-neutral-700 dark:text-neutral-100":
105                variant === "separated" && item !== current,
106            },
107            cellClassName,
108          )}
109          onClick={() => handleClick(item)}
110        >
111          {typeof item === "number" ? (
112            <span className="select-none font-medium">{item}</span>
113          ) : item === "<" ? (
114            <ChevronLeftIcon
115              className={clsx("h-4 w-4", {
116                "text-neutral-300 dark:text-neutral-600": current <= 1,
117              })}
118            />
119          ) : item === ">" ? (
120            <ChevronRightIcon
121              className={clsx("h-4 w-4", {
122                "text-neutral-300 dark:text-neutral-600": current >= total,
123              })}
124            />
125          ) : (
126            <EllipsisIcon className="h-3 w-3" />
127          )}
128        </div>
129      ))}
130    </div>
131  );
132};
133
134export default Pagination;
135