Skip to content

Commit

Permalink
Merge branch 'feat/paginated-table-components' into alpha
Browse files Browse the repository at this point in the history
  • Loading branch information
mguellsegarra committed Feb 6, 2025
2 parents e53f0b4 + 5c17b3c commit 10dde25
Show file tree
Hide file tree
Showing 12 changed files with 509 additions and 3 deletions.
191 changes: 191 additions & 0 deletions src/components/ui/PaginationHeader/PaginationHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { PaginationHeader } from "./PaginationHeader";

export default {
title: "Components/UI/PaginationHeader",
component: PaginationHeader,
parameters: {
docs: {
description: {
component:
"A pagination component that combines Ant Design Pagination with selection summary and controls.",
},
},
},
} as ComponentMeta<typeof PaginationHeader>;

const Template: ComponentStory<typeof PaginationHeader> = (args) => {
return <PaginationHeader {...args} />;
};

// Common handlers for all stories
const commonHandlers = {
onRequestPageChange: (page: number, pageSize?: number) => {
console.log(`Page changed to ${page} with size ${pageSize}`);
},
onPageSizeChange: (pageSize: number) => {
console.log(`Page size changed to ${pageSize}`);
},
onSelectAllGlobalRecords: async () => {
console.log("Select all records clicked");
await new Promise((resolve) => setTimeout(resolve, 1000));
},
};

export const NoResults = Template.bind({});
NoResults.args = {
initialPage: 1,
initialPageSize: 10,
total: 0,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
NoResults.parameters = {
docs: {
description: {
story: "Shows the pagination state when there are no results.",
},
},
};

export const SinglePage = Template.bind({});
SinglePage.args = {
initialPage: 1,
initialPageSize: 10,
total: 5,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
SinglePage.parameters = {
docs: {
description: {
story: "Shows the pagination when all items fit in a single page.",
},
},
};

export const MultiplePages = Template.bind({});
MultiplePages.args = {
initialPage: 2,
initialPageSize: 10,
total: 25,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
MultiplePages.parameters = {
docs: {
description: {
story: "Shows pagination with multiple pages, currently on page 2.",
},
},
};

export const LargeDataset = Template.bind({});
LargeDataset.args = {
initialPage: 5,
initialPageSize: 20,
total: 1000,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
LargeDataset.parameters = {
docs: {
description: {
story: "Shows pagination with a large dataset and larger page size.",
},
},
};

export const WithPartialPageSelected = Template.bind({});
WithPartialPageSelected.args = {
initialPage: 1,
initialPageSize: 10,
total: 100,
currentPageSelectedCount: 10,
totalSelectedCount: 10,
...commonHandlers,
};
WithPartialPageSelected.parameters = {
docs: {
description: {
story:
"Shows when current page is fully selected but there are more records available to select globally.",
},
},
};

export const WithAllPagesSelected = Template.bind({});
WithAllPagesSelected.args = {
initialPage: 1,
initialPageSize: 10,
total: 100,
currentPageSelectedCount: 10,
totalSelectedCount: 100,
...commonHandlers,
};
WithAllPagesSelected.parameters = {
docs: {
description: {
story: "Shows when all records across all pages are selected.",
},
},
};

export const CustomPageSize = Template.bind({});
CustomPageSize.args = {
initialPage: 1,
initialPageSize: 50,
total: 200,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
CustomPageSize.parameters = {
docs: {
description: {
story: "Shows pagination with a custom page size of 50 items.",
},
},
};

export const LastPage = Template.bind({});
LastPage.args = {
initialPage: 10,
initialPageSize: 10,
total: 100,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
...commonHandlers,
};
LastPage.parameters = {
docs: {
description: {
story: "Shows pagination when viewing the last page of results.",
},
},
};

export const WithoutSelectionControls = Template.bind({});
WithoutSelectionControls.args = {
initialPage: 1,
initialPageSize: 10,
total: 100,
currentPageSelectedCount: 0,
totalSelectedCount: 0,
onRequestPageChange: commonHandlers.onRequestPageChange,
onPageSizeChange: commonHandlers.onPageSizeChange,
// Omitting onSelectAllGlobalRecords to hide selection controls
};
WithoutSelectionControls.parameters = {
docs: {
description: {
story:
"Shows pagination without the selection controls by omitting the onSelectAllGlobalRecords handler.",
},
},
};
116 changes: 116 additions & 0 deletions src/components/ui/PaginationHeader/PaginationHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useLocale } from "@/context";
import { Col, Pagination, Row } from "antd";
import type { PaginationProps } from "antd";
import { useMemo, useState, useCallback, memo } from "react";
import { SelectAllRecordsRow } from "../SelectAllRecordsRow/SelectAllRecordsRow";
import type { PaginationHeaderProps } from "./PaginationHeader.types";
import type { SelectAllRecordsRowProps } from "../SelectAllRecordsRow/SelectAllRecordsRow.types";

const PaginationHeaderComponent = (props: PaginationHeaderProps) => {
const {
total,
initialPage,
initialPageSize,
currentPageSelectedCount,
totalSelectedCount,
onRequestPageChange,
onPageSizeChange,
onSelectAllGlobalRecords,
} = props;

const { t } = useLocale();

const [page, setPage] = useState(initialPage);
const [pageSize, setPageSize] = useState(initialPageSize);

const from = useMemo(() => (page - 1) * pageSize + 1, [page, pageSize]);
const to = useMemo(
() => Math.min(page * pageSize, total),
[page, pageSize, total],
);

const handlePageChange = useCallback(
(newPage: number, newPageSize?: number) => {
setPage(newPage);
if (newPageSize !== undefined) {
setPageSize(newPageSize);
}
onRequestPageChange(newPage, newPageSize);
},
[onRequestPageChange],
);

const handlePageSizeChange = useCallback(
(newPageSize: number, newPage: number) => {
setPageSize(newPageSize);
setPage(newPage);
onPageSizeChange(newPageSize);
onRequestPageChange(newPage, newPageSize);
},
[onPageSizeChange, onRequestPageChange],
);

const summary = useMemo(() => {
if (total === undefined) return null;
if (total === 0) return t("no_results");

return t("summary")
.replace("{from}", from?.toString())
.replace("{to}", to?.toString())
.replace("{total}", total?.toString());
}, [total, from, to, t]);

const paginationProps: PaginationProps = useMemo(
() => ({
total,
pageSize,
current: page,
onChange: handlePageChange,
showSizeChanger: true,
onShowSizeChange: handlePageSizeChange,
showLessItems: true,
locale: {
items_per_page: t("items_per_page"),
},
}),
[total, pageSize, page, handlePageChange, handlePageSizeChange, t],
);

const selectAllRecordsProps: SelectAllRecordsRowProps = useMemo(
() => ({
currentPageSelectedCount,
currentPageSize: pageSize,
totalRecordsCount: total,
totalSelectedCount,
onSelectAllRecords: onSelectAllGlobalRecords!,
}),
[
currentPageSelectedCount,
pageSize,
total,
totalSelectedCount,
onSelectAllGlobalRecords,
],
);

const hasSelectionFeature = onSelectAllGlobalRecords !== undefined;
const columnSpan = hasSelectionFeature ? 8 : 12;

return (
<Row align="bottom" className="pb-4" wrap={false}>
<Col span={columnSpan}>
<Pagination {...paginationProps} />
</Col>
{hasSelectionFeature && (
<Col span={8} className="text-center">
<SelectAllRecordsRow {...selectAllRecordsProps} />
</Col>
)}
<Col span={columnSpan} className="text-right">
{summary}
</Col>
</Row>
);
};

export const PaginationHeader = memo(PaginationHeaderComponent);
18 changes: 18 additions & 0 deletions src/components/ui/PaginationHeader/PaginationHeader.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type PaginationHeaderProps = {
/** Total number of records across all pages */
total: number;
/** Initial page number */
initialPage: number;
/** Initial number of items per page */
initialPageSize: number;
/** Number of selected records in the current page */
currentPageSelectedCount: number;
/** Total number of selected records across all pages */
totalSelectedCount: number;
/** Callback when page or page size changes */
onRequestPageChange: (page: number, pageSize?: number) => void;
/** Callback when page size changes */
onPageSizeChange: (pageSize: number) => void;
/** Optional callback to select all records across all pages */
onSelectAllGlobalRecords?: () => Promise<void>;
};
2 changes: 2 additions & 0 deletions src/components/ui/PaginationHeader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PaginationHeader } from "./PaginationHeader";
export type { PaginationHeaderProps } from "./PaginationHeader.types";
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { SelectAllRecordsRow } from "./SelectAllRecordsRow";

export default {
title: "Components/UI/SelectAllRecordsRow",
component: SelectAllRecordsRow,
parameters: {
docs: {
description: {
component:
"A component that appears when all records in the current page are selected, offering the option to select all records across all pages.",
},
},
},
} as ComponentMeta<typeof SelectAllRecordsRow>;

const Template: ComponentStory<typeof SelectAllRecordsRow> = (args) => {
return <SelectAllRecordsRow {...args} />;
};

export const CurrentPageSelected = Template.bind({});
CurrentPageSelected.args = {
currentPageSelectedCount: 10, // All 10 records in current page are selected
currentPageTotalCount: 10, // Current page has 10 records
totalRecordsCount: 100, // We have 100 records in total
totalSelectedCount: 10, // Only the current page (10 records) are selected
onSelectAllRecords: async () => {
console.log("Select all records clicked");
await new Promise((resolve) => setTimeout(resolve, 1000));
},
};
CurrentPageSelected.parameters = {
docs: {
description: {
story:
"Shows when all records in the current page are selected, offering the option to select all records across all pages.",
},
},
};

export const AllPagesSelected = Template.bind({});
AllPagesSelected.args = {
currentPageSelectedCount: 10, // All 10 records in current page are selected
currentPageTotalCount: 10, // Current page has 10 records
totalRecordsCount: 100, // We have 100 records in total
totalSelectedCount: 100, // All records across all pages are selected
onSelectAllRecords: async () => {
console.log("Select all records clicked");
await new Promise((resolve) => setTimeout(resolve, 1000));
},
};
AllPagesSelected.parameters = {
docs: {
description: {
story:
"Shows when all records across all pages are selected, displaying the total count.",
},
},
};
Loading

0 comments on commit 10dde25

Please sign in to comment.