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    console.log(siblings, showControls, current, total);
29    console.log(typeof siblings);
30
31    const maxSequentialPages = siblings * 2 + 1;
32
33    const paginationList: Array<string | number> = [];
34
35    if (showControls) paginationList.push("<");
36
37    if (total < maxSequentialPages) {
38      Array(total).forEach((_, index) => paginationList.push(index + 1));
39    } else {
40      let start = Math.max(1, current - siblings);
41      let end = Math.min(total, current + siblings);
42
43      if (current <= siblings + 1) end = maxSequentialPages;
44
45      if (current >= total - siblings) start = total - maxSequentialPages + 1;
46
47      if (start > 1) paginationList.push(1);
48      if (start > 2) paginationList.push("...");
49
50      for (let i = start; i <= end; i++) paginationList.push(i);
51
52      if (end < total - 1) paginationList.push("...");
53      if (end < total) paginationList.push(total);
54    }
55
56    if (showControls) paginationList.push(">");
57    console.log(paginationList);
58
59    return paginationList;
60  }, [siblings, showControls, current, total]);
61
62  const onPrev = () => {
63    if (current <= 1) return;
64    onChange(current - 1);
65  };
66
67  const onNext = () => {
68    if (current >= total) return;
69    onChange(current + 1);
70  };
71
72  const handleClick = (item: string | number) => {
73    if (typeof item === "number") {
74      onChange(item);
75    } else if (item === "<") {
76      onPrev();
77    } else if (item === ">") {
78      onNext();
79    }
80  };
81
82  return (
83    <div
84      className={clsx(
85        "flex h-10 w-fit",
86        {
87          "rounded-lg bg-neutral-100 dark:bg-neutral-700": variant === "solid",
88          "gap-2": variant === "separated",
89        },
90        className,
91      )}
92    >
93      {cells.map((item, index) => (
94        <div
95          key={index}
96          className={clsx(
97            "flex min-w-10 items-center justify-center px-1",
98            { "cursor-pointer": item !== "..." },
99            {
100              "border-l-[1px] border-neutral-200 dark:border-neutral-600":
101                variant === "solid" &&
102                index !== 0 &&
103                item !== current &&
104                item !== current + 1,
105            },
106            {
107              "rounded-lg bg-blue-500 text-white": item === current,
108              "rounded-lg bg-neutral-100 text-black dark:bg-neutral-700 dark:text-neutral-100":
109                variant === "separated" && item !== current,
110            },
111            cellClassName,
112          )}
113          onClick={() => handleClick(item)}
114        >
115          {typeof item === "number" ? (
116            <span className="select-none font-medium">{item}</span>
117          ) : item === "<" ? (
118            <ChevronLeftIcon
119              className={clsx("h-4 w-4", {
120                "text-neutral-300 dark:text-neutral-600": current <= 1,
121              })}
122            />
123          ) : item === ">" ? (
124            <ChevronRightIcon
125              className={clsx("h-4 w-4", {
126                "text-neutral-300 dark:text-neutral-600": current >= total,
127              })}
128            />
129          ) : (
130            <EllipsisIcon className="h-3 w-3" />
131          )}
132        </div>
133      ))}
134    </div>
135  );
136};
137
138export default Pagination;
139