Skip to content

feat/TablePagination - Table with Pagination #1353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
95 changes: 95 additions & 0 deletions packages/ui/src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Meta, StoryFn } from "@storybook/react";
import { useState } from "react";
import type { TableProps } from "./Table";
import { Table } from "./Table";

Expand Down Expand Up @@ -86,3 +87,97 @@ const Template: StoryFn<TableProps> = (args) => (

export const DefaultTable = Template.bind({});
DefaultTable.storyName = "Default";

const TPageTemplate: StoryFn<TableProps> = (args) => {
const [pageNo, setPageNo] = useState(1);
const [rowsPerPage] = useState(10);

const handlePageChange = (newPage: number) => setPageNo(newPage);

return (
<>
<Table {...args}>
<Table.Head>
<Table.HeadCell>Product name</Table.HeadCell>
<Table.HeadCell>Color</Table.HeadCell>
<Table.HeadCell>Category</Table.HeadCell>
<Table.HeadCell>Price</Table.HeadCell>
<Table.HeadCell>
<span className="sr-only">Edit</span>
</Table.HeadCell>
</Table.Head>
<Table.Body className="divide-y">
<Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800">
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
{'Apple MacBook Pro 17"'}
</Table.Cell>
<Table.Cell>Sliver</Table.Cell>
<Table.Cell>Laptop</Table.Cell>
<Table.Cell>$2999</Table.Cell>
<Table.Cell>
<a href="/tables" className="font-medium text-cyan-600 hover:underline dark:text-cyan-500">
Edit
</a>
</Table.Cell>
</Table.Row>
<Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800">
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
Microsoft Surface Pro
</Table.Cell>
<Table.Cell>White</Table.Cell>
<Table.Cell>Laptop PC</Table.Cell>
<Table.Cell>$1999</Table.Cell>
<Table.Cell>
<a href="/tables" className="font-medium text-cyan-600 hover:underline dark:text-cyan-500">
Edit
</a>
</Table.Cell>
</Table.Row>
<Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800">
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
Magic Mouse 2
</Table.Cell>
<Table.Cell>Black</Table.Cell>
<Table.Cell>Accessories</Table.Cell>
<Table.Cell>$99</Table.Cell>
<Table.Cell>
<a href="/tables" className="font-medium text-cyan-600 hover:underline dark:text-cyan-500">
Edit
</a>
</Table.Cell>
</Table.Row>
<Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800">
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
Google Pixel Phone
</Table.Cell>
<Table.Cell>Gray</Table.Cell>
<Table.Cell>Phone</Table.Cell>
<Table.Cell>$799</Table.Cell>
<Table.Cell>
<a href="/tables" className="font-medium text-cyan-600 hover:underline dark:text-cyan-500">
Edit
</a>
</Table.Cell>
</Table.Row>
<Table.Row className="bg-white dark:border-gray-700 dark:bg-gray-800">
<Table.Cell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
Apple Watch 5
</Table.Cell>
<Table.Cell>Red</Table.Cell>
<Table.Cell>Wearables</Table.Cell>
<Table.Cell>$999</Table.Cell>
<Table.Cell>
<a href="/tables" className="font-medium text-cyan-600 hover:underline dark:text-cyan-500">
Edit
</a>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
<Table.Pagination count={100} onPageChange={handlePageChange} page={pageNo} rowsPerPage={rowsPerPage} />
</>
);
};

export const PaginationTable = TPageTemplate.bind({});
PaginationTable.storyName = "Pagination";
3 changes: 3 additions & 0 deletions packages/ui/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { TableCell } from "./TableCell";
import { TableContext } from "./TableContext";
import { TableHead, type FlowbiteTableHeadTheme } from "./TableHead";
import { TableHeadCell } from "./TableHeadCell";
import { TablePagination, type FlowbiteTablePaginationTheme } from "./TablePagination";
import { TableRow, type FlowbiteTableRowTheme } from "./TableRow";

export interface FlowbiteTableTheme {
root: FlowbiteTableRootTheme;
head: FlowbiteTableHeadTheme;
row: FlowbiteTableRowTheme;
body: FlowbiteTableBodyTheme;
pagination: FlowbiteTablePaginationTheme;
}

export interface FlowbiteTableRootTheme {
Expand Down Expand Up @@ -56,4 +58,5 @@ export const Table = Object.assign(TableComponent, {
Row: TableRow,
Cell: TableCell,
HeadCell: TableHeadCell,
Pagination: TablePagination,
});
107 changes: 107 additions & 0 deletions packages/ui/src/components/Table/TablePagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client";

import { forwardRef, type ComponentPropsWithRef } from "react";
import { twMerge } from "tailwind-merge";
import { mergeDeep } from "../../helpers/merge-deep";
import { getTheme } from "../../theme-store";
import type { DeepPartial } from "../../types";

export interface FlowbiteTablePaginationTheme {
base: string;
totalPages: {
base: string;
pageRange: string;
outOf: string;
};
page: {
base: string;
previous: string;
pageNo: string;
next: string;
};
}

export interface TablePaginationProps extends ComponentPropsWithRef<"tfoot"> {
count: number;
onPageChange: (newPage: number) => void;
page: number;
rowsPerPage: number;
theme?: DeepPartial<FlowbiteTablePaginationTheme>;
}

export const TablePagination = forwardRef<HTMLDivElement, TablePaginationProps>(
({ count, onPageChange, page, rowsPerPage, className, theme: customTheme = {}, ...props }, ref) => {
const theme = mergeDeep(getTheme().table.pagination, customTheme);

const nPages = Math.ceil(count / rowsPerPage);
const pageNumbers = [...Array(nPages + 1).keys()].slice(1);

const goToPrevPage = (): void => {
if (page !== 1) {
onPageChange(page - 1);
}
};

const goToNextPage = (): void => {
if (page !== nPages) {
onPageChange(page + 1);
}
};

const directPageChange = (customPageNo: number): void => {
if (page !== customPageNo) {
onPageChange(customPageNo);
}
};

const getLabelDisplayedRowsTo = (): number => {
if (count === -1) {
return (page + 1) * rowsPerPage;
}

return rowsPerPage === -1 ? count : Math.min(count, (page + 1) * rowsPerPage);
};

return (
<div className={twMerge(theme.base, className)} aria-label="Table pagination" {...props} ref={ref}>
<span className={theme.totalPages.base}>
Showing{" "}
<span className={theme.totalPages.pageRange}>
{`${count === 0 ? 0 : page * rowsPerPage + 1}-${getLabelDisplayedRowsTo()}`}
</span>
of
<span className={theme.totalPages.outOf}>{count === -1 ? -1 : count}</span>
</span>

<ul className={theme.page.base}>
<button onClick={goToPrevPage} className={theme.page.previous} disabled={page === 1}>
Previous
</button>
{pageNumbers.map((pgNumber, index) => {
return (
<button
onClick={() => {
directPageChange(index + 1);
}}
key={index + 1}
className={twMerge(theme.page.pageNo, index + 1 === page ? "bg-blue-50 text-blue-600" : "")}
>
{pgNumber}
</button>
);
})}
<button
role="button"
onClick={goToNextPage}
className={theme.page.next}
disabled={count !== -1 ? page >= Math.ceil(count / rowsPerPage) - 1 : false}
>
Next
</button>
</ul>
</div>
);
},
);

TablePagination.displayName = "Table.Pagination";
2 changes: 2 additions & 0 deletions packages/ui/src/components/Table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export { TableHeadCell } from "./TableHeadCell";
export type { FlowbiteTableHeadCellTheme, TableHeadCellProps } from "./TableHeadCell";
export { TableRow } from "./TableRow";
export type { FlowbiteTableRowTheme, TableRowProps } from "./TableRow";
export { TablePagination } from "./TablePagination";
export type { FlowbiteTablePaginationTheme, TablePaginationProps } from "./TablePagination";
16 changes: 16 additions & 0 deletions packages/ui/src/components/Table/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ export const tableTheme: FlowbiteTableTheme = createTheme({
hovered: "hover:bg-gray-50 dark:hover:bg-gray-600",
striped: "odd:bg-white even:bg-gray-50 odd:dark:bg-gray-800 even:dark:bg-gray-700",
},
pagination: {
base: "mt-5 flex w-full flex-col flex-wrap items-center justify-between py-4 md:flex-row",
totalPages: {
base: "mb-4 block w-full text-sm font-normal text-gray-500 dark:text-gray-400 md:mb-0 md:inline md:w-auto",
pageRange: "mr-1 font-semibold text-gray-900 dark:text-white",
outOf: "ml-1 font-semibold text-gray-900 dark:text-white",
},
page: {
base: "inline-flex h-8 -space-x-px text-sm rtl:space-x-reverse",
next: "flex h-8 items-center justify-center rounded-e-lg border border-gray-300 bg-white px-3 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white",
pageNo:
"flex h-8 items-center justify-center border border-gray-300 bg-white px-3 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white",
previous:
"ms-0 flex h-8 items-center justify-center rounded-s-lg border border-gray-300 bg-white px-3 leading-tight text-gray-500 hover:bg-gray-100 hover:text-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white",
},
},
});