From d0295ec63b6f3517ea4ccaf9f81c9d36351ff59a Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Thu, 16 Oct 2025 11:27:09 +0530 Subject: [PATCH 1/2] Add Table component and its Storybook stories; update package dependencies --- package-lock.json | 4 + package.json | 4 + .../src/components/table/index.tsx | 103 ++++++++++++ .../src/components/table/table.stories.tsx | 151 ++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 packages/frappe-ui-react/src/components/table/index.tsx create mode 100644 packages/frappe-ui-react/src/components/table/table.stories.tsx diff --git a/package-lock.json b/package-lock.json index 5920106d..7d879e9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,10 @@ "workspaces": [ "packages/*" ], + "dependencies": { + "@rtcamp/frappe-ui-react": "^1.0.0-beta.0", + "classnames": "^2.5.1" + }, "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.22.5", "@babel/preset-env": "^7.22.5", diff --git a/package.json b/package.json index d9110c3d..04860b41 100644 --- a/package.json +++ b/package.json @@ -80,5 +80,9 @@ ], "*.(xml|xml.dist)": "prettier --write", "*.yml": "prettier --write" + }, + "dependencies": { + "@rtcamp/frappe-ui-react": "^1.0.0-beta.0", + "classnames": "^2.5.1" } } diff --git a/packages/frappe-ui-react/src/components/table/index.tsx b/packages/frappe-ui-react/src/components/table/index.tsx new file mode 100644 index 00000000..94801891 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/index.tsx @@ -0,0 +1,103 @@ +import { forwardRef } from "react"; +import classNames from "classnames"; + +const Table = forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); + +const TableHeader = forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); + +const TableBody = forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); + +const TableFooter = forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", className)} + {...props} + /> +)); + +const TableRow = forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); + +const TableHead = forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + + {children} + +); + +export default TableBody; diff --git a/packages/frappe-ui-react/src/components/table/components/tableCaption.tsx b/packages/frappe-ui-react/src/components/table/components/tableCaption.tsx new file mode 100644 index 00000000..c46ff26c --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableCaption.tsx @@ -0,0 +1,13 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableCaption = ({ + className, + children, +}: PropsWithChildren>) => ( + +); + +export default TableCaption; diff --git a/packages/frappe-ui-react/src/components/table/components/tableCell.tsx b/packages/frappe-ui-react/src/components/table/components/tableCell.tsx new file mode 100644 index 00000000..65f9ca37 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableCell.tsx @@ -0,0 +1,18 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableCell = ({ + className, + children, +}: PropsWithChildren>) => ( + +); + +export default TableCell; diff --git a/packages/frappe-ui-react/src/components/table/components/tableFooter.tsx b/packages/frappe-ui-react/src/components/table/components/tableFooter.tsx new file mode 100644 index 00000000..29033c4d --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableFooter.tsx @@ -0,0 +1,18 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableFooter = ({ + className, + children, +}: PropsWithChildren>) => ( + tr]:last:border-b-0", + className + )} + > + {children} + +); + +export default TableFooter; diff --git a/packages/frappe-ui-react/src/components/table/components/tableHead.tsx b/packages/frappe-ui-react/src/components/table/components/tableHead.tsx new file mode 100644 index 00000000..3c85d270 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableHead.tsx @@ -0,0 +1,20 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableHead = ({ + className, + children, + ...props +}: PropsWithChildren>) => ( + +); + +export default TableHead; diff --git a/packages/frappe-ui-react/src/components/table/components/tableHeader.tsx b/packages/frappe-ui-react/src/components/table/components/tableHeader.tsx new file mode 100644 index 00000000..7552af58 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableHeader.tsx @@ -0,0 +1,18 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableHeader = ({ + className, + children, +}: PropsWithChildren>) => ( + + {children} + +); + +export default TableHeader; diff --git a/packages/frappe-ui-react/src/components/table/components/tableRow.tsx b/packages/frappe-ui-react/src/components/table/components/tableRow.tsx new file mode 100644 index 00000000..aacc1f22 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableRow.tsx @@ -0,0 +1,18 @@ +import { PropsWithChildren } from "react"; +import classNames from "classnames"; + +const TableRow = ({ + className, + children, +}: PropsWithChildren>) => ( + + {children} + +); + +export default TableRow; diff --git a/packages/frappe-ui-react/src/components/table/index.tsx b/packages/frappe-ui-react/src/components/table/index.tsx index 94801891..257b1638 100644 --- a/packages/frappe-ui-react/src/components/table/index.tsx +++ b/packages/frappe-ui-react/src/components/table/index.tsx @@ -1,103 +1,128 @@ -import { forwardRef } from "react"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + OnChangeFn, + useReactTable, +} from "@tanstack/react-table"; import classNames from "classnames"; -const Table = forwardRef< - HTMLTableElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-
+)); + +const TableCell = forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); + +const TableCaption = forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/packages/frappe-ui-react/src/components/table/table.stories.tsx b/packages/frappe-ui-react/src/components/table/table.stories.tsx new file mode 100644 index 00000000..d39a0a1b --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/table.stories.tsx @@ -0,0 +1,151 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} from "./index"; + +export default { + title: "Components/Table", + component: Table, + parameters: { + layout: "centered", + }, +} as Meta; + +const Template: StoryObj = { + render: () => ( +
+ + Sample Table Caption + + + Name + Email + Status + + + + + Jane Doe + jane@example.com + Active + + + John Smith + john@example.com + Inactive + + + + + Footer: 2 users + + +
+
+ ), +}; + +export const Basic = { ...Template }; + +export const Advanced = { + render: () => ( +
+ + Team Members & Status + + + + Avatar + + Name + Email + Status + Role + + + + + + Jane + + + Jane Doe + + + jane@example.com + + + + Active + + + Developer + + + + John + + + John Smith + + + john@example.com + + + + Pending + + + Designer + + + + Alex + + + Alex Lee + + + alex@example.com + + + + Inactive + + + Manager + + + + + + Footer: 3 team members + + + +
+
+ ), +}; From 5bcfdbc085f1b2c7846325a322f5bf5a176921b8 Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Fri, 17 Oct 2025 07:19:52 +0530 Subject: [PATCH 2/2] Wrap Tanstack table library around table component for single point of logic --- package-lock.json | 34 +++ packages/frappe-ui-react/package.json | 1 + .../src/components/table/components/index.ts | 7 + .../components/table/components/tableBody.tsx | 17 ++ .../table/components/tableCaption.tsx | 13 ++ .../components/table/components/tableCell.tsx | 18 ++ .../table/components/tableFooter.tsx | 18 ++ .../components/table/components/tableHead.tsx | 20 ++ .../table/components/tableHeader.tsx | 18 ++ .../components/table/components/tableRow.tsx | 18 ++ .../src/components/table/index.tsx | 213 ++++++++++-------- .../src/components/table/table.stories.tsx | 151 ------------- 12 files changed, 283 insertions(+), 245 deletions(-) create mode 100644 packages/frappe-ui-react/src/components/table/components/index.ts create mode 100644 packages/frappe-ui-react/src/components/table/components/tableBody.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableCaption.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableCell.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableFooter.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableHead.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableHeader.tsx create mode 100644 packages/frappe-ui-react/src/components/table/components/tableRow.tsx delete mode 100644 packages/frappe-ui-react/src/components/table/table.stories.tsx diff --git a/package-lock.json b/package-lock.json index 7d879e9f..b6bba16d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5589,6 +5589,26 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", @@ -5606,6 +5626,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", @@ -16154,6 +16187,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-table": "^8.21.3", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index c4aac6f5..ca5a4acf 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -34,6 +34,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-table": "^8.21.3", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/src/components/table/components/index.ts b/packages/frappe-ui-react/src/components/table/components/index.ts new file mode 100644 index 00000000..5c31f275 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/index.ts @@ -0,0 +1,7 @@ +export { default as TableHeader } from "./tableHeader"; +export { default as TableBody } from "./tableBody"; +export { default as TableFooter } from "./tableFooter"; +export { default as TableHead } from "./tableHead"; +export { default as TableRow } from "./tableRow"; +export { default as TableCell } from "./tableCell"; +export { default as TableCaption } from "./tableCaption"; diff --git a/packages/frappe-ui-react/src/components/table/components/tableBody.tsx b/packages/frappe-ui-react/src/components/table/components/tableBody.tsx new file mode 100644 index 00000000..2f231cc7 --- /dev/null +++ b/packages/frappe-ui-react/src/components/table/components/tableBody.tsx @@ -0,0 +1,17 @@ +import classNames from "classnames"; +import { PropsWithChildren } from "react"; + +const TableBody = ({ + className, + children, + ...props +}: PropsWithChildren>) => ( +
+ {children} +
+ {children} +
+ {children} +
- -)); - -const TableHeader = forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); +import { + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "./components"; -const TableBody = forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); +interface TableProps { + className?: string; + columns: ColumnDef[]; + data: TData[]; + enablePagination?: boolean; + paginationState?: { + pageIndex: number; + pageSize: number; + }; + onPaginationChange?: OnChangeFn<{ + pageIndex: number; + pageSize: number; + }>; + componentClassNames?: { + caption?: string; + header?: string; + body?: string; + footer?: string; + head?: string; + row?: string; + cell?: string; + }; +} -const TableFooter = forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - tr]:last:border-b-0", className)} - {...props} - /> -)); +function Table({ + className, + columns, + data, + enablePagination = false, + paginationState = { pageIndex: 0, pageSize: 10 }, + onPaginationChange, + componentClassNames, +}: TableProps) { + const table = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + manualPagination: enablePagination, + getPaginationRowModel: enablePagination + ? getPaginationRowModel() + : undefined, + onPaginationChange: enablePagination ? onPaginationChange : undefined, + state: { + pagination: + enablePagination ? paginationState : undefined, + }, + }); -const TableRow = forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)); + return ( +
+
+ -const TableHead = forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + -const TableCell = forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( - -)); + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + -const TableCaption = forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.footer, + header.getContext() + )} + + ))} + + ))} + +
+
+ ); +} -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -}; +export default Table; diff --git a/packages/frappe-ui-react/src/components/table/table.stories.tsx b/packages/frappe-ui-react/src/components/table/table.stories.tsx deleted file mode 100644 index d39a0a1b..00000000 --- a/packages/frappe-ui-react/src/components/table/table.stories.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} from "./index"; - -export default { - title: "Components/Table", - component: Table, - parameters: { - layout: "centered", - }, -} as Meta; - -const Template: StoryObj = { - render: () => ( -
- - Sample Table Caption - - - Name - Email - Status - - - - - Jane Doe - jane@example.com - Active - - - John Smith - john@example.com - Inactive - - - - - Footer: 2 users - - -
-
- ), -}; - -export const Basic = { ...Template }; - -export const Advanced = { - render: () => ( -
- - Team Members & Status - - - - Avatar - - Name - Email - Status - Role - - - - - - Jane - - - Jane Doe - - - jane@example.com - - - - Active - - - Developer - - - - John - - - John Smith - - - john@example.com - - - - Pending - - - Designer - - - - Alex - - - Alex Lee - - - alex@example.com - - - - Inactive - - - Manager - - - - - - Footer: 3 team members - - - -
-
- ), -};