From d9d60a8223e1f70aa3908218e90d888f82ae8d50 Mon Sep 17 00:00:00 2001 From: Douglas Egiemeh Date: Wed, 7 Aug 2024 13:25:09 +0200 Subject: [PATCH] Enable custom configuration settings for the data table manager (#2851) * feat(data table manager): enable custom configuration settings for data table manager * feat(data table manager): remove unused files * feat(data table manager): include test for custom component * feat(data table manager): fix type issue * feat(data table manager): update changeset * feat(data table manager): update story * feat(data table manager): update data table with custom props * feat(data table manager): define data table payload data shape * feat(data table manager): option to change default settings dropdown label * feat(data table manager): expose column settings manager component * feat(data table manager): remove unused console logs * feat(data table manager): refactor columns update * feat(data table manager): restructure payload schema * feat(data table manager): update test * feat(data table manager): remove comments * feat(data table manager): fix searchable bug * feat(data table manager): add description to property definitions * feat(data table manager): add description to property definitions * feat(data table manager): prop description * feat(data table manager): update title type * feat(data table manager): change check for custom column manager * feat(data table manager): visual storybook changes for the custom settings * feat(data table manager): visual storybook changes for the custom settings * feat(data table manager): update reame docs with new prop changes * feat(data table manager): update reame docs to include column manager label override * feat(data table manager): update reame docs * feat(data table manager): update docs * feat(data table manager): update types * feat(data table manager): initiate simple poc for the nested table onclick * feat(data table manager): initiate simple poc for the nested table onclick --------- Co-authored-by: Ddouglasz --- .changeset/spotty-suits-develop.md | 7 + .../components/data-table-manager/README.md | 85 +- .../column-settings-manager/package.json | 4 + .../data-table-manager/package.json | 8 +- .../column-settings-manager.tsx | 2 + .../src/column-settings-manager/index.ts | 2 +- .../data-table-manager/src/constants.ts | 2 + .../custom-settings-manager.spec.js | 34 + .../custom-settings-manager.tsx | 31 + .../custom-settings-manager/export-types.ts | 3 + .../src/custom-settings-manager/index.ts | 2 + .../src/custom-settings-manager/messages.ts | 9 + .../data-table-manager-provider.tsx | 66 +- .../src/data-table-manager.story.js | 814 +++++++++++++++++- .../src/data-table-manager.tsx | 40 +- .../data-table-settings.tsx | 144 +++- .../display-settings-manager.tsx | 2 + .../data-table-manager/src/export-types.ts | 2 + .../data-table-manager/src/index.ts | 1 + .../src/settings-container/index.ts | 1 + .../settings-container/settings-container.tsx | 17 +- .../data-table-manager/src/types.tsx | 75 +- packages/components/data-table/README.md | 3 + .../components/data-table/src/data-row.tsx | 1 + .../components/data-table/src/data-table.tsx | 57 ++ 25 files changed, 1321 insertions(+), 91 deletions(-) create mode 100644 .changeset/spotty-suits-develop.md create mode 100644 packages/components/data-table-manager/column-settings-manager/package.json create mode 100644 packages/components/data-table-manager/src/custom-settings-manager/custom-settings-manager.spec.js create mode 100644 packages/components/data-table-manager/src/custom-settings-manager/custom-settings-manager.tsx create mode 100644 packages/components/data-table-manager/src/custom-settings-manager/export-types.ts create mode 100644 packages/components/data-table-manager/src/custom-settings-manager/index.ts create mode 100644 packages/components/data-table-manager/src/custom-settings-manager/messages.ts diff --git a/.changeset/spotty-suits-develop.md b/.changeset/spotty-suits-develop.md new file mode 100644 index 0000000000..a0cc1d0c6b --- /dev/null +++ b/.changeset/spotty-suits-develop.md @@ -0,0 +1,7 @@ +--- +'@commercetools-uikit/data-table-manager': minor +'@commercetools-uikit/data-table': minor +--- + +This extends the data table manager settings to make provision for additional settings that can allow users add more configurations to the already existing settings of the data table manager. +With the new approach, a user can now add any desired configuration option to the settings dropdown, and also create their own settings interface (a custom settings component) to configure the data table to their desired settings. diff --git a/packages/components/data-table-manager/README.md b/packages/components/data-table-manager/README.md index a7554be55e..e431094830 100644 --- a/packages/components/data-table-manager/README.md +++ b/packages/components/data-table-manager/README.md @@ -112,40 +112,55 @@ export default Example; ## Properties -| Props | Type | Required | Default | Description | -| ---------------------------------------------- | ----------------------------------------------------------- | :------: | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `columns` | `array` | ✅ | | Each object requires a unique `key` which should correspond to property key of the items of `rows` that you want to render under this column, and a `label` which defines the name shown on the header. The list of columns to be rendered. Each column can be customized (see properties below). | -| `columns[].key` | `string` | ✅ | | The unique key of the column that is used to identify your data type. You can use this value to determine which value from a row item should be rendered.
For example, if the data is a list of users, where each user has a `firstName` property, the column key should be `firstName`, which renders the correct value by default. The key can also be some custom or computed value, in which case you need to provide an explicit mapping of the value by implementing either the `itemRendered` function or the column-specific `renderItem` function. | -| `columns[].label` | `node` | ✅ | | The label of the column that will be shown on the column header. | -| `columns[].width` | `string` | | `auto` | Sets a width for this column. Accepts the same values as the ones specified for individual [grid-template-columns](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns).
For example, using `minmax` pairs (e.g. `minmax(200px, 400px)`), a combinations of fraction values (`1fr`/`2fr`/etc), or fixed values such as `200px`. By default, the column grows according to the content and respecting the total table available width. | -| `columns[].align` | `enum`
Possible values:
`'left', 'center', 'right'` | | | Use this to override the table's own `horizontalCellAlignment` prop for this specific column. | -| `columns[].onClick` | `func` | | | A callback function, called when the header cell is clicked.
Signature: `(event) => void` | -| `columns[].renderItem` | `func` | | | A callback function to render the content of cells under this column, overriding the default `itemRenderer` prop of the table.
Signature: `(row: object, isRowCollapsed: boolean) => React.Node` | -| `columns[].headerIcon` | `node` | | | Use this prop to place an `Icon` or `IconButton` on the left of the column label. It is advised to place these types of components through this prop instead of `label`, in order to properly position and align the elements. This is particularly useful for medium-sized icons which require more vertical space than the typography. | -| `columns[].isTruncated` | `bool` | | `false` | Set this to `true` to allow text content of this cell to be truncated with an ellipsis, instead of breaking into multiple lines.
NOTE: when using this option, it is recommended to specify a `width` for the column, because if the table doesn't have enough space for all columns, it will start clipping the columns with _truncated_ content, and if no `width` is set (or the value is set `auto` -- the default) it can shrink until the column disappears completely. By enforcing a minimum width for these columns, the table will respect them and grow horizontally, adding scrollbars if needed. | -| `columns[].isSortable` | `bool` | | `false` | Set this to `true` to show a sorting button, which calls `onSortChange` upon being clicked. You should enable this flag for every column you want to be able to sort. When at least one column is sortable, the table props `sortBy`, `sortDirection` and `onSortChange` should be provided. | -| `columns[].disableResizing` | `bool` | | `false` | Set this to `true` to prevent this column from being manually resized by dragging the edge of the header with a mouse. | -| `columns[].shouldIgnoreRowClick` | `bool` | | `false` | Set this to `true` to prevent click event propagation for this cell. You might want this if you need the column to have its own call-to-action or input while the row also has a defined `onRowClick`. | -| `children` | `node` | ✅ | | Any React node. Usually you want to render the `` component.
Note that the child component will implicitly receive the props `columns` and `isCondensed` from the ``. | -| `displaySettings` | `object` | | | The managed display settings of the table. | -| `displaySettings.disableDisplaySettings` | `bool` | | `true` | Set this flag to `false` to show the display settings panel option. | -| `displaySettings.isCondensed` | `bool` | | `false` | Set this to `true` to reduce the paddings of all cells, allowing the table to display more data in less space. | -| `displaySettings.isWrappingText` | `bool` | | `false` | Set this to `true` to allow text in a cell to wrap.
This is required if `disableDisplaySettings` is set to `false`. | -| `displaySettings.primaryButton` | `element` | | | A React element to be rendered as the primary button, useful when the display settings work as a form. | -| `displaySettings.secondaryButton` | `element` | | | A React element to be rendered as the secondary button, useful when the display settings work as a form. | -| `columnManager` | `object` | | | The managed column settings of the table. | -| `columnManager.disableColumnManager` | `bool` | | `true` | Set this to `false` to show the column settings panel option. | -| `columnManager.visibleColumnKeys` | Array of `string` | | | The keys of the visible columns. | -| `columnManager.hideableColumns` | `array` | | | The keys of the visible columns. | -| `columnManager.hideableColumns[].key` | `string` | ✅ | | | -| `columnManager.hideableColumns[].label` | `` | ✅ | | | -| `columnManager.areHiddenColumnsSearchable` | `bool` | | | Set this to `true` to show a search input for the hidden columns panel. | -| `columnManager.searchHiddenColumns` | `func` | | | A callback function, called when the search input for the hidden columns panel changes.
Signature: `(searchTerm: string) => Promise | -| `columnManager.searchHiddenColumnsPlaceholder` | `string` | | | Placeholder value of the search input for the hidden columns panel. | -| `columnManager.primaryButton` | `element` | | | A React element to be rendered as the primary button, useful when the column settings work as a form. | -| `columnManager.secondaryButton` | `element` | | | A React element to be rendered as the secondary button, useful when the column settings work as a form. | -| `onSettingsChange` | `func` | | | A callback function, called when any of the properties of either display settings or column settings is modified.
Signature: `(action: string, nextValue: object) => void` | -| `topBar` | `node` | | | A React node for rendering additional information within the table manager. | -| `managerTheme` | `enum`
Possible values:
`'light', 'dark'` | | | Sets the background theme of the Card that contains the settings | +| Props | Type | Required | Default | Description | +| ------------------------------------------------------ | ----------------------------------------------------------- | :------: | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `columns` | `array` | ✅ | | Each object requires a unique `key` which should correspond to property key of the items of `rows` that you want to render under this column, and a `label` which defines the name shown on the header. The list of columns to be rendered. Each column can be customized (see properties below). | +| `columns[].key` | `string` | ✅ | | The unique key of the column that is used to identify your data type. You can use this value to determine which value from a row item should be rendered.
For example, if the data is a list of users, where each user has a `firstName` property, the column key should be `firstName`, which renders the correct value by default. The key can also be some custom or computed value, in which case you need to provide an explicit mapping of the value by implementing either the `itemRendered` function or the column-specific `renderItem` function. | +| `columns[].label` | `node` | ✅ | | The label of the column that will be shown on the column header. | +| `columns[].width` | `string` | | `auto` | Sets a width for this column. Accepts the same values as the ones specified for individual [grid-template-columns](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns).
For example, using `minmax` pairs (e.g. `minmax(200px, 400px)`), a combinations of fraction values (`1fr`/`2fr`/etc), or fixed values such as `200px`. By default, the column grows according to the content and respecting the total table available width. | +| `columns[].align` | `enum`
Possible values:
`'left', 'center', 'right'` | | | Use this to override the table's own `horizontalCellAlignment` prop for this specific column. | +| `columns[].onClick` | `func` | | | A callback function, called when the header cell is clicked.
Signature: `(event) => void` | +| `columns[].renderItem` | `func` | | | A callback function to render the content of cells under this column, overriding the default `itemRenderer` prop of the table.
Signature: `(row: object, isRowCollapsed: boolean) => React.Node` | +| `columns[].headerIcon` | `node` | | | Use this prop to place an `Icon` or `IconButton` on the left of the column label. It is advised to place these types of components through this prop instead of `label`, in order to properly position and align the elements. This is particularly useful for medium-sized icons which require more vertical space than the typography. | +| `columns[].isTruncated` | `bool` | | `false` | Set this to `true` to allow text content of this cell to be truncated with an ellipsis, instead of breaking into multiple lines.
NOTE: when using this option, it is recommended to specify a `width` for the column, because if the table doesn't have enough space for all columns, it will start clipping the columns with _truncated_ content, and if no `width` is set (or the value is set `auto` -- the default) it can shrink until the column disappears completely. By enforcing a minimum width for these columns, the table will respect them and grow horizontally, adding scrollbars if needed. | +| `columns[].isSortable` | `bool` | | `false` | Set this to `true` to show a sorting button, which calls `onSortChange` upon being clicked. You should enable this flag for every column you want to be able to sort. When at least one column is sortable, the table props `sortBy`, `sortDirection` and `onSortChange` should be provided. | +| `columns[].disableResizing` | `bool` | | `false` | Set this to `true` to prevent this column from being manually resized by dragging the edge of the header with a mouse. | +| `columns[].shouldIgnoreRowClick` | `bool` | | `false` | Set this to `true` to prevent click event propagation for this cell. You might want this if you need the column to have its own call-to-action or input while the row also has a defined `onRowClick`. | +| `children` | `node` | ✅ | | Any React node. Usually you want to render the `` component.
Note that the child component will implicitly receive the props `columns` and `isCondensed` from the ``. | +| `displaySettings` | `object` | | | The managed display settings of the table. | +| `displaySettings.disableDisplaySettings` | `bool` | | `true` | Set this flag to `false` to show the display settings panel option. | +| `displaySettings.isCondensed` | `bool` | | `false` | Set this to `true` to reduce the paddings of all cells, allowing the table to display more data in less space. | +| `displaySettings.isWrappingText` | `bool` | | `false` | Set this to `true` to allow text in a cell to wrap.
This is required if `disableDisplaySettings` is set to `false`. | +| `displaySettings.primaryButton` | `element` | | | A React element to be rendered as the primary button, useful when the display settings work as a form. | +| `displaySettings.secondaryButton` | `element` | | | A React element to be rendered as the secondary button, useful when the display settings work as a form. | +| `columnManager` | `object` | | | The managed column settings of the table. | +| `columnManager.disableColumnManager` | `bool` | | `true` | Set this to `false` to show the column settings panel option. | +| `columnManager.visibleColumnKeys` | Array of `string` | | | The keys of the visible columns. | +| `columnManager.hideableColumns` | `array` | | | The keys of the visible columns. | +| `columnManager.hideableColumns[].key` | `string` | ✅ | | | +| `columnManager.hideableColumns[].label` | `` | ✅ | | | +| `columnManager.areHiddenColumnsSearchable` | `bool` | | | Set this to `true` to show a search input for the hidden columns panel. | +| `columnManager.searchHiddenColumns` | `func` | | | A callback function, called when the search input for the hidden columns panel changes.
Signature: `(searchTerm: string) => Promise` | +| `columnManager.searchHiddenColumnsPlaceholder` | `string` | | | Placeholder value of the search input for the hidden columns panel. | +| `columnManager.primaryButton` | `element` | | | A React element to be rendered as the primary button, useful when the column settings work as a form. | +| `columnManager.secondaryButton` | `element` | | | A React element to be rendered as the secondary button, useful when the column settings work as a form. | +| `columnManager.columnManagerLabel` | `node` | | | This value overrides the default label that will be shown on the column header and dropdown. | +| `onSettingsChange` | `func` | | | A callback function, called when any of the properties of either display settings or column settings is modified.
Signature: `(action: string, nextValue: object) => void` | +| `topBar` | `node` | | | A React node for rendering additional information within the table manager. | +| `managerTheme` | `enum`
Possible values:
`'light', 'dark'` | | | Sets the background theme of the Card that contains the settings | +| `customSettings` | `JSON` | | | A JSON object carrying all the settings for each custom panel. When using the customSettings, each item in the custom setting can be designed to serve as an interface for data table configuration, It should at least have a `key`, `customPanelTitle`, and `customComponent` as property. | +| `customSettings['key']` | `string` | ✅ | | The key of the custom component. Every panel is identifiable by it's unique key provided in the JSON object. | +| `customSettings['key'].customPanelTitle` | `string` | ✅ | | The title of the custom component setting | +| `customSettings['key'].customComponent` | `ReactNode` | | | A component added to the settings interface to provide additional configuration for the data table setting | +| `customColumnManager.disableCustomColumnManager` | `bool` | | `true` | Set this to `false` to show the column settings panel option. | +| `customColumnManager.visibleColumnKeys` | Array of `string` | | | The keys of the visible columns. | +| `customColumnManager.hideableColumns` | `array` | | | The keys of the hideable columns. | +| `customColumnManager.hideableColumns[].key` | `string` | ✅ | | The uniqque key of each hideable column. | +| `customColumnManager.hideableColumns[].label` | `` | ✅ | | The label of each hideable column. | +| `customColumnManager.areHiddenCustomColumnsSearchable` | `bool` | | | Set this to `true` to show a search input for the hidden columns panel. | +| `customColumnManager.searchHiddenColumns` | `func` | | | A callback function, called when the search input for the hidden custom columns panel changes.
Signature: `(searchTerm: string) => Promise` | +| `customColumnManager.searchHiddenColumnsPlaceholder` | `string` | | | Placeholder value of the search input for the hidden custom columns panel. | +| `customColumnManager.primaryButton` | `element` | | | A React element to be rendered as the primary button, useful when the custom column settings work as a form. | +| `customColumnManager.secondaryButton` | `element` | | | A React element to be rendered as the secondary button, useful when the custom column settings work as a form. | > `*`: `DataTableManagerProvider` component accepts the same properties as the `DataTableManager` diff --git a/packages/components/data-table-manager/column-settings-manager/package.json b/packages/components/data-table-manager/column-settings-manager/package.json new file mode 100644 index 0000000000..f978bc7a99 --- /dev/null +++ b/packages/components/data-table-manager/column-settings-manager/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/commercetools-uikit-data-table-manager-column-settings-manager.cjs.js", + "module": "dist/commercetools-uikit-data-table-manager-column-settings-manager.esm.js" +} diff --git a/packages/components/data-table-manager/package.json b/packages/components/data-table-manager/package.json index c557a56795..063a89c44a 100644 --- a/packages/components/data-table-manager/package.json +++ b/packages/components/data-table-manager/package.json @@ -18,9 +18,13 @@ "main": "dist/commercetools-uikit-data-table-manager.cjs.js", "module": "dist/commercetools-uikit-data-table-manager.esm.js", "preconstruct": { - "entrypoints": ["./index.ts", "data-table-manager-provider/index.ts"] + "entrypoints": [ + "./index.ts", + "data-table-manager-provider/index.ts", + "column-settings-manager/index.ts" + ] }, - "files": ["dist", "data-table-manager-provider"], + "files": ["dist", "data-table-manager-provider", "column-settings-manager"], "dependencies": { "@babel/runtime": "^7.20.13", "@babel/runtime-corejs3": "^7.20.13", diff --git a/packages/components/data-table-manager/src/column-settings-manager/column-settings-manager.tsx b/packages/components/data-table-manager/src/column-settings-manager/column-settings-manager.tsx index 3fa0980cc3..cd6311aaec 100644 --- a/packages/components/data-table-manager/src/column-settings-manager/column-settings-manager.tsx +++ b/packages/components/data-table-manager/src/column-settings-manager/column-settings-manager.tsx @@ -42,6 +42,7 @@ export type TColumnData = { }; export type TColumnSettingsManagerProps = { + title?: string; availableColumns: TColumnData[]; selectedColumns: TColumnData[]; onUpdateColumns: (updatedColums: TColumnData[]) => void; @@ -178,6 +179,7 @@ export const ColumnSettingsManager = (props: TColumnSettingsManagerProps) => { return ( ( + +); + +const createTestProps = (customProps) => ({ + managerTheme: 'light', + children: , + customPanelTitle: 'Test Title', + onClose: jest.fn(), + ...customProps, +}); + +describe('CustomSettingsManager', () => { + it('renders with the correct title', () => { + const props = createTestProps(); + render(); + expect(screen.getByText('Test Title')).toBeInTheDocument(); + }); + it('renders with the correct children', () => { + const props = createTestProps(); + render(); + + const customButton = screen.getByText('Custom Component'); + expect(customButton).toBeInTheDocument(); + + fireEvent.click(customButton); + expect(mockCustomComponentClick).toHaveBeenCalled(); + }); +}); diff --git a/packages/components/data-table-manager/src/custom-settings-manager/custom-settings-manager.tsx b/packages/components/data-table-manager/src/custom-settings-manager/custom-settings-manager.tsx new file mode 100644 index 0000000000..2200b6fe55 --- /dev/null +++ b/packages/components/data-table-manager/src/custom-settings-manager/custom-settings-manager.tsx @@ -0,0 +1,31 @@ +import type { MouseEvent, KeyboardEvent, ReactNode } from 'react'; +import SettingsContainer, { type TIntlMessage } from '../settings-container'; +import messages from './messages'; + +export type TCustomSettingsManagerProps = { + onClose: ( + event: MouseEvent | KeyboardEvent + ) => void; + managerTheme?: 'light' | 'dark'; + children: ReactNode; + customPanelTitle: string | TIntlMessage; +}; + +const CustomSettingsManager = (props: TCustomSettingsManagerProps) => { + return ( + <> + + {props.children} + + + ); +}; + +CustomSettingsManager.displayName = 'CustomSettingsManager'; + +export default CustomSettingsManager; diff --git a/packages/components/data-table-manager/src/custom-settings-manager/export-types.ts b/packages/components/data-table-manager/src/custom-settings-manager/export-types.ts new file mode 100644 index 0000000000..b80e839105 --- /dev/null +++ b/packages/components/data-table-manager/src/custom-settings-manager/export-types.ts @@ -0,0 +1,3 @@ +import type { TCustomSettingsManagerProps as CustomSettingsManagerProps } from './custom-settings-manager'; + +export type TCustomSettingsManagerProps = CustomSettingsManagerProps; diff --git a/packages/components/data-table-manager/src/custom-settings-manager/index.ts b/packages/components/data-table-manager/src/custom-settings-manager/index.ts new file mode 100644 index 0000000000..ce3d5200b1 --- /dev/null +++ b/packages/components/data-table-manager/src/custom-settings-manager/index.ts @@ -0,0 +1,2 @@ +export { default } from './custom-settings-manager'; +export * from './export-types'; diff --git a/packages/components/data-table-manager/src/custom-settings-manager/messages.ts b/packages/components/data-table-manager/src/custom-settings-manager/messages.ts new file mode 100644 index 0000000000..37dad8f73f --- /dev/null +++ b/packages/components/data-table-manager/src/custom-settings-manager/messages.ts @@ -0,0 +1,9 @@ +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + closeButtonLabel: { + id: 'UIKit.DataTableManager.CustomSettingsManager.closeButtonLabel', + description: 'Label for custom settings manager close button.', + defaultMessage: 'Close', + }, +}); diff --git a/packages/components/data-table-manager/src/data-table-manager-provider/data-table-manager-provider.tsx b/packages/components/data-table-manager/src/data-table-manager-provider/data-table-manager-provider.tsx index d886a72f95..e3fee38817 100644 --- a/packages/components/data-table-manager/src/data-table-manager-provider/data-table-manager-provider.tsx +++ b/packages/components/data-table-manager/src/data-table-manager-provider/data-table-manager-provider.tsx @@ -1,17 +1,28 @@ -import { createContext, useContext, useMemo } from 'react'; -import type { TDataTableSettingsProps, TColumnManagerProps } from '../types'; +import { createContext, useContext, useMemo, useState } from 'react'; +import type { + TDataTableSettingsProps, + TColumnManagerProps, + TCustomSettingsProps, + TAdditionalSettings, +} from '../types'; import type { TDataTableManagerColumnProps, TRow } from './types'; +import { TColumnData } from '../column-settings-manager'; export type TDataTableManagerContext = TDataTableSettingsProps & { columns: TDataTableManagerColumnProps[]; isCondensed?: boolean; + customSettingsPayload?: Record; + customColumns?: TColumnData[]; + debug: boolean; // TODO - remove when nested rows are implemented }; const DataTableManagerContext = createContext({ columns: [], displaySettings: undefined, isCondensed: true, + debug: false, + additionalSettings: {}, }); export const useDataTableManagerContext = () => { @@ -33,6 +44,11 @@ export const DataTableManagerProvider = ({ topBar, onSettingsChange, columnManager, + customSettings, + selectedColumns, + customColumnManager, + customColumns, + debug, // TODO - remove when nested rows are implemented }: { children: React.ReactNode; columns: TDataTableManagerColumnProps[]; @@ -40,7 +56,24 @@ export const DataTableManagerProvider = ({ topBar: string; onSettingsChange: () => void; columnManager: TColumnManagerProps; + customSettings?: TCustomSettingsProps[]; + selectedColumns?: TColumnData[]; + customColumnManager?: TColumnManagerProps; + customColumns?: TColumnData[]; + debug: boolean; }) => { + const [additionalSettings, setAdditionalSettings] = useState<{ + [key: string]: unknown; + }>({}); + + const updateCustomSettings = ( + additionalCustomSettings: TAdditionalSettings + ) => { + setAdditionalSettings( + additionalCustomSettings as { [key: string]: unknown } + ); + }; + const decoupledDataTableManagerContext = useMemo(() => { const areDisplaySettingsEnabled = Boolean( displaySettings && !displaySettings.disableDisplaySettings @@ -49,6 +82,12 @@ export const DataTableManagerProvider = ({ const isWrappingText = areDisplaySettingsEnabled && displaySettings!.isWrappingText; + const customSettingsPayload = {} as Record; + customSettings && + Object.entries(customSettings).forEach(([key, settingsPayload]) => { + customSettingsPayload[key] = settingsPayload; + }); + return { columns: columns.map((column) => ({ ...column, @@ -60,9 +99,30 @@ export const DataTableManagerProvider = ({ topBar, onSettingsChange, columnManager, + customSettings, + customSettingsPayload, isCondensed: areDisplaySettingsEnabled && displaySettings!.isCondensed, + updateCustomSettings: (settings: TAdditionalSettings) => + updateCustomSettings(settings), + additionalSettings, + selectedColumns, + customColumnManager, + customColumns, + debug, // TODO - remove when nested rows are implemented }; - }, [columns, displaySettings, topBar, onSettingsChange, columnManager]); + }, [ + displaySettings, + customSettings, + columns, + topBar, + onSettingsChange, + columnManager, + additionalSettings, + selectedColumns, + customColumnManager, + customColumns, + debug, + ]); return ( diff --git a/packages/components/data-table-manager/src/data-table-manager.story.js b/packages/components/data-table-manager/src/data-table-manager.story.js index fc34c15c9f..cd2a7edf26 100644 --- a/packages/components/data-table-manager/src/data-table-manager.story.js +++ b/packages/components/data-table-manager/src/data-table-manager.story.js @@ -1,5 +1,6 @@ import { useState } from 'react'; import { storiesOf } from '@storybook/react'; +import { Value } from 'react-value'; import { text, boolean, select, withKnobs } from '@storybook/addon-knobs/react'; import withReadme from 'storybook-readme/with-readme'; import times from 'lodash/times'; @@ -13,98 +14,136 @@ import Readme from '../README.md'; import { UPDATE_ACTIONS } from './constants'; import DataTableManager from './data-table-manager'; import { DataTableManagerProvider } from '@commercetools-uikit/data-table-manager/data-table-manager-provider'; +import { ColumnSettingsManager } from '@commercetools-uikit/data-table-manager/column-settings-manager'; import Spacings from '@commercetools-uikit/spacings'; +import SelectInput from '@commercetools-uikit/select-input'; +import Grid from '@commercetools-uikit/grid'; +import { designTokens } from '@commercetools-uikit/design-system'; +import ToggleInput from '@commercetools-uikit/toggle-input'; +import Text from '@commercetools-uikit/text'; +import Constraints from '@commercetools-uikit/constraints'; +import PropTypes from 'prop-types'; const items = [ { id: '5e188c29791747d9c54250e2', + isExpanded: false, name: 'Morgan Bean', customRenderer: 'CYCLONICA', phone: '+1 (895) 529-3300', age: 23, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c295ae0bb19afbb115f', + isExpanded: false, name: 'Franklin Cochran', customRenderer: 'TINGLES', phone: '+1 (835) 571-3268', age: 36, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c298f0ea901553c517f', + isExpanded: false, name: 'Salazar Craig', customRenderer: 'ECRAZE', phone: '+1 (944) 445-2594', age: 21, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c29b09bb748df833ed0', + isExpanded: false, name: 'Pamela Noble', customRenderer: 'FILODYNE', phone: '+1 (875) 421-3328', age: 34, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c29bc14e3b97ab2ad7d', + isExpanded: false, name: 'Terra Morrow', customRenderer: 'DAISU', phone: '+1 (807) 436-2026', age: 30, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c296c9b7cf486a0479c', + isExpanded: false, name: 'Cline Hansen', customRenderer: 'ULTRIMAX', phone: '+1 (934) 402-3675', age: 21, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c29b45c669d8e60303f', + isExpanded: false, name: 'Jefferson Rosario', customRenderer: 'COMTOURS', phone: '+1 (874) 437-2581', age: 32, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c29ca865647af147b4a', + isExpanded: false, name: 'Tania Waller', customRenderer: 'DOGSPA', phone: '+1 (964) 585-3040', age: 35, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c2910b83f907e9c66ab', + isExpanded: false, name: 'Butler Shepard', customRenderer: 'HOUSEDOWN', phone: '+1 (888) 434-2153', age: 21, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, { id: '5e188c29a9ece9123d6a87a1', + isExpanded: false, name: 'Diana Wise', customRenderer: 'SPEEDBOLT', phone: '+1 (992) 535-2912', age: 27, about: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', + itemDetails: + 'More details rendered in the nested item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu dictum varius duis at consectetur lorem donec.', }, ]; @@ -130,11 +169,6 @@ const initialHiddenColumns = times(15, (num) => ({ })); const initialVisibleColumns = [ - { - key: 'name', - label: 'Name', - isSortable: true, - }, { key: 'customRenderer', label: 'Custom Column', @@ -171,8 +205,60 @@ storiesOf('Components|DataTable', module) visibleColumnKeys: initialVisibleColumns.map(({ key }) => key), }); + const [customTableData, setCustomTableData] = useState({ + columns: initialColumnsState, + visibleColumnKeys: initialVisibleColumns.map(({ key }) => key), + }); + + const customColumnManager = { + areHiddenCustomColumnsSearchable: boolean( + 'areHiddenCustomColumnsSearchable', + true + ), + searchHiddenColumns: (searchTerm) => { + setCustomTableData({ + ...customTableData, + columns: initialColumnsState.filter( + (column) => + customTableData.visibleColumnKeys.includes(column.key) || + column.label + .toLocaleLowerCase() + .includes(searchTerm.toLocaleLowerCase()) + ), + }); + }, + disableCustomColumnManager: boolean('disableCustomColumnManager', false), + visibleColumnKeys: customTableData.visibleColumnKeys, + hideableColumns: customTableData.columns, + }; + + const mappedCustomColumns = customColumnManager.hideableColumns.reduce( + (columns, column) => ({ + ...columns, + [column.key]: column, + }), + {} + ); + const [isCondensed, setIsCondensed] = useState(true); const [isWrappingText, setIsWrappingText] = useState(false); + const [rowClick, setRowClick] = useState(true); + const [rowSelection, setRowSelection] = useState(true); + const [textColor, setTextColor] = useState('black'); + const [disableResize, setDisableResize] = useState(true); + + const updateRowSelection = (newState) => { + setRowSelection(newState); + }; + const updateRowClick = (newState) => { + setRowClick(newState); + }; + const updateTextColor = (newState) => { + setTextColor(newState); + }; + const updateColumnResize = (newState) => { + setDisableResize(newState); + }; const { items: rows, @@ -181,7 +267,7 @@ storiesOf('Components|DataTable', module) onSortChange, } = useSorting(items); - const withRowSelection = boolean('withRowSelection', true); + const withRowSelection = boolean('withRowSelection', false) || rowSelection; const footer = text('footer', 'This is a Footer'); const topBar = text('topBar', 'This is a Top Bar'); const showDisplaySettingsConfirmationButtons = boolean( @@ -215,9 +301,19 @@ storiesOf('Components|DataTable', module) }), {} ); - const visibleColumns = tableData.visibleColumnKeys.map( - (columnKey) => mappedColumns[columnKey] - ); + + const visibleColumns = [ + { + key: 'name', + label: 'Name', + isSortable: true, + disableResizing: disableResize, + renderItem: (row) =>
{row.name}
, + }, + ...tableData.visibleColumnKeys.map( + (columnKey) => mappedColumns[columnKey] + ), + ]; const columnsWithSelect = [ { @@ -242,12 +338,279 @@ storiesOf('Components|DataTable', module) ...visibleColumns, ]; + const getSelectedColumns = (mappedColumns, visibleColumnsKeys) => + visibleColumnsKeys.map((columnKey) => mappedColumns[columnKey]); + + const selectedColumns = getSelectedColumns( + mappedCustomColumns, + customColumnManager.visibleColumnKeys + ); + + const visibleCustomColumns = customTableData.visibleColumnKeys.map( + (columnKey) => mappedColumns[columnKey] + ); + const CustomSettingBlankComponent = (props) => { + return ( + + + This is a demonstration of a blank canvas that can be populated with + any kind of custom settings. + + + + + Column settings + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + isColumnResizable: event.target.checked, + }); + props.additionalSettings.updateColumnResize( + !event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Resizable column (Name) + + + ); + }} + /> +
+ + First column color: + + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + textColor: event.target.value, + }); + props.additionalSettings.updateTextColor( + event.target.value + ); + onChange(event.target.value); + }} + options={[ + { value: 'black', label: 'black' }, + { value: 'red', label: 'Red' }, + { value: 'green', label: 'Green' }, + ]} + /> + + ); + }} + /> +
+
+
+ + + Row settings + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + displayText: event.target.checked, + }); + props.additionalSettings.updateRowSelection( + event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Row selection + + + ); + }} + /> + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + rowClick: event.target.checked, + }); + props.additionalSettings.updateRowClick( + event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Row click + + + ); + }} + /> + + +
+
+ ); + }; + CustomSettingBlankComponent.propTypes = { + additionalSettings: PropTypes.shape({ + updateColumnResize: PropTypes.func, + isColumnResizable: PropTypes.bool, + updateTextColor: PropTypes.func, + textColor: PropTypes.string, + updateRowClick: PropTypes.func, + updateRowSelection: PropTypes.func, + rowClick: PropTypes.bool, + phoneNumberTextColor: PropTypes.string, + displayText: PropTypes.string, + key: PropTypes.string.isRequired, + imageSize: PropTypes.string, + }).isRequired, + updateCustomSettings: PropTypes.func, + }; + + const CustomColumnComponent = (props) => { + return ( + { + props.onUpdateColumns( + nextVisibleColumns, + props.additionalSettings.key + ); + }} + managerTheme={props.managerTheme} + /> + ); + }; + + CustomColumnComponent.propTypes = { + selectedColumns: PropTypes.array, + availableColumns: PropTypes.shape({ + areHiddenCustomColumnsSearchable: PropTypes.bool, + columnManagerLabel: PropTypes.string, + areHiddenColumnsSearchable: PropTypes.bool, + searchHiddenColumns: PropTypes.func, + disableColumnManager: PropTypes.bool, + visibleColumnKeys: PropTypes.array, + hideableColumns: PropTypes.array, + }), + customColumns: PropTypes.array, + onUpdateColumns: PropTypes.func, + onClose: PropTypes.func, + managerTheme: PropTypes.string, + updateCustomSettings: PropTypes.func, + additionalSettings: PropTypes.shape({ + customPanelTitle: PropTypes.string, + key: PropTypes.string.isRequired, + }).isRequired, + }; + + const initialCustomSettings = { + customSettings: { + key: 'customSettings', + customPanelTitle: 'Custom Settings (blank)', + customComponent: CustomSettingBlankComponent, + updateRowSelection, + updateRowClick, + updateTextColor, + updateColumnResize, + }, + customColumnsSettings: { + key: 'customColumnsSettings', + customPanelTitle: 'Custom settings (columns)', + type: 'columnManager', + customComponent: CustomColumnComponent, + visibleColumnKeys: ['name', 'customRenderer', 'phone', 'age'], + }, + }; + + const [customSettings, setCustomSettings] = useState(initialCustomSettings); + const tableSettingsChangeHandler = { [UPDATE_ACTIONS.COLUMNS_UPDATE]: (visibleColumnKeys) => setTableData({ ...tableData, visibleColumnKeys, }), + [UPDATE_ACTIONS.CUSTOM_COLUMNS_UPDATE]: (visibleColumnKeys, key) => { + setCustomTableData({ + ...customTableData, + visibleColumnKeys, + }); + setCustomSettings({ + ...customSettings, + [key]: { + ...customSettings[key], + visibleColumnKeys, + }, + }); + }, + [UPDATE_ACTIONS.CUSTOM_SETTINGS_UPDATE]: (additionalSettings) => + setCustomSettings({ + ...customSettings, + [additionalSettings.key]: { + ...customSettings[additionalSettings.key], + ...additionalSettings, + }, + }), [UPDATE_ACTIONS.IS_TABLE_CONDENSED_UPDATE]: setIsCondensed, [UPDATE_ACTIONS.IS_TABLE_WRAPPING_TEXT_UPDATE]: setIsWrappingText, }; @@ -267,6 +630,7 @@ storiesOf('Components|DataTable', module) : {}; const displaySettings = { + displaySettingsLabel: 'Display Settings', disableDisplaySettings: boolean('disableDisplaySettings', false), isCondensed, isWrappingText, @@ -274,6 +638,7 @@ storiesOf('Components|DataTable', module) }; const columnManager = { + columnManagerLabel: 'Column Manager', areHiddenColumnsSearchable: boolean('areHiddenColumnsSearchable', true), searchHiddenColumns: (searchTerm) => { setTableData({ @@ -293,16 +658,44 @@ storiesOf('Components|DataTable', module) ...columnManagerButtons, }; + const NestedComponent = (props) => { + const item = items.find((item) => item.id === props.id); + return ( +
+

+ Item ID: {item.id} +
+ Item Name: {item.name} +
+ Item Details: +
+ {item.itemDetails} +

+
+ ); + }; + NestedComponent.propTypes = { + id: PropTypes.string.isRequired, + }; + return ( <> { - tableSettingsChangeHandler[action](nextValue); + onSettingsChange={(action, nextValue, key) => { + tableSettingsChangeHandler[action](nextValue, key); }} columnManager={columnManager} displaySettings={displaySettings} + customSettings={customSettings} + selectedColumns={selectedColumns} + customColumnManager={customColumnManager} + customColumns={visibleCustomColumns} managerTheme={select( 'managerTheme', { @@ -317,7 +710,15 @@ storiesOf('Components|DataTable', module) sortedBy={sortedBy} onSortChange={onSortChange} sortDirection={sortDirection} - footer={footer} + onRowClick={ + rowClick + ? (item, index) => alert(`Row click: Row number ${index}`) + : null + } + // TODO - Comment this out to test the nested rows + // onRowClick={rowClick ? (item, index) => item.id : null} + // maxExpandableHeight={100} + // renderNestedRow={(row) => } />
@@ -331,8 +732,61 @@ storiesOf('Components|DataTable', module) visibleColumnKeys: initialVisibleColumns.map(({ key }) => key), }); + const [customTableData, setCustomTableData] = useState({ + columns: initialColumnsState, + visibleColumnKeys: initialVisibleColumns.map(({ key }) => key), + }); + + const customColumnManager = { + areHiddenCustomColumnsSearchable: boolean( + 'areHiddenCustomColumnsSearchable', + true + ), + searchHiddenColumns: (searchTerm) => { + setCustomTableData({ + ...customTableData, + columns: initialColumnsState.filter( + (column) => + customTableData.visibleColumnKeys.includes(column.key) || + column.label + .toLocaleLowerCase() + .includes(searchTerm.toLocaleLowerCase()) + ), + }); + }, + disableCustomColumnManager: boolean('disableCustomColumnManager', false), + visibleColumnKeys: customTableData.visibleColumnKeys, + hideableColumns: customTableData.columns, + }; + + const mappedCustomColumns = customColumnManager.hideableColumns.reduce( + (columns, column) => ({ + ...columns, + [column.key]: column, + }), + {} + ); + const [isCondensed, setIsCondensed] = useState(true); const [isWrappingText, setIsWrappingText] = useState(false); + const [rowClick, setRowClick] = useState(true); + const [rowSelection, setRowSelection] = useState(true); + const [textColor, setTextColor] = useState('black'); + const [disableResize, setDisableResize] = useState(false); + + const updateRowSelection = (newState) => { + setRowSelection(newState); + }; + const updateRowClick = (newState) => { + setRowClick(newState); + }; + const updateTextColor = (newState) => { + setTextColor(newState); + }; + + const updateColumnResize = (newState) => { + setDisableResize(newState); + }; const { items: rows, @@ -341,7 +795,7 @@ storiesOf('Components|DataTable', module) onSortChange, } = useSorting(items); - const withRowSelection = boolean('withRowSelection', true); + const withRowSelection = boolean('withRowSelection', false) || rowSelection; const showDisplaySettingsConfirmationButtons = boolean( 'showDisplaySettingsConfirmationButtons', false @@ -373,9 +827,18 @@ storiesOf('Components|DataTable', module) }), {} ); - const visibleColumns = tableData.visibleColumnKeys.map( - (columnKey) => mappedColumns[columnKey] - ); + const visibleColumns = [ + { + key: 'name', + label: 'Name', + isSortable: true, + disableResizing: disableResize, + renderItem: (row) =>
{row.name}
, + }, + ...tableData.visibleColumnKeys.map( + (columnKey) => mappedColumns[columnKey] + ), + ]; const columnsWithSelect = [ { @@ -400,12 +863,279 @@ storiesOf('Components|DataTable', module) ...visibleColumns, ]; + const getSelectedColumns = (mappedColumns, visibleColumnsKeys) => + visibleColumnsKeys.map((columnKey) => mappedColumns[columnKey]); + + const selectedColumns = getSelectedColumns( + mappedCustomColumns, + customColumnManager.visibleColumnKeys + ); + + const visibleCustomColumns = customTableData.visibleColumnKeys.map( + (columnKey) => mappedColumns[columnKey] + ); + + const CustomSettingBlankComponent = (props) => { + return ( + + + This is a demonstration of a blank canvas that can be populated with + any kind of custom settings. + + + + + Column settings + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + isColumnResizable: event.target.checked, + }); + props.additionalSettings.updateColumnResize( + !event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Resizable column (Name) + + + ); + }} + /> +
+ + First column color: + + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + textColor: event.target.value, + }); + props.additionalSettings.updateTextColor( + event.target.value + ); + onChange(event.target.value); + }} + options={[ + { value: 'black', label: 'black' }, + { value: 'red', label: 'Red' }, + { value: 'green', label: 'Green' }, + ]} + /> + + ); + }} + /> +
+
+
+ + + Row settings + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + displayText: event.target.checked, + }); + props.additionalSettings.updateRowSelection( + event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Row selection + + + ); + }} + /> + { + return ( + + { + props.updateCustomSettings({ + key: props.additionalSettings.key, + rowClick: event.target.checked, + }); + props.additionalSettings.updateRowClick( + event.target.checked + ); + onChange(event.target.checked); + }} + size="small" + /> + + Row click + + + ); + }} + /> + + +
+
+ ); + }; + CustomSettingBlankComponent.propTypes = { + additionalSettings: PropTypes.shape({ + updateColumnResize: PropTypes.func, + isColumnResizable: PropTypes.bool, + updateTextColor: PropTypes.func, + textColor: PropTypes.string, + updateRowClick: PropTypes.func, + updateRowSelection: PropTypes.func, + rowClick: PropTypes.bool, + phoneNumberTextColor: PropTypes.string, + displayText: PropTypes.string, + key: PropTypes.string.isRequired, + imageSize: PropTypes.string, + }).isRequired, + updateCustomSettings: PropTypes.func, + }; + + const CustomColumnComponent = (props) => { + return ( + { + props.onUpdateColumns( + nextVisibleColumns, + props.additionalSettings.key + ); + }} + managerTheme={props.managerTheme} + /> + ); + }; + + CustomColumnComponent.propTypes = { + selectedColumns: PropTypes.array, + availableColumns: PropTypes.shape({ + areHiddenCustomColumnsSearchable: PropTypes.bool, + columnManagerLabel: PropTypes.string, + areHiddenColumnsSearchable: PropTypes.bool, + searchHiddenColumns: PropTypes.func, + disableColumnManager: PropTypes.bool, + visibleColumnKeys: PropTypes.array, + hideableColumns: PropTypes.array, + }), + customColumns: PropTypes.array, + onUpdateColumns: PropTypes.func, + onClose: PropTypes.func, + managerTheme: PropTypes.string, + updateCustomSettings: PropTypes.func, + additionalSettings: PropTypes.shape({ + key: PropTypes.string.isRequired, + }).isRequired, + }; + + const initialCustomSettings = { + customSettings: { + key: 'customSettings', + customPanelTitle: 'Custom Settings (blank)', + customComponent: CustomSettingBlankComponent, + updateRowSelection, + updateRowClick, + updateTextColor, + updateColumnResize, + }, + customColumnsSettings: { + key: 'customColumnsSettings', + customPanelTitle: 'Custom settings (columns)', + type: 'columnManager', + customComponent: CustomColumnComponent, + visibleColumnKeys: ['name', 'customRenderer', 'phone', 'age'], + }, + }; + + const [customSettings, setCustomSettings] = useState(initialCustomSettings); + const tableSettingsChangeHandler = { [UPDATE_ACTIONS.COLUMNS_UPDATE]: (visibleColumnKeys) => setTableData({ ...tableData, visibleColumnKeys, }), + [UPDATE_ACTIONS.CUSTOM_COLUMNS_UPDATE]: (visibleColumnKeys, key) => { + setCustomTableData({ + ...customTableData, + visibleColumnKeys, + }); + setCustomSettings({ + ...customSettings, + [key]: { + ...customSettings[key], + visibleColumnKeys, + }, + }); + }, + [UPDATE_ACTIONS.CUSTOM_SETTINGS_UPDATE]: (additionalSettings) => + setCustomSettings({ + ...customSettings, + [additionalSettings.key]: { + ...customSettings[additionalSettings.key], + ...additionalSettings, + }, + }), [UPDATE_ACTIONS.IS_TABLE_CONDENSED_UPDATE]: setIsCondensed, [UPDATE_ACTIONS.IS_TABLE_WRAPPING_TEXT_UPDATE]: setIsWrappingText, }; @@ -425,6 +1155,7 @@ storiesOf('Components|DataTable', module) : {}; const displaySettings = { + displaySettingsLabel: 'Display Settings', disableDisplaySettings: boolean('disableDisplaySettings', false), isCondensed, isWrappingText, @@ -432,6 +1163,7 @@ storiesOf('Components|DataTable', module) }; const columnManager = { + columnManagerLabel: 'Column Manager', areHiddenColumnsSearchable: boolean('areHiddenColumnsSearchable', true), searchHiddenColumns: (searchTerm) => { setTableData({ @@ -451,14 +1183,48 @@ storiesOf('Components|DataTable', module) ...columnManagerButtons, }; + const NestedComponent = (props) => { + const item = items.find((item) => item.id === props.id); + if (!item) return null; + return ( +
+

+ Item ID: {item.id} + Item Name: {item.name} + Item Details:{' '} + {item.itemDetails} +

+
+ ); + }; + NestedComponent.propTypes = { + id: PropTypes.string.isRequired, + }; + return ( { - tableSettingsChangeHandler[action](nextValue); + onSettingsChange={(action, nextValue, key) => { + tableSettingsChangeHandler[action](nextValue, key); }} columnManager={columnManager} + displaySettings={displaySettings} + customSettings={customSettings} + selectedColumns={selectedColumns} + customColumnManager={customColumnManager} + customColumns={visibleCustomColumns} + managerTheme={select( + 'managerTheme', + { + dark: 'dark', + light: 'light', + }, + 'dark' + )} >
@@ -476,9 +1242,17 @@ storiesOf('Components|DataTable', module) sortedBy={sortedBy} onSortChange={onSortChange} sortDirection={sortDirection} + onRowClick={ + rowClick + ? (item, index) => alert(`Row click: Row number ${index}`) + : null + } + // TODO - Comment this out to test the nested rows + // onRowClick={rowClick ? (item, index) => item.id : null} + // maxExpandableHeight={100} + renderNestedRow={(row) => } /> -

diff --git a/packages/components/data-table-manager/src/data-table-manager.tsx b/packages/components/data-table-manager/src/data-table-manager.tsx index 7dc944f731..89eb5da2f9 100644 --- a/packages/components/data-table-manager/src/data-table-manager.tsx +++ b/packages/components/data-table-manager/src/data-table-manager.tsx @@ -1,7 +1,12 @@ -import { useMemo, cloneElement } from 'react'; +import { useMemo, cloneElement, useState } from 'react'; import Spacings from '@commercetools-uikit/spacings'; import DataTableSettings from './data-table-settings'; -import type { TRow, TColumnProps, TDataTableManagerProps } from './types'; +import type { + TRow, + TColumnProps, + TDataTableManagerProps, + TCustomSettingsProps, +} from './types'; import { useDataTableManagerContext } from '@commercetools-uikit/data-table-manager/data-table-manager-provider'; const DataTableManager = ( @@ -18,7 +23,14 @@ const DataTableManager = ( props.onSettingsChange || dataTableManagerContext.onSettingsChange; const columnManager = props.columnManager || dataTableManagerContext.columnManager; - + const customSettings = + props.customSettings || dataTableManagerContext.customSettings; + const selectedColumns = + props.selectedColumns || dataTableManagerContext.selectedColumns; + const customColumnManager = + props.customColumnManager || dataTableManagerContext.customColumnManager; + const customColumns = + props.customColumns || dataTableManagerContext.customColumns; const areDisplaySettingsEnabled = Boolean( displaySettings && !displaySettings.disableDisplaySettings ); @@ -42,6 +54,22 @@ const DataTableManager = ( [dataTableColumns, areDisplaySettingsEnabled, isWrappingText] ); + const [additionalSettings, setAdditionalSettings] = useState<{ + key: string; + [key: string]: unknown; + }>({ key: '' }); + + const additionalCustomSetting = + dataTableManagerContext.additionalSettings || additionalSettings; + + const updateSettings = (additionalCustomSettings: unknown) => { + setAdditionalSettings( + additionalCustomSettings as { [key: string]: unknown; key: string } + ); + }; + const updateCustomSettings = + dataTableManagerContext.updateCustomSettings || updateSettings; + return ( ( onSettingsChange={onSettingsChange} columnManager={columnManager} displaySettings={displaySettings} + customSettings={customSettings as TCustomSettingsProps[] | undefined} managerTheme="light" + additionalSettings={additionalCustomSetting} + updateCustomSettings={(settings) => updateCustomSettings(settings)} + selectedColumns={selectedColumns ?? []} + customColumnManager={customColumnManager ?? undefined} /> {props.children ? cloneElement(props.children, { columns, + customColumns, isCondensed: areDisplaySettingsEnabled && props.displaySettings!.isCondensed, }) diff --git a/packages/components/data-table-manager/src/data-table-settings/data-table-settings.tsx b/packages/components/data-table-manager/src/data-table-settings/data-table-settings.tsx index 08481a40d1..421821ddc2 100644 --- a/packages/components/data-table-manager/src/data-table-settings/data-table-settings.tsx +++ b/packages/components/data-table-manager/src/data-table-settings/data-table-settings.tsx @@ -9,12 +9,17 @@ import DisplaySettingsManager, { DENSITY_COMPACT, SHOW_HIDE_ON_DEMAND, } from '../display-settings-manager'; -import ColumnSettingsManager from '../column-settings-manager'; +import { ColumnSettingsManager } from '../column-settings-manager'; +import CustomSettingsManager from '../custom-settings-manager'; import messages from './messages'; import DropdownMenu from '@commercetools-uikit/dropdown-menu'; import IconButton from '@commercetools-uikit/icon-button'; import Tooltip from '@commercetools-uikit/tooltip'; -import { TColumnData, TDataTableSettingsProps } from '../types'; +import { + TColumnData, + TDataTableSettingsProps, + TCustomSettingsProps, +} from '../types'; export type TSelectChangeEvent = { target: { @@ -36,31 +41,56 @@ const TopBarContainer = styled.div` `; export const getDropdownOptions = ({ + areCustomColumnSettingsEnabled, areColumnSettingsEnabled, areDisplaySettingsEnabled, + customSettings, + columnManagerLabel, + displaySettingsLabel, formatMessage, }: { + areCustomColumnSettingsEnabled: boolean; areColumnSettingsEnabled: boolean; areDisplaySettingsEnabled: boolean; + customSettings?: TCustomSettingsProps[]; + columnManagerLabel?: string; + displaySettingsLabel?: string; formatMessage: (message: MessageDescriptor) => string; -}) => [ - ...(areColumnSettingsEnabled - ? [ - { - value: COLUMN_MANAGER, - label: formatMessage(messages.columnManagerOption), - }, - ] - : []), - ...(areDisplaySettingsEnabled - ? [ - { - value: DISPLAY_SETTINGS, - label: formatMessage(messages.displaySettingsOption), - }, - ] - : []), -]; +}) => { + return [ + ...(areColumnSettingsEnabled + ? [ + { + value: COLUMN_MANAGER, + label: columnManagerLabel + ? columnManagerLabel + : formatMessage(messages.columnManagerOption), + }, + ] + : []), + ...(customSettings + ? Object.entries(customSettings).map(([key, customSetting]) => { + return customSetting.type === COLUMN_MANAGER && + !areCustomColumnSettingsEnabled + ? undefined + : { + value: key, + label: customSetting.customPanelTitle, + }; + }) + : []), + ...(areDisplaySettingsEnabled + ? [ + { + value: DISPLAY_SETTINGS, + label: displaySettingsLabel + ? displaySettingsLabel + : formatMessage(messages.displaySettingsOption), + }, + ] + : []), + ].filter((option) => option !== undefined); +}; export const getMappedColumns = (columns: TColumnData[] = []) => columns.reduce( @@ -83,6 +113,12 @@ const DataTableSettings = (props: TDataTableSettingsProps) => { const areColumnSettingsEnabled = Boolean( props.columnManager && !props.columnManager.disableColumnManager ); + + const areCustomColumnSettingsEnabled = Boolean( + props.customColumnManager && + !props.customColumnManager?.disableCustomColumnManager + ); + warning( areDisplaySettingsEnabled || areColumnSettingsEnabled ? typeof props.onSettingsChange === 'function' @@ -94,9 +130,14 @@ const DataTableSettings = (props: TDataTableSettingsProps) => { const [openedPanelId, setOpenedPanelId] = useState( null ); + const dropdownOptions: TDropdownOption[] = getDropdownOptions({ + areCustomColumnSettingsEnabled, areDisplaySettingsEnabled, areColumnSettingsEnabled, + customSettings: props.customSettings, + columnManagerLabel: props.columnManager?.columnManagerLabel, + displaySettingsLabel: props.displaySettings?.displaySettingsLabel, formatMessage: intl.formatMessage, }); @@ -150,6 +191,7 @@ const DataTableSettings = (props: TDataTableSettingsProps) => { {openedPanelId === DISPLAY_SETTINGS && ( { props.onSettingsChange?.( @@ -169,6 +211,7 @@ const DataTableSettings = (props: TDataTableSettingsProps) => { {openedPanelId === COLUMN_MANAGER && ( { managerTheme={props.managerTheme} /> )} + {props.customSettings && + Object.entries(props.customSettings).map(([key, customSetting]) => { + if (!customSetting.key) { + throw new Error( + 'ui-kit/DataTableManager: missing: `key` prop, `customSettings` must be a JSON in the format Record.' + ); + } + const CustomComponent = customSetting.customComponent; + return ( + key === openedPanelId && ( +
+ {customSetting.type === COLUMN_MANAGER ? ( + CustomComponent && ( + { + const keysOfVisibleColumns = nextVisibleColumns.map( + (visibleColumn) => visibleColumn.key + ); + props.onSettingsChange?.( + UPDATE_ACTIONS.CUSTOM_COLUMNS_UPDATE, + keysOfVisibleColumns, + key + ); + }} + /> + ) + ) : ( + + {CustomComponent && ( + { + props.onSettingsChange?.( + UPDATE_ACTIONS.CUSTOM_SETTINGS_UPDATE, + settings + ); + }} + additionalSettings={{ + ...customSetting, + ...props.additionalSettings, + }} + /> + )} + + )} +
+ ) + ); + })}
); }; diff --git a/packages/components/data-table-manager/src/display-settings-manager/display-settings-manager.tsx b/packages/components/data-table-manager/src/display-settings-manager/display-settings-manager.tsx index 1d51788d8a..a72fe9a729 100644 --- a/packages/components/data-table-manager/src/display-settings-manager/display-settings-manager.tsx +++ b/packages/components/data-table-manager/src/display-settings-manager/display-settings-manager.tsx @@ -21,6 +21,7 @@ import { } from './constants'; export type TDensityManagerProps = { + title?: string; isCondensed?: boolean; isWrappingText?: boolean; primaryButton?: ReactElement; @@ -44,6 +45,7 @@ const DensityManager = (props: TDensityManagerProps) => { return ( ; +}; + type TSettingsContainerProps = { - title: MessageDescriptor & { - values?: Record; - }; + title?: TIntlMessage; closeButtonLabel: MessageDescriptor & { values?: Record; }; @@ -23,6 +25,7 @@ type TSettingsContainerProps = { secondaryButton?: ReactElement; children: ReactNode; containerTheme?: 'light' | 'dark'; + customSettingsTitle?: string | TIntlMessage; }; const HeaderContainer = styled.div` @@ -45,7 +48,13 @@ const SettingsContainer = (props: TSettingsContainerProps) => { - + {props.customSettingsTitle ? ( + + {props.customSettingsTitle} + + ) : ( + + )}