From 511566909d7223f8a8057bf2256ef1da449aec1e Mon Sep 17 00:00:00 2001 From: Arno V Date: Mon, 15 Apr 2024 17:12:24 -0400 Subject: [PATCH] feat(Table): adding TableCellSort to support sortable data (#501) ## Summary by CodeRabbit - **New Features** - Introduced sorting functionality in tables with new `TableCellSort` component, allowing users to sort data by clicking on table headers. - **Documentation** - Updated documentation to include new sorting features in tables. - **Tests** - Added tests for new sorting capabilities in table components. - **Refactor** - Modified the timestamp in data. - Changed `ButtonIcon` props from `noBorder` to `noBsortDirection`. - **Chores** - Added `TableCellSortDirections` and `TableCellSort` imports. - Updated the `importName` meta field to include `TableCellSort`. - Added new constant `TableCellSortDirections` with sorting directions `ASC` and `DESC`. - Added `TableCellSort` and `TableCellSortDirections` exports to the list of exports from the `Table` module. --- .../src/Components/Table.stories.tsx | 117 +++++++++++++++++- .../src/components/Table/Table.tsx | 56 +++++++++ .../src/components/Table/TableTypes.d.ts | 14 +++ .../components/Table/__tests__/Table.test.tsx | 76 ++++++++++++ .../src/components/Table/utilities.ts | 5 + .../ui-components/src/components/index.ts | 4 + 6 files changed, 270 insertions(+), 2 deletions(-) diff --git a/packages/documentation/src/Components/Table.stories.tsx b/packages/documentation/src/Components/Table.stories.tsx index 48dbafdc..485335f2 100644 --- a/packages/documentation/src/Components/Table.stories.tsx +++ b/packages/documentation/src/Components/Table.stories.tsx @@ -4,16 +4,20 @@ import { Table, TableBody, TableCell, + TableCellSort, + TableCellSortDirections, TableFooter, TableHead, TableRow, } from "@versini/ui-components"; import { IconDelete, IconRestore } from "@versini/ui-icons"; +import { useState } from "react"; export default { title: "Components/Table", meta: { - importName: "Table, TableBody, TableCell, TableHead, TableRow", + importName: + "Table, TableBody, TableCell, TableCellSort, TableHead, TableRow", }, args: { mode: "system", @@ -39,7 +43,7 @@ const data = [ id: 1, character: "Paul Atreides", actor: "Timothée Chalamet", - timestamp: "10/16/2023 08:46 PM EDT", + timestamp: "10/17/2023 08:46 PM EDT", }, { id: 2, @@ -258,3 +262,112 @@ export const WithRowNumbers: Story = (args) => { ); }; + +export const Sortable: Story = (args) => { + const [sortState, setSortState] = useState<{ + cell: string; + direction: boolean | string; + }>({ direction: false, cell: "" }); + + const sortedData = data.sort((a, b) => { + switch (sortState.cell) { + case "actor": + case "character": + if (sortState.direction === TableCellSortDirections.ASC) { + return a[sortState.cell].localeCompare(b[sortState.cell]); + } else if (sortState.direction === TableCellSortDirections.DESC) { + return b[sortState.cell].localeCompare(a[sortState.cell]); + } + break; + + case "timestamp": + if (sortState.direction === TableCellSortDirections.ASC) { + return ( + new Date(a[sortState.cell]).getTime() - + new Date(b[sortState.cell]).getTime() + ); + } else if (sortState.direction === TableCellSortDirections.DESC) { + return ( + new Date(b[sortState.cell]).getTime() - + new Date(a[sortState.cell]).getTime() + ); + } + break; + + default: + return 0; + } + return 0; + }); + + const onClickSort = (key: string) => { + switch (sortState.direction) { + case false: + setSortState({ cell: key, direction: TableCellSortDirections.ASC }); + break; + case TableCellSortDirections.ASC: + setSortState({ cell: key, direction: TableCellSortDirections.DESC }); + break; + default: + setSortState({ cell: key, direction: TableCellSortDirections.ASC }); + break; + } + }; + + return ( +
+
+ + + + { + onClickSort("timestamp"); + }} + > + Date + + { + onClickSort("character"); + }} + > + Character + + { + onClickSort("actor"); + }} + > + Actor + + + + + + {sortedData.map((row) => ( + + {row.timestamp} + {row.character} + {row.actor} + + ))} + +
+
+
+ ); +}; diff --git a/packages/ui-components/src/components/Table/Table.tsx b/packages/ui-components/src/components/Table/Table.tsx index 541251ef..465cef3c 100644 --- a/packages/ui-components/src/components/Table/Table.tsx +++ b/packages/ui-components/src/components/Table/Table.tsx @@ -1,9 +1,12 @@ +import { IconDown, IconSort, IconUp } from "@versini/ui-icons"; import { useContext } from "react"; +import { ButtonIcon } from "../Button/ButtonIcon"; import { TableContext } from "./TableContext"; import type { TableBodyProps, TableCellProps, + TableCellSortProps, TableHeadProps, TableProps, TableRowProps, @@ -17,6 +20,7 @@ import { getTableFooterClasses, getTableHeadClasses, getTableRowClasses, + TableCellSortDirections, } from "./utilities"; export const Table = ({ @@ -145,3 +149,55 @@ export const TableCell = ({ ); }; + +export const TableCellSort = ({ + align, + children, + className, + component, + focusMode = "alt-system", + mode = "alt-system", + onClick, + sortDirection, + sortedCell, + cellId, + ...otherProps +}: TableCellSortProps) => { + return ( + + + {sortDirection === TableCellSortDirections.ASC && + sortedCell === cellId ? ( + + ) : sortDirection === TableCellSortDirections.DESC && + sortedCell === cellId ? ( + + ) : ( + + )} + + + ); +}; diff --git a/packages/ui-components/src/components/Table/TableTypes.d.ts b/packages/ui-components/src/components/Table/TableTypes.d.ts index b4953e09..7cff8ea6 100644 --- a/packages/ui-components/src/components/Table/TableTypes.d.ts +++ b/packages/ui-components/src/components/Table/TableTypes.d.ts @@ -57,3 +57,17 @@ export type TableCellProps = { component?: "td" | "th"; } & React.ThHTMLAttributes & React.TdHTMLAttributes; + +export type TableCellSortProps = { + cellId: string; + children: string; + onClick: (event: React.MouseEvent) => void; + sortDirection: "asc" | "desc" | false; + sortedCell: string; + + align?: "left" | "center" | "right"; + component?: "td" | "th"; + focusMode?: "system" | "light" | "dark" | "alt-system"; + mode?: "system" | "light" | "dark" | "alt-system"; +} & React.ThHTMLAttributes & + React.TdHTMLAttributes; diff --git a/packages/ui-components/src/components/Table/__tests__/Table.test.tsx b/packages/ui-components/src/components/Table/__tests__/Table.test.tsx index ed5d2bf5..fc3814ce 100644 --- a/packages/ui-components/src/components/Table/__tests__/Table.test.tsx +++ b/packages/ui-components/src/components/Table/__tests__/Table.test.tsx @@ -8,6 +8,7 @@ import { Table, TableBody, TableCell, + TableCellSort, TableFooter, TableHead, TableRow, @@ -540,6 +541,81 @@ describe("Table components", () => { expect(tableCell.tagName).toBe("TD"); }); + it("should render a sortable table cell with ASC icon", async () => { + render( + + + + {}} + data-testid="table-cell-sort" + > + hello + + + +
, + ); + const tableCell = await screen.findByTestId("table-cell-sort"); + expect(tableCell).toBeInTheDocument(); + expect(tableCell.tagName).toBe("TD"); + expect(tableCell.querySelector("svg")).toBeInTheDocument(); + expect(tableCell).toHaveAttribute("aria-sort", "ascending"); + }); + + it("should render a sortable table cell with DESC icon", async () => { + render( + + + + {}} + data-testid="table-cell-sort" + > + hello + + + +
, + ); + const tableCell = await screen.findByTestId("table-cell-sort"); + expect(tableCell).toBeInTheDocument(); + expect(tableCell.tagName).toBe("TD"); + expect(tableCell.querySelector("svg")).toBeInTheDocument(); + expect(tableCell).toHaveAttribute("aria-sort", "descending"); + }); + + it("should render a sortable table cell with non-sorted icon", async () => { + render( + + + + {}} + data-testid="table-cell-sort" + > + hello + + + +
, + ); + const tableCell = await screen.findByTestId("table-cell-sort"); + expect(tableCell).toBeInTheDocument(); + expect(tableCell.tagName).toBe("TD"); + expect(tableCell.querySelector("svg")).toBeInTheDocument(); + expect(tableCell).toHaveAttribute("aria-sort", "other"); + }); + it("should render a generated table cell (th)", async () => { render( diff --git a/packages/ui-components/src/components/Table/utilities.ts b/packages/ui-components/src/components/Table/utilities.ts index 140c58fe..4d084287 100644 --- a/packages/ui-components/src/components/Table/utilities.ts +++ b/packages/ui-components/src/components/Table/utilities.ts @@ -6,6 +6,11 @@ export const CELL_WRAPPER_HEAD = "thead"; export const CELL_WRAPPER_FOOTER = "tfoot"; export const CELL_WRAPPER_BODY = "tbody"; +export const TableCellSortDirections = { + ASC: "asc", + DESC: "desc", +}; + export const getTableClasses = ({ mode, className, diff --git a/packages/ui-components/src/components/index.ts b/packages/ui-components/src/components/index.ts index 12495c22..e6b00dff 100644 --- a/packages/ui-components/src/components/index.ts +++ b/packages/ui-components/src/components/index.ts @@ -16,10 +16,12 @@ import { Table, TableBody, TableCell, + TableCellSort, TableFooter, TableHead, TableRow, } from "./Table/Table"; +import { TableCellSortDirections } from "./Table/utilities"; export { Anchor, @@ -39,6 +41,8 @@ export { Table, TableBody, TableCell, + TableCellSort, + TableCellSortDirections, TableFooter, TableHead, TableRow,