From 5b7ccf0b671e2999b62befc729a3e517a0433728 Mon Sep 17 00:00:00 2001
From: Bertrand Yuan <bert.yuan@outlook.com>
Date: Mon, 15 Dec 2025 23:48:10 +0800
Subject: initial commit -- the front-end prototype

The initial code is base on Anirudh's work.

More to see at:

    https://github.com/techwithanirudh/shadcn-blog

Therefore, the code in this commit is under MIT license.
---
 src/components/numbered-pagination.tsx | 131 +++++++++++++++++++++++++++++++++
 1 file changed, 131 insertions(+)
 create mode 100644 src/components/numbered-pagination.tsx

(limited to 'src/components/numbered-pagination.tsx')

diff --git a/src/components/numbered-pagination.tsx b/src/components/numbered-pagination.tsx
new file mode 100644
index 0000000..cf02ef1
--- /dev/null
+++ b/src/components/numbered-pagination.tsx
@@ -0,0 +1,131 @@
+'use client';
+import { Icons } from '@/components/icons/icons';
+import { buttonVariants } from '@/components/ui/button';
+import {
+  Pagination,
+  PaginationContent,
+  PaginationItem,
+  PaginationLink,
+} from '@/components/ui/pagination';
+import { usePagination } from '@/hooks/use-pagination';
+import { cn } from '@/lib/utils';
+
+type NumberedPaginationProps = {
+  currentPage: number;
+  totalPages: number;
+  paginationItemsToDisplay?: number;
+  onPageChange: (page: number) => void;
+};
+
+function NumberedPagination({
+  currentPage,
+  totalPages,
+  paginationItemsToDisplay = 5,
+  onPageChange,
+}: NumberedPaginationProps) {
+  const { pages, showLeftEllipsis, showRightEllipsis } = usePagination({
+    currentPage,
+    totalPages,
+    paginationItemsToDisplay,
+  });
+
+  const handlePageChange = (page: number) => (e: React.MouseEvent) => {
+    e.preventDefault();
+    if (page >= 1 && page <= totalPages) {
+      onPageChange(page);
+    }
+  };
+
+  return (
+    <Pagination>
+      <PaginationContent className='-space-x-px inline-flex w-full gap-0 rtl:space-x-reverse'>
+        <PaginationItem>
+          <PaginationLink
+            className={cn(
+              buttonVariants({
+                variant: 'ghost',
+              }),
+              'rounded-none shadow-none focus-visible:z-10 aria-disabled:pointer-events-none [&[aria-disabled]>svg]:opacity-50',
+            )}
+            href='#'
+            onClick={handlePageChange(currentPage - 1)}
+            aria-label='Go to previous page'
+            aria-disabled={currentPage === 1}
+          >
+            <Icons.chevronLeft size={16} strokeWidth={2} aria-hidden='true' />
+          </PaginationLink>
+        </PaginationItem>
+
+        <div className='inline-flex w-full justify-center '>
+          {showLeftEllipsis && (
+            <PaginationItem>
+              <PaginationLink
+                className={cn(
+                  buttonVariants({
+                    variant: 'ghost',
+                  }),
+                  'pointer-events-none rounded-none shadow-none',
+                )}
+              >
+                ...
+              </PaginationLink>
+            </PaginationItem>
+          )}
+
+          {pages.map((page) => (
+            <PaginationItem key={page} className='w-max'>
+              <PaginationLink
+                className={cn(
+                  buttonVariants({
+                    variant: page === currentPage ? 'default' : 'ghost',
+                  }),
+                  'rounded-none border-0 shadow-none focus-visible:z-10',
+                  page === currentPage &&
+                    'min-w-full dark:bg-primary dark:hover:bg-primary/90',
+                )}
+                href='#'
+                onClick={handlePageChange(page)}
+                isActive={page === currentPage}
+              >
+                {page}
+              </PaginationLink>
+            </PaginationItem>
+          ))}
+
+          {showRightEllipsis && (
+            <PaginationItem>
+              <PaginationLink
+                className={cn(
+                  buttonVariants({
+                    variant: 'ghost',
+                  }),
+                  'pointer-events-none rounded-none shadow-none',
+                )}
+              >
+                ...
+              </PaginationLink>
+            </PaginationItem>
+          )}
+        </div>
+        <PaginationItem>
+          <PaginationLink
+            className={cn(
+              buttonVariants({
+                variant: 'ghost',
+              }),
+              'rounded-none shadow-none focus-visible:z-10 aria-disabled:pointer-events-none [&[aria-disabled]>svg]:opacity-50',
+            )}
+            href='#'
+            onClick={handlePageChange(currentPage + 1)}
+            aria-label='Go to next page'
+            aria-disabled={currentPage === totalPages}
+          >
+            <Icons.chevronRight size={16} strokeWidth={2} aria-hidden='true' />
+          </PaginationLink>
+        </PaginationItem>
+      </PaginationContent>
+    </Pagination>
+  );
+}
+
+export { NumberedPagination };
-- 
cgit v1.2.3

