From e52ca48ff81ff84da926248df7d539b73bf2ea3e Mon Sep 17 00:00:00 2001 From: Arno V Date: Tue, 16 Apr 2024 13:29:38 -0700 Subject: [PATCH] feat(Table): better sort indicator when a column is sorted (#507) ## Summary by CodeRabbit - **New Features** - Added an active state feature to button icons, enhancing visual feedback when buttons are active. - **Bug Fixes** - Improved sorting functionality in tables by correctly setting active states. - **Tests** - Implemented new tests for active state styling of button icons. --- .../src/Components/Table.stories.tsx | 134 ++++++++++++++---- .../src/components/Button/ButtonIcon.tsx | 46 ++++-- .../src/components/Button/ButtonTypes.d.ts | 6 + .../Button/__tests__/ButtonIcon.test.tsx | 50 +++++++ .../src/components/Table/Table.tsx | 3 +- 5 files changed, 196 insertions(+), 43 deletions(-) diff --git a/packages/documentation/src/Components/Table.stories.tsx b/packages/documentation/src/Components/Table.stories.tsx index eefde818..1b30cd53 100644 --- a/packages/documentation/src/Components/Table.stories.tsx +++ b/packages/documentation/src/Components/Table.stories.tsx @@ -59,6 +59,71 @@ const data = [ }, ]; +const nutritionData = [ + { id: 1, name: "Cupcake", calories: 305, fat: 3.7, carbs: 67, protein: 4.3 }, + { id: 2, name: "Donut", calories: 452, fat: 25.0, carbs: 51, protein: 4.9 }, + { id: 3, name: "Eclair", calories: 262, fat: 16.0, carbs: 24, protein: 6.0 }, + { + id: 4, + name: "Frozen yoghurt", + calories: 159, + fat: 6.0, + carbs: 24, + protein: 4.0, + }, + { + id: 5, + name: "Gingerbread", + calories: 356, + fat: 16.0, + carbs: 49, + protein: 3.9, + }, + { + id: 6, + name: "Honeycomb", + calories: 408, + fat: 3.2, + carbs: 87, + protein: 6.5, + }, + { + id: 7, + name: "Ice cream sandwich", + calories: 237, + fat: 9.0, + carbs: 37, + protein: 4.3, + }, + { + id: 8, + name: "Jelly Bean", + calories: 375, + fat: 0.0, + carbs: 94, + protein: 0.0, + }, + { id: 9, name: "KitKat", calories: 518, fat: 26.0, carbs: 65, protein: 7.0 }, + { + id: 10, + name: "Lollipop", + calories: 392, + fat: 0.2, + carbs: 98, + protein: 0.0, + }, + { + id: 11, + name: "Marshmallow", + calories: 318, + fat: 0, + carbs: 81, + protein: 2.0, + }, + { id: 12, name: "Nougat", calories: 360, fat: 19.0, carbs: 9, protein: 37.0 }, + { id: 13, name: "Oreo", calories: 437, fat: 18.0, carbs: 63, protein: 4.0 }, +]; + export const Basic: Story = (args) => { return (
@@ -265,17 +330,16 @@ export const WithRowNumbers: Story = (args) => { export const Sortable: Story = (args) => { const [sortState, setSortState] = useState<{ - cell: string; + cell: string | number; direction: | typeof TableCellSortDirections.ASC | typeof TableCellSortDirections.DESC | false; }>({ direction: false, cell: "" }); - const sortedData = data.sort((a, b) => { + const sortedData = nutritionData.sort((a, b) => { switch (sortState.cell) { - case "actor": - case "character": + case "name": if (sortState.direction === TableCellSortDirections.ASC) { return a[sortState.cell].localeCompare(b[sortState.cell]); } else if (sortState.direction === TableCellSortDirections.DESC) { @@ -283,17 +347,14 @@ export const Sortable: Story = (args) => { } break; - case "timestamp": + case "calories": + case "fat": + case "carbs": + case "protein": if (sortState.direction === TableCellSortDirections.ASC) { - return ( - new Date(a[sortState.cell]).getTime() - - new Date(b[sortState.cell]).getTime() - ); + return a[sortState.cell] - b[sortState.cell]; } else if (sortState.direction === TableCellSortDirections.DESC) { - return ( - new Date(b[sortState.cell]).getTime() - - new Date(a[sortState.cell]).getTime() - ); + return b[sortState.cell] - a[sortState.cell]; } break; @@ -320,42 +381,53 @@ export const Sortable: Story = (args) => { return (
- +
+ Name { - onClickSort("timestamp"); + onClickSort("calories"); }} > - Date + Calories { - onClickSort("character"); + onClickSort("fat"); }} > - Character + Fat { - onClickSort("actor"); + onClickSort("carbs"); }} > - Actor + Carbs + + { + onClickSort("protein"); + }} + > + Protein @@ -363,9 +435,11 @@ export const Sortable: Story = (args) => { {sortedData.map((row) => ( - {row.timestamp} - {row.character} - {row.actor} + {row.name} + {row.calories} + {row.fat} + {row.carbs} + {row.protein} ))} diff --git a/packages/ui-components/src/components/Button/ButtonIcon.tsx b/packages/ui-components/src/components/Button/ButtonIcon.tsx index 55d4638a..4e36b027 100644 --- a/packages/ui-components/src/components/Button/ButtonIcon.tsx +++ b/packages/ui-components/src/components/Button/ButtonIcon.tsx @@ -24,6 +24,7 @@ export const ButtonIcon = React.forwardRef( spacing, noBackground = false, align = "center", + active = false, ...otherProps }, @@ -53,19 +54,40 @@ export const ButtonIcon = React.forwardRef( "text-copy-light dark:text-copy-accent-dark": mode === "system" && !raw, }); + const activeClass = clsx( + "focus-within:static", + "after:absolute", + "after:content-['']", + "after:border-b-2", + "after:z-[-1px]", + "after:bottom-[-4px]", + "after:left-0", + "after:right-0", + { + relative: active, + "after:border-table-dark": mode === "dark", + "after:border-table-light": mode === "light", + "after:border-table-dark dark:after:border-table-light": + mode === "system", + "after:border-table-light dark:after:border-table-dark": + mode === "alt-system", + }, + ); return ( - +
+ +
); }, ); diff --git a/packages/ui-components/src/components/Button/ButtonTypes.d.ts b/packages/ui-components/src/components/Button/ButtonTypes.d.ts index 71c259d6..f1385785 100644 --- a/packages/ui-components/src/components/Button/ButtonTypes.d.ts +++ b/packages/ui-components/src/components/Button/ButtonTypes.d.ts @@ -79,6 +79,12 @@ export type ButtonIconProps = { * The children to render. */ children: React.ReactNode; + /** + * Prop to signal if the Button is active. + * This is an internal prop and should not be used by consumers. + * @private + */ + active?: boolean; /** * Cell content alignment. * @default "center" diff --git a/packages/ui-components/src/components/Button/__tests__/ButtonIcon.test.tsx b/packages/ui-components/src/components/Button/__tests__/ButtonIcon.test.tsx index 001d64f7..e5bab4d8 100644 --- a/packages/ui-components/src/components/Button/__tests__/ButtonIcon.test.tsx +++ b/packages/ui-components/src/components/Button/__tests__/ButtonIcon.test.tsx @@ -180,6 +180,56 @@ describe("ButtonIcon modifiers", () => { expect(buttonClass).toContain("disabled:cursor-not-allowed"); }); + it("should render an active light button icon", async () => { + render( + + + , + ); + const button = await screen.findByRole("button"); + const parent = button.parentElement; + + if (parent) { + expectToHaveClasses(parent, [ + "focus-within:static", + "after:absolute", + "after:content-['']", + "after:border-b-2", + "after:z-[-1px]", + "after:bottom-[-4px]", + "after:left-0", + "after:right-0", + "relative", + "after:border-table-light", + ]); + } + }); + + it("should render an active dark button icon", async () => { + render( + + + , + ); + const button = await screen.findByRole("button"); + const parent = button.parentElement; + + if (parent) { + expectToHaveClasses(parent, [ + "focus-within:static", + "after:absolute", + "after:content-['']", + "after:border-b-2", + "after:z-[-1px]", + "after:bottom-[-4px]", + "after:left-0", + "after:right-0", + "relative", + "after:border-table-dark", + ]); + } + }); + it("should render a fullWidth button icon", async () => { render( diff --git a/packages/ui-components/src/components/Table/Table.tsx b/packages/ui-components/src/components/Table/Table.tsx index 465cef3c..b1cfaf08 100644 --- a/packages/ui-components/src/components/Table/Table.tsx +++ b/packages/ui-components/src/components/Table/Table.tsx @@ -179,7 +179,8 @@ export const TableCellSort = ({ {...otherProps} >