From da75132383c78de732ea248dee49df5026a8f1f3 Mon Sep 17 00:00:00 2001 From: imikulec Date: Mon, 1 Jan 2024 15:25:31 +0100 Subject: [PATCH 1/6] #149 - DataTable sorting - hook proposal --- libs/data-display/src/index.tsx | 2 + .../data-display/src/useSortableDataTable.tsx | 21 ++++++++++ storybook/src/data-display/DataTable.mdx | 39 +++++++++++++++++++ .../src/data-display/DataTable.stories.tsx | 28 ++++++++++++- 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 libs/data-display/src/useSortableDataTable.tsx diff --git a/libs/data-display/src/index.tsx b/libs/data-display/src/index.tsx index 8b36e3a..68b3b0c 100644 --- a/libs/data-display/src/index.tsx +++ b/libs/data-display/src/index.tsx @@ -19,6 +19,8 @@ import { SortInfo as InternalSortInfo } from "./DataTable"; export type SortInfo = InternalSortInfo; +export { default as useSortableDataTable } from "./useSortableDataTable"; + export { default as Amount } from "./Amount"; export { default as DataTable, useDataTable, useLocalSummary } from "./DataTable"; export { default as DescriptionList } from "./DescriptionList"; diff --git a/libs/data-display/src/useSortableDataTable.tsx b/libs/data-display/src/useSortableDataTable.tsx new file mode 100644 index 0000000..ffca3d4 --- /dev/null +++ b/libs/data-display/src/useSortableDataTable.tsx @@ -0,0 +1,21 @@ +import _ from "lodash"; + +import { useDataTable } from "./index"; + +export default function useSortableDataTable(initialData: T[], columnMapping: Record) { + const [dataTableState, dataTableHook] = useDataTable(); + + const generateSortedData = () => { + const sortInfo = dataTableState.sortBy[0]; + + if (!sortInfo) { + return initialData; + } + + const sortedData = _.sortBy(initialData, columnMapping[sortInfo.column as U]); + + return sortInfo.sortDirection === "ASCENDING" ? sortedData : sortedData.reverse(); + }; + + return { sortedData: generateSortedData(), dataTableHook }; +} diff --git a/storybook/src/data-display/DataTable.mdx b/storybook/src/data-display/DataTable.mdx index 634a2e5..10763d4 100644 --- a/storybook/src/data-display/DataTable.mdx +++ b/storybook/src/data-display/DataTable.mdx @@ -111,6 +111,45 @@ Here is an example utilizing the `useMemo` hook. This specific logic is applied Now you have a DataTable component with sorting enabled for the specified column, and you can implement your own sorting logic based on your application requirements. +### useSortableDataTable hook +The `@tiller-ds/data-display` package provides a datatable sorting hook that allows you to implement custom sorting logic for your table columns. Here's an example of using the sorting hook in a DataTable: +```tsx +import { DataTable, useSortableDataTable } from '@tiller-ds/data-display'; + +// Define column mapping for custom sorting +const columnMapping = { + name: 'name', +}; + +const { dataTableHook, sortedData } = useSortableDataTable(smallData || [], columnMapping); + + + + +; +``` + +The sorting hook, `useSortableDataTable`, enhances the DataTable component by providing a mechanism for sorting logic. +It takes the table data and a column mapping as input and returns the necessary hook and sorted data. +The column mapping is an object where keys represent column names and values represent their corresponding data keys. + +The hook returns an object with two properties: +- `dataTableHook`: the hook that should be passed to the `hook` prop of the DataTable component +- `sortedData`: the data array that has been sorted + +Ensure that the `canSort` prop is set to `true` for columns that should be sortable.
+Default sorting behavior can be configured using the `defaultSortBy` prop on the DataTable component.
+You can use this hook to implement sorting for specific columns according to your requirements. + ## Best Practices Data Tables should: diff --git a/storybook/src/data-display/DataTable.stories.tsx b/storybook/src/data-display/DataTable.stories.tsx index 4dee49c..7c2e5b8 100644 --- a/storybook/src/data-display/DataTable.stories.tsx +++ b/storybook/src/data-display/DataTable.stories.tsx @@ -22,7 +22,7 @@ import { range, slice } from "lodash"; import { withDesign } from "storybook-addon-designs"; import { Button, Card, IconButton, Link, Pagination, Typography, useLocalPagination } from "@tiller-ds/core"; -import { DataTable, useDataTable, useLocalSummary } from "@tiller-ds/data-display"; +import { DataTable, useDataTable, useLocalSummary, useSortableDataTable } from "@tiller-ds/data-display"; import { Icon } from "@tiller-ds/icons"; import { DropdownMenu } from "@tiller-ds/menu"; import { defaultThemeConfig } from "@tiller-ds/theme"; @@ -1235,6 +1235,31 @@ export const WithDefaultAscendingSortByName = (args) => { ); }; +export const WithDefaultAscendingSortByNameUsingHook = () => { + // incl-code + const columnMapping = { + name: "name", + }; + + const { dataTableHook, sortedData } = useSortableDataTable(smallData || [], columnMapping); + + return ( + + + + + ) +}; + export const WithIconButtons = (args) => ( @@ -1440,6 +1465,7 @@ WithHorizontalScroll.argTypes = HideControls; WithHorizontalScrollAndFirstColumnFixed.argTypes = HideControls; WithHorizontalScrollAndLastColumnFixed.argTypes = HideControls; WithDefaultAscendingSortByName.argTypes = HideControls; +WithDefaultAscendingSortByNameUsingHook.argTypes = HideControls; WithIconButtons.argTypes = HideControls; WithPrimaryAndSecondaryRows.argTypes = HideControls; WithPrimaryAndSecondaryRowsAndComplexValues.argTypes = HideControls; From 9a4f76f469abdcdd6159fb1273148bc9a9ba5054 Mon Sep 17 00:00:00 2001 From: imikulec Date: Tue, 2 Jan 2024 18:37:32 +0100 Subject: [PATCH 2/6] #149 - Expose dataTableState from useSortableDataTable hook --- libs/data-display/src/useSortableDataTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/data-display/src/useSortableDataTable.tsx b/libs/data-display/src/useSortableDataTable.tsx index ffca3d4..adc50a9 100644 --- a/libs/data-display/src/useSortableDataTable.tsx +++ b/libs/data-display/src/useSortableDataTable.tsx @@ -17,5 +17,5 @@ export default function useSortableDataTable(initialData: return sortInfo.sortDirection === "ASCENDING" ? sortedData : sortedData.reverse(); }; - return { sortedData: generateSortedData(), dataTableHook }; + return { sortedData: generateSortedData(), dataTableState, dataTableHook }; } From 5dac6455860d74684c6949c57086e21a52b98bd5 Mon Sep 17 00:00:00 2001 From: imikulec Date: Tue, 23 Jan 2024 21:41:31 +0100 Subject: [PATCH 3/6] #149 - Fix sorting logic in useSortableDataTable --- .../data-display/src/useSortableDataTable.tsx | 21 +++++++++++++------ storybook/src/data-display/DataTable.mdx | 8 ++++++- .../src/data-display/DataTable.stories.tsx | 10 +++++++-- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/libs/data-display/src/useSortableDataTable.tsx b/libs/data-display/src/useSortableDataTable.tsx index adc50a9..30a466d 100644 --- a/libs/data-display/src/useSortableDataTable.tsx +++ b/libs/data-display/src/useSortableDataTable.tsx @@ -1,20 +1,29 @@ -import _ from "lodash"; - import { useDataTable } from "./index"; export default function useSortableDataTable(initialData: T[], columnMapping: Record) { const [dataTableState, dataTableHook] = useDataTable(); const generateSortedData = () => { - const sortInfo = dataTableState.sortBy[0]; + const sortInstructions = dataTableState.sortBy; - if (!sortInfo) { + if (!sortInstructions || sortInstructions.length === 0) { return initialData; } - const sortedData = _.sortBy(initialData, columnMapping[sortInfo.column as U]); + return [...initialData].sort((a, b) => { + for (const sortInfo of sortInstructions) { + const columnKey = columnMapping[sortInfo.column]; + const compareResult = sortInfo.sortDirection === "ASCENDING" ? -1 : 1; + const aValue = a[columnKey]; + const bValue = b[columnKey]; + + if (aValue !== bValue) { + return (aValue < bValue ? -1 : 1) * compareResult; + } + } - return sortInfo.sortDirection === "ASCENDING" ? sortedData : sortedData.reverse(); + return 0; + }) }; return { sortedData: generateSortedData(), dataTableState, dataTableHook }; diff --git a/storybook/src/data-display/DataTable.mdx b/storybook/src/data-display/DataTable.mdx index 10763d4..92e02ea 100644 --- a/storybook/src/data-display/DataTable.mdx +++ b/storybook/src/data-display/DataTable.mdx @@ -119,9 +119,10 @@ import { DataTable, useSortableDataTable } from '@tiller-ds/data-display'; // Define column mapping for custom sorting const columnMapping = { name: 'name', + surname: "surname", }; -const { dataTableHook, sortedData } = useSortableDataTable(smallData || [], columnMapping); +const { dataTableHook, sortedData } = useSortableDataTable(allData || [], columnMapping); + ; ``` diff --git a/storybook/src/data-display/DataTable.stories.tsx b/storybook/src/data-display/DataTable.stories.tsx index 7c2e5b8..5809f37 100644 --- a/storybook/src/data-display/DataTable.stories.tsx +++ b/storybook/src/data-display/DataTable.stories.tsx @@ -95,7 +95,7 @@ type Item = { }; const names = ["Emily", "Michael", "Sarah", "Matthew"]; -const surname = ["Moore", "Williams", "Brown", "Davis"]; +const surname = ["Moore", "Williams", "Brown", "Davis", "Aron"]; const jobs = ["Nurse", "Teacher", "Software developer", "Lawyer"]; const jobDescription = [ "You will be tasked with caring for pediatric patients with a variety of health conditions and challenges as well as collaborating with physicians to provide the highest-quality care possible to each individual. As a registered nurse on staff, you will communicate orders to medical assistants and other team members and coordinate with staff and families to ensure the adherence to the attending physician’s instructions as well as proper care and disease control practices.", @@ -1239,9 +1239,10 @@ export const WithDefaultAscendingSortByNameUsingHook = () => { // incl-code const columnMapping = { name: "name", + surname: "surname", }; - const { dataTableHook, sortedData } = useSortableDataTable(smallData || [], columnMapping); + const { dataTableHook, sortedData } = useSortableDataTable(allData || [], columnMapping); return ( { column: "name", sortDirection: "ASCENDING", }, + { + column: "surname", + sortDirection: "ASCENDING", + }, ]} > + ) }; From ce972a8b39bf4391ef346bfd2db67a05ea76ad18 Mon Sep 17 00:00:00 2001 From: imikulec Date: Tue, 23 Jan 2024 21:49:42 +0100 Subject: [PATCH 4/6] #149 - Rename Story --- storybook/src/data-display/DataTable.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storybook/src/data-display/DataTable.stories.tsx b/storybook/src/data-display/DataTable.stories.tsx index 5809f37..f3b2feb 100644 --- a/storybook/src/data-display/DataTable.stories.tsx +++ b/storybook/src/data-display/DataTable.stories.tsx @@ -1235,7 +1235,7 @@ export const WithDefaultAscendingSortByName = (args) => { ); }; -export const WithDefaultAscendingSortByNameUsingHook = () => { +export const WithDefaultAscendingSortUsingHook = () => { // incl-code const columnMapping = { name: "name", @@ -1471,7 +1471,7 @@ WithHorizontalScroll.argTypes = HideControls; WithHorizontalScrollAndFirstColumnFixed.argTypes = HideControls; WithHorizontalScrollAndLastColumnFixed.argTypes = HideControls; WithDefaultAscendingSortByName.argTypes = HideControls; -WithDefaultAscendingSortByNameUsingHook.argTypes = HideControls; +WithDefaultAscendingSortUsingHook.argTypes = HideControls; WithIconButtons.argTypes = HideControls; WithPrimaryAndSecondaryRows.argTypes = HideControls; WithPrimaryAndSecondaryRowsAndComplexValues.argTypes = HideControls; From 5354b1ddbd95ad62f14ff8572d556d4b43c9bbf2 Mon Sep 17 00:00:00 2001 From: imikulec Date: Wed, 24 Jan 2024 09:38:55 +0100 Subject: [PATCH 5/6] #149 - Optimise useSortableDataTable logic with useMemo --- libs/data-display/src/useSortableDataTable.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/data-display/src/useSortableDataTable.tsx b/libs/data-display/src/useSortableDataTable.tsx index 30a466d..46d0d35 100644 --- a/libs/data-display/src/useSortableDataTable.tsx +++ b/libs/data-display/src/useSortableDataTable.tsx @@ -1,9 +1,11 @@ +import { useMemo } from "react"; + import { useDataTable } from "./index"; export default function useSortableDataTable(initialData: T[], columnMapping: Record) { const [dataTableState, dataTableHook] = useDataTable(); - const generateSortedData = () => { + const generateSortedData = useMemo(() => { const sortInstructions = dataTableState.sortBy; if (!sortInstructions || sortInstructions.length === 0) { @@ -23,8 +25,10 @@ export default function useSortableDataTable(initialData: } return 0; - }) - }; + }); + }, [dataTableState.sortBy]); - return { sortedData: generateSortedData(), dataTableState, dataTableHook }; + return useMemo(() => { + return { sortedData: generateSortedData, dataTableState, dataTableHook }; + }, [generateSortedData]); } From 2c38a4f0b2b55f5c3af70a2bf0b418cf26caae11 Mon Sep 17 00:00:00 2001 From: imikulec Date: Tue, 30 Jan 2024 09:28:44 +0100 Subject: [PATCH 6/6] #149 - Add sorting note to DataTable.mdx --- storybook/src/data-display/DataTable.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/storybook/src/data-display/DataTable.mdx b/storybook/src/data-display/DataTable.mdx index 92e02ea..a3a314c 100644 --- a/storybook/src/data-display/DataTable.mdx +++ b/storybook/src/data-display/DataTable.mdx @@ -156,6 +156,10 @@ Ensure that the `canSort` prop is set to `true` for columns that should be sorta Default sorting behavior can be configured using the `defaultSortBy` prop on the DataTable component.
You can use this hook to implement sorting for specific columns according to your requirements. +**Note:** +When using this hook, be aware that it is designed for **client-side sorting**. While it provides flexibility, it may **not be optimal** for handling **larger datasets**.
+Client-side sorting can lead to performance issues with larger amounts of data. It is recommended to consider server-side sorting for improved performance in such cases. + ## Best Practices Data Tables should: