diff --git a/apps/web/src/app/(overview)/history/page.tsx b/apps/web/src/app/(overview)/history/page.tsx
index 83111b4..82fb5e6 100644
--- a/apps/web/src/app/(overview)/history/page.tsx
+++ b/apps/web/src/app/(overview)/history/page.tsx
@@ -7,7 +7,6 @@ import { useWallet } from "@/providers/StellarWalletProvider";
import DashboardLayout from "@/components/layouts/DashboardLayout";
import { ConnectWalletPrompt } from "@/components/layouts/ProtectedRoute";
import HistoryTable from "@/components/modules/history/HistoryTable";
-import HistoryTableSkeleton from "@/components/modules/history/HistoryTableSkeleton";
import { columns } from "@/components/modules/history/columns";
import { HistoryRecord } from "@/services/types";
import AppSelect from "@/components/molecules/AppSelect";
@@ -250,8 +249,10 @@ const HistoryPage = () => {
page={page}
limit={limit}
totalCount={filteredData.length}
- onPageChange={setPage}
- onLimitChange={setLimit}
+ onPageChange={(nextPage) => updateHistoryParams({ page: nextPage })}
+ onLimitChange={(nextLimit) =>
+ updateHistoryParams({ page: 1, limit: nextLimit })
+ }
onExport={handleExportCSV}
isLoading={isLoading}
/>
diff --git a/apps/web/src/components/modules/history/HistoryTable.tsx b/apps/web/src/components/modules/history/HistoryTable.tsx
index 9e313af..95cdb18 100644
--- a/apps/web/src/components/modules/history/HistoryTable.tsx
+++ b/apps/web/src/components/modules/history/HistoryTable.tsx
@@ -19,20 +19,12 @@ import {
TableHeader,
} from "@/components/ui/table";
-import {
- Pagination,
- PaginationContent,
- PaginationItem,
- PaginationLink,
- PaginationNext,
- PaginationPrevious,
-} from "@/components/ui/pagination";
-
import { HistoryRecord } from "@/services/types";
import AppSelect from "@/components/molecules/AppSelect";
import { validPageLimits } from "@/lib/constants";
import { Download } from "lucide-react";
import { Button } from "@/components/ui/button";
+import SlidingPagination from "@/components/molecules/SlidingPagination";
import ActionsCell from "./ActionsCell";
import { format } from "date-fns";
@@ -335,44 +327,12 @@ const HistoryTable = ({
Showing {data.length} of {totalCount} transactions
-
-
-
- {
- e.preventDefault();
- if (page > 1) onPageChange(page - 1);
- }}
- className={page <= 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
- />
-
- {Array.from({ length: Math.min(5, pageCount) }).map((_, i) => (
-
- {
- e.preventDefault();
- onPageChange(i + 1);
- }}
- isActive={page === i + 1}
- >
- {i + 1}
-
-
- ))}
-
- {
- e.preventDefault();
- if (page < pageCount) onPageChange(page + 1);
- }}
- className={page >= pageCount ? "pointer-events-none opacity-50" : "cursor-pointer"}
- />
-
-
-
+
);
diff --git a/apps/web/src/components/modules/payment-stream/StreamsTable.tsx b/apps/web/src/components/modules/payment-stream/StreamsTable.tsx
index 8c85d3d..ede22df 100644
--- a/apps/web/src/components/modules/payment-stream/StreamsTable.tsx
+++ b/apps/web/src/components/modules/payment-stream/StreamsTable.tsx
@@ -23,19 +23,11 @@ import {
TableHeader,
} from "@/components/ui/table";
-import {
- Pagination,
- PaginationNext,
- PaginationLink,
- PaginationContent,
- PaginationPrevious,
- PaginationEllipsis,
-} from "@/components/ui/pagination";
-
import { streamColumns } from "./streamColumns";
import { StreamRecord } from "@/lib/validations";
import { validPageLimits } from "@/lib/constants";
import AppSelect from "@/components/molecules/AppSelect";
+import SlidingPagination from "@/components/molecules/SlidingPagination";
interface StreamsTableProps {
data: StreamRecord[];
@@ -86,9 +78,6 @@ function StreamsTable({
router.push(`?${params.toString()}`);
};
- const canPreviousPage = page > 1;
- const canNextPage = page < pageCount;
-
const pageSize = validPageLimits.map((limit) => ({
label: `${limit} per page`,
value: limit.toString(),
@@ -204,8 +193,8 @@ function StreamsTable({
{/* Pagination */}
{totalCount > 0 && (
-
-
+
+
Page {page} of {pageCount}
-
-
-
- updatePage(page - 1)}
- disabled={!canPreviousPage}
- />
- {Array.from(
- { length: pageCount > 3 ? 3 : pageCount },
- (_, index) => (
- updatePage(index + 1)}
- isActive={page === index + 1}
- >
- {index + 1}
-
- )
- )}
-
- {pageCount > 3 ? (
-
-
- updatePage(pageCount)}
- isActive={page === pageCount}
- >
- {pageCount}
-
-
- ) : null}
-
- updatePage(page + 1)}
- disabled={!canNextPage}
- />
-
-
+
+
+
+
)}
);
diff --git a/apps/web/src/components/molecules/SlidingPagination.tsx b/apps/web/src/components/molecules/SlidingPagination.tsx
new file mode 100644
index 0000000..558346d
--- /dev/null
+++ b/apps/web/src/components/molecules/SlidingPagination.tsx
@@ -0,0 +1,123 @@
+"use client";
+
+import {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import { usePagination } from "@/hooks/use-pagination";
+
+type SlidingPaginationProps = {
+ page: number;
+ pageCount: number;
+ onPageChange: (p: number) => void;
+ className?: string;
+};
+
+const clampPage = (value: number, min: number, max: number) =>
+ Math.min(Math.max(value, min), max);
+
+const SlidingPagination = ({
+ page,
+ pageCount,
+ onPageChange,
+ className,
+}: SlidingPaginationProps) => {
+ const safePageCount = Math.max(1, pageCount);
+ const currentPage = clampPage(page, 1, safePageCount);
+ const items = usePagination({
+ currentPage,
+ pageCount: safePageCount,
+ siblingCount: 2,
+ });
+
+ const navigate = (targetPage: number) => {
+ const nextPage = clampPage(targetPage, 1, safePageCount);
+ if (nextPage !== currentPage) {
+ onPageChange(nextPage);
+ }
+ };
+
+ return (
+
+
+
+ {
+ e.preventDefault();
+ navigate(1);
+ }}
+ >
+ First
+
+
+
+
+ {
+ e.preventDefault();
+ navigate(currentPage - 1);
+ }}
+ />
+
+
+ {items.map((item) => (
+
+ {item.type === "ellipsis" ? (
+
+ ) : (
+ {
+ e.preventDefault();
+ navigate(item.page);
+ }}
+ >
+ {item.page}
+
+ )}
+
+ ))}
+
+
+ {
+ e.preventDefault();
+ navigate(currentPage + 1);
+ }}
+ />
+
+
+
+ {
+ e.preventDefault();
+ navigate(safePageCount);
+ }}
+ >
+ Last
+
+
+
+
+ );
+};
+
+export default SlidingPagination;
diff --git a/apps/web/src/hooks/use-pagination.ts b/apps/web/src/hooks/use-pagination.ts
new file mode 100644
index 0000000..24d6ef9
--- /dev/null
+++ b/apps/web/src/hooks/use-pagination.ts
@@ -0,0 +1,69 @@
+import { useMemo } from "react";
+
+export type PaginationRangeItem =
+ | { type: "page"; page: number }
+ | { type: "ellipsis"; key: "left" | "right" };
+
+type UsePaginationParams = {
+ currentPage: number;
+ pageCount: number;
+ siblingCount?: number;
+};
+
+const range = (start: number, end: number) =>
+ Array.from({ length: end - start + 1 }, (_, index) => start + index);
+
+export const getPaginationRange = ({
+ currentPage,
+ pageCount,
+ siblingCount = 2,
+}: UsePaginationParams): PaginationRangeItem[] => {
+ const safePageCount = Math.max(1, pageCount);
+ const safeCurrentPage = Math.min(Math.max(1, currentPage), safePageCount);
+ const windowSize = siblingCount * 2 + 1;
+
+ if (safePageCount <= 7) {
+ return range(1, safePageCount).map((page) => ({ type: "page", page }));
+ }
+
+ let left = Math.max(2, safeCurrentPage - siblingCount);
+ let right = Math.min(safePageCount - 1, safeCurrentPage + siblingCount);
+
+ while (right - left + 1 < windowSize) {
+ if (left > 2) {
+ left -= 1;
+ continue;
+ }
+
+ if (right < safePageCount - 1) {
+ right += 1;
+ continue;
+ }
+
+ break;
+ }
+
+ const showLeftEllipsis = left > 2;
+ const showRightEllipsis = right < safePageCount - 1;
+
+ const items: PaginationRangeItem[] = [{ type: "page", page: 1 }];
+
+ if (showLeftEllipsis) {
+ items.push({ type: "ellipsis", key: "left" });
+ }
+
+ range(left, right).forEach((page) => {
+ items.push({ type: "page", page });
+ });
+
+ if (showRightEllipsis) {
+ items.push({ type: "ellipsis", key: "right" });
+ }
+
+ items.push({ type: "page", page: safePageCount });
+
+ return items;
+};
+
+export const usePagination = (params: UsePaginationParams) =>
+ useMemo(() => getPaginationRange(params), [params]);