From 7f0b056a79bbb108c6aac0ce93f19ed695104583 Mon Sep 17 00:00:00 2001 From: theJohnnyMe Date: Tue, 14 May 2024 15:45:31 +0200 Subject: [PATCH] demo(table): using tanstack --- package-lock.json | 48 +++++++ package.json | 2 + src/components/Navigation/SideMenu.tsx | 6 + src/customEvents.d.ts | 10 ++ src/index.tsx | 5 + src/pages/Tanstack.tsx | 166 +++++++++++++++++++++++++ src/pages/demoData.ts | 46 +++++++ 7 files changed, 283 insertions(+) create mode 100644 src/pages/Tanstack.tsx create mode 100644 src/pages/demoData.ts diff --git a/package-lock.json b/package-lock.json index 6f869bc2..4e6dc2b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "@scania/tegel-react-demo", "version": "0.0.1", "dependencies": { + "@faker-js/faker": "^8.4.1", "@scania/tegel-react": "1.8.1", + "@tanstack/react-table": "^8.17.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -2340,6 +2342,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -3621,6 +3638,37 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/react-table": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.17.0.tgz", + "integrity": "sha512-LSJxTDzlKGs8EN7/UHB1l3yLR9HUIxoHFkTbTjHaUUGL4kgYZFYhsQsdDJSIykG86qpIA/6gSWmtwNfy5Iprhw==", + "dependencies": { + "@tanstack/table-core": "8.16.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.16.0.tgz", + "integrity": "sha512-dCG8vQGk4js5v88/k83tTedWOwjGnIyONrKpHpfmSJB8jwFHl8GSu1sBBxbtACVAPtAQgwNxl0rw1d3RqRM1Tg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.3", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", diff --git a/package.json b/package.json index 17ec1660..2e077b59 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "access": "public" }, "dependencies": { + "@faker-js/faker": "^8.4.1", "@scania/tegel-react": "1.8.1", + "@tanstack/react-table": "^8.17.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/components/Navigation/SideMenu.tsx b/src/components/Navigation/SideMenu.tsx index 9853cb99..606699e1 100644 --- a/src/components/Navigation/SideMenu.tsx +++ b/src/components/Navigation/SideMenu.tsx @@ -84,6 +84,12 @@ const SideMenu = ({ style, className, pathname, toggleMobileNav, sideMenuRef }: Text + + toggleMobileNav()}> + + Tanstack POC + +
, }, + { + path: 'tanstack', + element: , + }, { path: 'text/subpage-with-a-very-long-title', element: , diff --git a/src/pages/Tanstack.tsx b/src/pages/Tanstack.tsx new file mode 100644 index 00000000..5567a8b9 --- /dev/null +++ b/src/pages/Tanstack.tsx @@ -0,0 +1,166 @@ +import React from 'react'; + +import { + ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + SortingFn, + SortingState, + useReactTable, +} from '@tanstack/react-table'; +import { makeData, Person } from './demoData'; +import { + TdsTable, + TdsTableHeader, + TdsHeaderCell, + TdsTableBody, + TdsTableBodyRow, + TdsBodyCell, +} from '@scania/tegel-react'; + +//custom sorting logic for one of our enum columns +const sortStatusFn: SortingFn = (rowA, rowB, _columnId) => { + const statusA = rowA.original.status; + const statusB = rowB.original.status; + const statusOrder = ['single', 'complicated', 'relationship']; + return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB); +}; + +const Tanstack = () => { + const rerender = React.useReducer(() => ({}), {})[1]; + + const [sorting, setSorting] = React.useState([]); + + const columns = React.useMemo[]>( + () => [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + //this column will sort in ascending order by default since it is a string column + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => info.getValue(), + header: () => Last Name, + sortUndefined: 'last', //force undefined values to the end + sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order) + }, + { + accessorKey: 'age', + header: () => 'Age', + //this column will sort in descending order by default since it is a number column + }, + { + accessorKey: 'visits', + header: () => Visits, + sortUndefined: 'last', //force undefined values to the end + }, + { + accessorKey: 'status', + header: 'Status', + sortingFn: sortStatusFn, //use our custom sorting function for this enum column + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + // enableSorting: false, //disable sorting for this column + }, + { + accessorKey: 'rank', + header: 'Rank', + invertSorting: true, //invert the sorting order (golf score-like where smaller is better) + }, + ], + [], + ); + + const [data, setData] = React.useState(() => makeData(1_000)); + const refreshData = () => setData(() => makeData(100_000)); //stress test with 100k rows + + const table = useReactTable({ + columns, + data, + debugTable: true, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), //client-side sorting + onSortingChange: setSorting, //optionally control sorting state in your own scope for easy access + // sortingFns: { + // sortStatusFn, //or provide our custom sorting function globally for all columns to be able to use + // }, + //no need to pass pageCount or rowCount with client-side pagination as it is calculated automatically + state: { + sorting, + }, + // autoResetPageIndex: false, // turn off page index reset when sorting or filtering - default on/true + // enableMultiSort: false, //Don't allow shift key to sort multiple columns - default on/true + // enableSorting: false, // - default on/true + // enableSortingRemoval: false, //Don't allow - default on/true + // isMultiSortEvent: (e) => true, //Make all clicks multi-sort - default requires `shift` key + // maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once - default is Infinity + }); + + //access sorting state from the table instance + console.log(table.getState().sorting); + + return ( +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : ( +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+ ); + })} +
+ ))} + + + {table + .getRowModel() + .rows.slice(0, 10) + .map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} + + ); + })} + +
+
+ ); +}; + +export default Tanstack; diff --git a/src/pages/demoData.ts b/src/pages/demoData.ts new file mode 100644 index 00000000..c41519cb --- /dev/null +++ b/src/pages/demoData.ts @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker'; + +export type Person = { + firstName: string; + lastName: string | undefined; + age: number; + visits: number | undefined; + progress: number; + status: 'relationship' | 'complicated' | 'single'; + rank: number; + subRows?: Person[]; +}; + +const range = (len: number) => { + const arr: number[] = []; + for (let i = 0; i < len; i++) { + arr.push(i); + } + return arr; +}; + +const newPerson = (): Person => { + return { + firstName: faker.person.firstName(), + lastName: Math.random() < 0.1 ? undefined : faker.person.lastName(), + age: faker.number.int(40), + visits: Math.random() < 0.1 ? undefined : faker.number.int(1000), + progress: faker.number.int(100), + status: faker.helpers.shuffle(['relationship', 'complicated', 'single'])[0]!, + rank: faker.number.int(100), + }; +}; + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]!; + return range(len).map((_d): Person => { + return { + ...newPerson(), + subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, + }; + }); + }; + + return makeDataLevel(); +}