From 35e5de455a71b708d63a30cf13b9112c3dffd00f Mon Sep 17 00:00:00 2001 From: Govind Gs Date: Thu, 28 May 2026 02:43:51 +0530 Subject: [PATCH 1/2] feat(Paging): extract paging into standalone component - Create Paging component from ReadOnlyGrid inline paging UI - Refactor ReadOnlyGrid to use the new Paging component - Add stories with autodocs, args-based controls, and interactive render Closes #108 --- src/components/Paging/Paging.stories.tsx | 101 +++++++++++++++++++ src/components/Paging/Paging.tsx | 94 +++++++++++++++++ src/components/Paging/index.ts | 2 + src/components/ReadOnlyGrid/ReadOnlyGrid.tsx | 69 ++----------- src/components/index.ts | 3 + 5 files changed, 211 insertions(+), 58 deletions(-) create mode 100644 src/components/Paging/Paging.stories.tsx create mode 100644 src/components/Paging/Paging.tsx create mode 100644 src/components/Paging/index.ts diff --git a/src/components/Paging/Paging.stories.tsx b/src/components/Paging/Paging.stories.tsx new file mode 100644 index 0000000..a0cde97 --- /dev/null +++ b/src/components/Paging/Paging.stories.tsx @@ -0,0 +1,101 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { fn } from 'storybook/test' +import { useState } from 'react' +import { Paging } from './Paging' + +const meta = { + title: 'Components/Paging', + component: Paging, + tags: ['autodocs'], + parameters: { layout: 'padded' }, + argTypes: { + pagingControls: { + control: 'select', + options: ['STANDARD', 'ROW_COUNT'], + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Standard: Story = { + args: { + startIndex: 1, + endIndex: 10, + totalCount: 50, + currentPage: 1, + totalPages: 5, + pagingControls: 'STANDARD', + onPageChange: fn(), + }, + render: (args) => { + const [currentPage, setCurrentPage] = useState(args.currentPage) + const pageSize = args.endIndex - args.startIndex + 1 + const startIndex = (currentPage - 1) * pageSize + 1 + const endIndex = Math.min(currentPage * pageSize, args.totalCount) + return ( + + ) + }, +} + +export const RowCount: Story = { + args: { + startIndex: 1, + endIndex: 10, + totalCount: 50, + currentPage: 1, + totalPages: 5, + pagingControls: 'ROW_COUNT', + onPageChange: fn(), + }, + render: (args) => { + const [currentPage, setCurrentPage] = useState(args.currentPage) + const pageSize = args.endIndex - args.startIndex + 1 + const startIndex = (currentPage - 1) * pageSize + 1 + const endIndex = Math.min(currentPage * pageSize, args.totalCount) + return ( + + ) + }, +} + +export const TwoPages: Story = { + args: { + startIndex: 1, + endIndex: 10, + totalCount: 15, + currentPage: 1, + totalPages: 2, + pagingControls: 'ROW_COUNT', + onPageChange: fn(), + }, + render: (args) => { + const [currentPage, setCurrentPage] = useState(args.currentPage) + const pageSize = args.endIndex - args.startIndex + 1 + const startIndex = (currentPage - 1) * pageSize + 1 + const endIndex = Math.min(currentPage * pageSize, args.totalCount) + return ( + + ) + }, +} diff --git a/src/components/Paging/Paging.tsx b/src/components/Paging/Paging.tsx new file mode 100644 index 0000000..eb3854a --- /dev/null +++ b/src/components/Paging/Paging.tsx @@ -0,0 +1,94 @@ +import * as React from 'react' +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react' + +export interface PagingProps { + /** 1-based index of the first visible item */ + startIndex: number + /** Index of the last visible item on the current page */ + endIndex: number + /** Total number of items (used when pagingControls is "ROW_COUNT") */ + totalCount: number + /** Current page number (1-based) */ + currentPage: number + /** Total number of pages */ + totalPages: number + /** Determines if the paging includes the total row count. "STANDARD" hides total count; "ROW_COUNT" shows total count and first/last controls. */ + pagingControls?: 'STANDARD' | 'ROW_COUNT' + /** Callback when page changes */ + onPageChange: (page: number) => void + /** Whether the component is displayed */ + showWhen?: boolean +} + +export const Paging: React.FC = ({ + startIndex, + endIndex, + totalCount, + currentPage, + totalPages, + pagingControls = 'STANDARD', + onPageChange, + showWhen = true, +}) => { + if (!showWhen) return null + if (totalPages <= 1) return null + + const hasPreviousPage = currentPage > 1 + const hasNextPage = currentPage < totalPages + + return ( +
+ {pagingControls === 'ROW_COUNT' && totalPages >= 3 && ( + + )} + + {pagingControls === 'ROW_COUNT' ? ( + + {startIndex} – {endIndex} + {' '}of {totalCount} + + ) : ( + + {startIndex} – {endIndex} + {' '}of many + + )} + + {pagingControls === 'ROW_COUNT' && totalPages >= 3 && ( + + )} +
+ ) +} diff --git a/src/components/Paging/index.ts b/src/components/Paging/index.ts new file mode 100644 index 0000000..a3c9c93 --- /dev/null +++ b/src/components/Paging/index.ts @@ -0,0 +1,2 @@ +export { Paging } from './Paging' +export type { PagingProps } from './Paging' diff --git a/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx b/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx index 1008bce..5e8b8d2 100644 --- a/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx +++ b/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, MoveUp, MoveDown } from "lucide-react"; +import { MoveUp, MoveDown } from "lucide-react"; +import { Paging } from "../Paging"; import { isPaletteColor, resolveColorClass } from '../../utils/colorResolver' import { FieldWrapper } from "../shared/FieldWrapper"; import { GridColumn, type GridColumnProps } from "./GridColumn"; @@ -283,8 +284,6 @@ export const ReadOnlyGrid: React.FC = ({ const totalPages = Math.ceil(sortedRows.length / pageSize); const startIndex = (currentPage - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize, sortedRows.length); - const hasPreviousPage = currentPage > 1; - const hasNextPage = currentPage < totalPages; // Slice sorted data for current page const pageRows = sortedRows.slice(startIndex, endIndex); @@ -503,61 +502,15 @@ export const ReadOnlyGrid: React.FC = ({ ) : ( renderTable() )} - {totalPages > 1 && ( -
- {pagingControls === "ROW_COUNT" && totalPages >= 3 && ( - - )} - - {pagingControls === "ROW_COUNT" ? ( - - {startIndex + 1} – {endIndex} - {" "}of {sortedRows.length} - - ) : ( - - {startIndex + 1} – {endIndex} - {" "}of many - - )} - - {pagingControls === "ROW_COUNT" && totalPages >= 3 && ( - - )} -
- )} + )} diff --git a/src/components/index.ts b/src/components/index.ts index d515025..77968fd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -70,6 +70,9 @@ export * from './SideNavAdmin' // ReadOnlyGrid components export * from './ReadOnlyGrid' +// Paging components +export * from './Paging' + // SiteNav component export * from './SiteNav' From 736e09e77c10b45b14c734df83c292e94f6aadbf Mon Sep 17 00:00:00 2001 From: Govind Gs Date: Thu, 4 Jun 2026 13:27:57 +0530 Subject: [PATCH 2/2] refactor(Paging): simplify API to derive values internally - Remove startIndex, endIndex, totalPages from public props - Add pageSize prop; compute totalPages, startIndex, endIndex internally - Update ReadOnlyGrid to pass simplified props - Simplify stories to match new API Addresses PR #128 review feedback --- src/components/Paging/Paging.stories.tsx | 27 +++----------------- src/components/Paging/Paging.tsx | 24 ++++++++--------- src/components/ReadOnlyGrid/ReadOnlyGrid.tsx | 4 +-- 3 files changed, 15 insertions(+), 40 deletions(-) diff --git a/src/components/Paging/Paging.stories.tsx b/src/components/Paging/Paging.stories.tsx index a0cde97..20045ee 100644 --- a/src/components/Paging/Paging.stories.tsx +++ b/src/components/Paging/Paging.stories.tsx @@ -21,24 +21,17 @@ type Story = StoryObj export const Standard: Story = { args: { - startIndex: 1, - endIndex: 10, totalCount: 50, + pageSize: 10, currentPage: 1, - totalPages: 5, pagingControls: 'STANDARD', onPageChange: fn(), }, render: (args) => { const [currentPage, setCurrentPage] = useState(args.currentPage) - const pageSize = args.endIndex - args.startIndex + 1 - const startIndex = (currentPage - 1) * pageSize + 1 - const endIndex = Math.min(currentPage * pageSize, args.totalCount) return ( @@ -48,24 +41,17 @@ export const Standard: Story = { export const RowCount: Story = { args: { - startIndex: 1, - endIndex: 10, totalCount: 50, + pageSize: 10, currentPage: 1, - totalPages: 5, pagingControls: 'ROW_COUNT', onPageChange: fn(), }, render: (args) => { const [currentPage, setCurrentPage] = useState(args.currentPage) - const pageSize = args.endIndex - args.startIndex + 1 - const startIndex = (currentPage - 1) * pageSize + 1 - const endIndex = Math.min(currentPage * pageSize, args.totalCount) return ( @@ -75,24 +61,17 @@ export const RowCount: Story = { export const TwoPages: Story = { args: { - startIndex: 1, - endIndex: 10, totalCount: 15, + pageSize: 10, currentPage: 1, - totalPages: 2, pagingControls: 'ROW_COUNT', onPageChange: fn(), }, render: (args) => { const [currentPage, setCurrentPage] = useState(args.currentPage) - const pageSize = args.endIndex - args.startIndex + 1 - const startIndex = (currentPage - 1) * pageSize + 1 - const endIndex = Math.min(currentPage * pageSize, args.totalCount) return ( diff --git a/src/components/Paging/Paging.tsx b/src/components/Paging/Paging.tsx index eb3854a..e5c27d0 100644 --- a/src/components/Paging/Paging.tsx +++ b/src/components/Paging/Paging.tsx @@ -2,37 +2,35 @@ import * as React from 'react' import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react' export interface PagingProps { - /** 1-based index of the first visible item */ - startIndex: number - /** Index of the last visible item on the current page */ - endIndex: number - /** Total number of items (used when pagingControls is "ROW_COUNT") */ + /** Total number of items */ totalCount: number + /** Number of items per page */ + pageSize: number /** Current page number (1-based) */ currentPage: number - /** Total number of pages */ - totalPages: number - /** Determines if the paging includes the total row count. "STANDARD" hides total count; "ROW_COUNT" shows total count and first/last controls. */ - pagingControls?: 'STANDARD' | 'ROW_COUNT' /** Callback when page changes */ onPageChange: (page: number) => void + /** Determines if the paging includes the total row count. "STANDARD" hides total count; "ROW_COUNT" shows total count and first/last controls. */ + pagingControls?: 'STANDARD' | 'ROW_COUNT' /** Whether the component is displayed */ showWhen?: boolean } export const Paging: React.FC = ({ - startIndex, - endIndex, totalCount, + pageSize, currentPage, - totalPages, - pagingControls = 'STANDARD', onPageChange, + pagingControls = 'STANDARD', showWhen = true, }) => { if (!showWhen) return null + + const totalPages = Math.ceil(totalCount / pageSize) if (totalPages <= 1) return null + const startIndex = (currentPage - 1) * pageSize + 1 + const endIndex = Math.min(currentPage * pageSize, totalCount) const hasPreviousPage = currentPage > 1 const hasNextPage = currentPage < totalPages diff --git a/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx b/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx index 5e8b8d2..91b168d 100644 --- a/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx +++ b/src/components/ReadOnlyGrid/ReadOnlyGrid.tsx @@ -503,11 +503,9 @@ export const ReadOnlyGrid: React.FC = ({ renderTable() )}