From fefdc5cfbb2714c882d3ecdecea7ad97c86c932a Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 24 May 2024 16:12:54 +0200 Subject: [PATCH 1/5] :sparkles: - feat: make the table also include React nodes --- .../data/attributetable/attributetable.tsx | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/components/data/attributetable/attributetable.tsx b/src/components/data/attributetable/attributetable.tsx index 19e857f9..2582480e 100644 --- a/src/components/data/attributetable/attributetable.tsx +++ b/src/components/data/attributetable/attributetable.tsx @@ -1,21 +1,28 @@ -import React from "react"; +import React, { ReactNode } from "react"; -import { AttributeData } from "../../../lib"; -import { field2Title } from "../../../lib/format/string"; +import { Attribute } from "../../../lib"; import { Value } from "../value"; import "./attributetable.scss"; -export type AttributeTableProps = { - object: AttributeData; - fields?: Array; +export type ValueType = ReactNode; +export type LabelType = string | ReactNode; +export type FieldData = { + label: LabelType; + value: ValueType; }; +export type ObjectData = Record; -export type AttributeTableRowProps = { - object: AttributeData; - field: keyof AttributeData; +export type ObjectTableProps = { + object: ObjectData; + fields?: Array; }; -export const AttributeTable: React.FC = ({ +export type ObjectTableRowProps = { + object: ObjectData; + field: keyof ObjectData; +}; + +export const AttributeTable: React.FC = ({ object = {}, fields = Object.keys(object), ...props @@ -27,16 +34,24 @@ export const AttributeTable: React.FC = ({ ); -export const AttributeTableRow: React.FC = ({ +export const AttributeTableRow: React.FC = ({ object, field, -}) => ( -
-
- {field2Title(field)} -
-
- +}) => { + let value = object[field].value; + if ( + ["boolean", "number", "string"].includes(typeof value) || + value === null + ) { + value = ; + } + + return ( +
+
+ {object[field].label} +
+
{value}
-
-); + ); +}; From 533f97a48bb46f292734b924d335faeff2e02c91 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 24 May 2024 16:13:32 +0200 Subject: [PATCH 2/5] :sparkles: - feat: add stories for table with React nodes --- .../attributetable/attributetable.stories.tsx | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/components/data/attributetable/attributetable.stories.tsx b/src/components/data/attributetable/attributetable.stories.tsx index 627cec1f..f88303ea 100644 --- a/src/components/data/attributetable/attributetable.stories.tsx +++ b/src/components/data/attributetable/attributetable.stories.tsx @@ -1,5 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react"; +import * as React from "react"; +import { ButtonLink } from "../../button"; +import { Outline } from "../../icon"; import { AttributeTable } from "./attributetable"; const meta: Meta = { @@ -13,14 +16,42 @@ type Story = StoryObj; export const AttributeTableComponent: Story = { args: { object: { - url: "https://www.example.com", - Omschrijving: "Afvalpas vervangen", - Zaaktype: "https://www.example.com", - Versie: 2, - Opmerkingen: null, - Actief: false, - Toekomstig: false, - Concept: true, + url: { label: "Url", value: "https://www.example.com" }, + omschrijving: { label: "Omschrijving", value: "Afvalpas vervangen" }, + zaaktype: { label: "Zaaktype", value: "https://www.example.com" }, + versie: { label: "Versie", value: 2 }, + opmerkingen: { label: "Opmerkingen", value: null }, + actief: { label: "Actief", value: false }, + toekomstig: { label: "Toekomstig", value: false }, + concept: { label: "Concept", value: true }, + }, + }, +}; + +export const AttributeTableComponentWithNodes: Story = { + args: { + object: { + button: { + label: "A button!", + value: ( + + + Click me! + + ), + }, + labelWithIcon: { + label: ( +
+ A label with icon +
+ ), + value: "Some value", + }, }, }, }; From 213b7b3d166beb3067695d083cd3e04627d6f744 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Tue, 28 May 2024 16:42:50 +0200 Subject: [PATCH 3/5] :sparkles: - feat: Add functionality to do a custom comparison If the items are objects and there are initial selected values that are not the same object (different reference) as the items in objectList, the selection didn't work --- src/components/data/datagrid/datagrid.tsx | 32 +++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/data/datagrid/datagrid.tsx b/src/components/data/datagrid/datagrid.tsx index ede1f7b3..c500ed72 100644 --- a/src/components/data/datagrid/datagrid.tsx +++ b/src/components/data/datagrid/datagrid.tsx @@ -110,6 +110,12 @@ export type DataGridProps = { /** References to the selected items in `objectList`, setting this preselects the items. */ selected?: AttributeData[]; + /** Can be used to specify how to compare the selected items and the items in the data grid */ + customComparisonFunction?: ( + item1: AttributeData, + item2: AttributeData, + ) => boolean; + /** Renders buttons allowing to perform actions on selection, `onClick` is called with selection array. */ selectionActions?: ButtonProps[]; @@ -198,6 +204,7 @@ export const DataGrid: React.FC = ({ selectable = false, fieldsSelectable = false, selected, + customComparisonFunction = (item1, item2) => item1 === item2, selectionActions = [], sort, title = "", @@ -302,11 +309,14 @@ export const DataGrid: React.FC = ({ const handleSelect = (attributeData: AttributeData) => { const currentlySelected = selectedState || []; - const isAttributeDataCurrentlySelected = - currentlySelected.includes(attributeData); + const isAttributeDataCurrentlySelected = currentlySelected.find((element) => + customComparisonFunction(element, attributeData), + ); const newSelectedState = isAttributeDataCurrentlySelected - ? [...currentlySelected].filter((a) => a !== attributeData) + ? [...currentlySelected].filter( + (a) => !customComparisonFunction(a, attributeData), + ) : [...currentlySelected, attributeData]; setSelectedState(newSelectedState); @@ -431,6 +441,7 @@ export const DataGrid: React.FC = ({ setEditingState={setEditingState} selectable={selectable} selectedRows={selectedState || []} + customComparisonFunction={customComparisonFunction} sortDirection={sortDirection} sortField={sortField} urlFields={urlFields} @@ -765,6 +776,10 @@ export type DataGridBodyProps = { sortDirection: "ASC" | "DESC" | undefined; sortField: string | undefined; urlFields: string[]; + customComparisonFunction?: ( + item1: AttributeData, + item2: AttributeData, + ) => boolean; }; /** @@ -792,6 +807,7 @@ export const DataGridBody: React.FC = ({ renderableRows, selectable, selectedRows, + customComparisonFunction = (item1, item2) => item1 === item2, setEditingState, sortDirection, sortField, @@ -802,7 +818,9 @@ export const DataGridBody: React.FC = ({ + customComparisonFunction(element, rowData), + ), })} > {selectable && ( @@ -814,7 +832,11 @@ export const DataGridBody: React.FC = ({ > + customComparisonFunction(element, rowData), + ) || false + } count={count} handleSelect={handleSelect} labelSelect={labelSelect} From 03f225d08560321913ac194cd977cbc4e56ea8fa Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Tue, 28 May 2024 16:43:30 +0200 Subject: [PATCH 4/5] :memo: - feat: Add story for custom comparison in DataGrid --- .../data/datagrid/datagrid.stories.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/components/data/datagrid/datagrid.stories.tsx b/src/components/data/datagrid/datagrid.stories.tsx index e9ffe174..30af66e6 100644 --- a/src/components/data/datagrid/datagrid.stories.tsx +++ b/src/components/data/datagrid/datagrid.stories.tsx @@ -218,9 +218,15 @@ export const JSONPlaceholderExample: Story = { ]} selected={ // SelectableRows story - args.selectable && - objectList.length > 0 && [objectList[1], objectList[3], objectList[4]] + args.selected || + (args.selectable && + objectList.length > 0 && [ + objectList[1], + objectList[3], + objectList[4], + ]) } + customComparisonFunction={args.customComparisonFunction} onPageChange={setPage} onPageSizeChange={setPageSize} onSort={(field) => setSort(field)} @@ -275,6 +281,44 @@ export const SelectableRows: Story = { }, }; +export const SelectableRowsWithCustomMatchFunction: Story = { + ...JSONPlaceholderExample, + args: { + ...JSONPlaceholderExample.args, + fields: ["userId", "id", "title"], + selectable: true, + selectionActions: [ + { + children: "Aanmaken", + name: "create", + onClick: ({ detail }) => { + alert(`${detail.length} items selected.`); + }, + }, + ], + selected: [ + { + userId: 1, + id: 1, + title: + "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto", + }, + { + userId: 1, + id: 5, + title: "nesciunt quas odio", + body: "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque", + }, + ], + customComparisonFunction: (item1, item2) => item1.id === item2.id, + }, + argTypes: { + onSelect: { action: "onSelect" }, + onSelectionChange: { action: "onSelectionChange" }, + }, +}; + export const EditableRows: Story = { ...JSONPlaceholderExample, args: { From d4897e6e6e1559f610b548b957738877ef5ab8a3 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Mon, 3 Jun 2024 12:46:33 +0200 Subject: [PATCH 5/5] :ok_hand: - feat: PR Feedback --- .../attributetable/attributetable.stories.tsx | 23 +++++-- .../data/attributetable/attributetable.tsx | 62 +++++++++++-------- .../data/datagrid/datagrid.stories.tsx | 4 +- src/components/data/datagrid/datagrid.tsx | 26 +++----- src/lib/data/attributedata.ts | 10 +++ 5 files changed, 75 insertions(+), 50 deletions(-) diff --git a/src/components/data/attributetable/attributetable.stories.tsx b/src/components/data/attributetable/attributetable.stories.tsx index f88303ea..e9e9d21b 100644 --- a/src/components/data/attributetable/attributetable.stories.tsx +++ b/src/components/data/attributetable/attributetable.stories.tsx @@ -13,9 +13,9 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const AttributeTableComponent: Story = { +export const LabeledAttributeTableComponent: Story = { args: { - object: { + labeledObject: { url: { label: "Url", value: "https://www.example.com" }, omschrijving: { label: "Omschrijving", value: "Afvalpas vervangen" }, zaaktype: { label: "Zaaktype", value: "https://www.example.com" }, @@ -28,9 +28,9 @@ export const AttributeTableComponent: Story = { }, }; -export const AttributeTableComponentWithNodes: Story = { +export const LabeledAttributeTableComponentWithNodes: Story = { args: { - object: { + labeledObject: { button: { label: "A button!", value: ( @@ -55,3 +55,18 @@ export const AttributeTableComponentWithNodes: Story = { }, }, }; + +export const AttributeTableComponent: Story = { + args: { + object: { + url: "https://www.example.com", + omschrijving: "Afvalpas vervangen", + zaaktype: "https://www.example.com", + versie: 2, + opmerkingen: null, + actief: false, + toekomstig: false, + concept: true, + }, + }, +}; diff --git a/src/components/data/attributetable/attributetable.tsx b/src/components/data/attributetable/attributetable.tsx index 2582480e..2748b328 100644 --- a/src/components/data/attributetable/attributetable.tsx +++ b/src/components/data/attributetable/attributetable.tsx @@ -1,55 +1,63 @@ -import React, { ReactNode } from "react"; +import React from "react"; -import { Attribute } from "../../../lib"; +import { + Attribute, + AttributeData, + LabeledAttributeData, + field2Title, + isPrimitive, +} from "../../../lib"; import { Value } from "../value"; import "./attributetable.scss"; -export type ValueType = ReactNode; -export type LabelType = string | ReactNode; -export type FieldData = { - label: LabelType; - value: ValueType; +export type AttributeTableProps = { + object?: AttributeData; + labeledObject?: LabeledAttributeData; + fields?: Array; }; -export type ObjectData = Record; -export type ObjectTableProps = { - object: ObjectData; - fields?: Array; +export type AttributeTableRowProps = { + object?: AttributeData; + labeledObject?: LabeledAttributeData; + field: keyof LabeledAttributeData | keyof AttributeData; }; -export type ObjectTableRowProps = { - object: ObjectData; - field: keyof ObjectData; -}; - -export const AttributeTable: React.FC = ({ +export const AttributeTable: React.FC = ({ object = {}, - fields = Object.keys(object), + labeledObject = {}, + fields = Object.keys(object).concat(Object.keys(labeledObject)), ...props }) => (
{fields.map((field) => ( - + ))}
); -export const AttributeTableRow: React.FC = ({ - object, +export const AttributeTableRow: React.FC = ({ + object = {}, + labeledObject = {}, field, }) => { - let value = object[field].value; - if ( - ["boolean", "number", "string"].includes(typeof value) || - value === null - ) { + const fieldInObject = Object.keys(object).includes(field); + let value = fieldInObject ? object[field] : labeledObject[field].value; + + if (isPrimitive(value) || value === null) { value = ; } + const label = fieldInObject ? field2Title(field) : labeledObject[field].label; + return (
- {object[field].label} + {label}
{value}
diff --git a/src/components/data/datagrid/datagrid.stories.tsx b/src/components/data/datagrid/datagrid.stories.tsx index 30af66e6..038ca700 100644 --- a/src/components/data/datagrid/datagrid.stories.tsx +++ b/src/components/data/datagrid/datagrid.stories.tsx @@ -226,7 +226,7 @@ export const JSONPlaceholderExample: Story = { objectList[4], ]) } - customComparisonFunction={args.customComparisonFunction} + equalityChecker={args.equalityChecker} onPageChange={setPage} onPageSizeChange={setPageSize} onSort={(field) => setSort(field)} @@ -311,7 +311,7 @@ export const SelectableRowsWithCustomMatchFunction: Story = { body: "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque", }, ], - customComparisonFunction: (item1, item2) => item1.id === item2.id, + equalityChecker: (item1, item2) => item1.id === item2.id, }, argTypes: { onSelect: { action: "onSelect" }, diff --git a/src/components/data/datagrid/datagrid.tsx b/src/components/data/datagrid/datagrid.tsx index c500ed72..5f5d24b7 100644 --- a/src/components/data/datagrid/datagrid.tsx +++ b/src/components/data/datagrid/datagrid.tsx @@ -111,10 +111,7 @@ export type DataGridProps = { selected?: AttributeData[]; /** Can be used to specify how to compare the selected items and the items in the data grid */ - customComparisonFunction?: ( - item1: AttributeData, - item2: AttributeData, - ) => boolean; + equalityChecker?: (item1: AttributeData, item2: AttributeData) => boolean; /** Renders buttons allowing to perform actions on selection, `onClick` is called with selection array. */ selectionActions?: ButtonProps[]; @@ -204,7 +201,7 @@ export const DataGrid: React.FC = ({ selectable = false, fieldsSelectable = false, selected, - customComparisonFunction = (item1, item2) => item1 === item2, + equalityChecker = (item1, item2) => item1 === item2, selectionActions = [], sort, title = "", @@ -310,13 +307,11 @@ export const DataGrid: React.FC = ({ const currentlySelected = selectedState || []; const isAttributeDataCurrentlySelected = currentlySelected.find((element) => - customComparisonFunction(element, attributeData), + equalityChecker(element, attributeData), ); const newSelectedState = isAttributeDataCurrentlySelected - ? [...currentlySelected].filter( - (a) => !customComparisonFunction(a, attributeData), - ) + ? [...currentlySelected].filter((a) => !equalityChecker(a, attributeData)) : [...currentlySelected, attributeData]; setSelectedState(newSelectedState); @@ -441,7 +436,7 @@ export const DataGrid: React.FC = ({ setEditingState={setEditingState} selectable={selectable} selectedRows={selectedState || []} - customComparisonFunction={customComparisonFunction} + equalityChecker={equalityChecker} sortDirection={sortDirection} sortField={sortField} urlFields={urlFields} @@ -776,10 +771,7 @@ export type DataGridBodyProps = { sortDirection: "ASC" | "DESC" | undefined; sortField: string | undefined; urlFields: string[]; - customComparisonFunction?: ( - item1: AttributeData, - item2: AttributeData, - ) => boolean; + equalityChecker?: (item1: AttributeData, item2: AttributeData) => boolean; }; /** @@ -807,7 +799,7 @@ export const DataGridBody: React.FC = ({ renderableRows, selectable, selectedRows, - customComparisonFunction = (item1, item2) => item1 === item2, + equalityChecker = (item1, item2) => item1 === item2, setEditingState, sortDirection, sortField, @@ -819,7 +811,7 @@ export const DataGridBody: React.FC = ({ key={`${dataGridId}-row-${index}`} className={clsx("mykn-datagrid__row", { "mykn-datagrid__row--selected": !!selectedRows.find((element) => - customComparisonFunction(element, rowData), + equalityChecker(element, rowData), ), })} > @@ -834,7 +826,7 @@ export const DataGridBody: React.FC = ({ amountSelected={amountSelected} checked={ !!selectedRows.find((element) => - customComparisonFunction(element, rowData), + equalityChecker(element, rowData), ) || false } count={count} diff --git a/src/lib/data/attributedata.ts b/src/lib/data/attributedata.ts index 31ee2276..5d1315cf 100644 --- a/src/lib/data/attributedata.ts +++ b/src/lib/data/attributedata.ts @@ -1,3 +1,5 @@ +import { ReactNode } from "react"; + import { ChoiceFieldProps } from "../../components"; /** @@ -15,6 +17,14 @@ export const DEFAULT_URL_FIELDS = [ /** An object with key/value pairs describing various attributes to be presented. */ export type AttributeData = Record; +/** An attribute with its corresponding label */ +export type LabeledAttribute = { + label: string | ReactNode; + value: Attribute | ReactNode; +}; + +export type LabeledAttributeData = Record; + /** A value in `AttributeData`. */ export type Attribute = boolean | number | string | null;