From 17fd02901fbe1f972d6cc69afa392a2ede5736f6 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 1 Nov 2024 15:52:20 +0200 Subject: [PATCH 1/6] [DataTable]: added column groups. --- .../demo/tables/editableTable/TableMode.tsx | 3 +- app/src/demo/tables/editableTable/columns.tsx | 11 +- .../src/table/DataTableCellContainer.tsx | 6 +- .../src/table/DataTableHeaderRow.tsx | 16 ++ .../table/DataTableRowContainer.module.scss | 10 ++ .../src/table/DataTableRowContainer.tsx | 46 +++++- .../columnsGroupsUtils.ts | 40 +++++ uui-core/src/constants/selectors.ts | 7 + uui-core/src/hooks/index.ts | 1 + uui-core/src/hooks/useColumnGroups.ts | 22 +++ uui-core/src/types/tables.ts | 44 ++++++ uui/components/tables/DataTable.tsx | 6 + .../tables/DataTableHeaderCell.module.scss | 2 +- .../DataTableHeaderGroupCell.module.scss | 97 ++++++++++++ .../tables/DataTableHeaderGroupCell.tsx | 138 ++++++++++++++++++ uui/components/tables/DataTableHeaderRow.tsx | 10 ++ uui/settings.ts | 19 +++ uui/settings.types.ts | 14 ++ 18 files changed, 479 insertions(+), 13 deletions(-) create mode 100644 uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts create mode 100644 uui-core/src/hooks/useColumnGroups.ts create mode 100644 uui/components/tables/DataTableHeaderGroupCell.module.scss create mode 100644 uui/components/tables/DataTableHeaderGroupCell.tsx diff --git a/app/src/demo/tables/editableTable/TableMode.tsx b/app/src/demo/tables/editableTable/TableMode.tsx index e7b8b3f6aa..f4ba0be40c 100644 --- a/app/src/demo/tables/editableTable/TableMode.tsx +++ b/app/src/demo/tables/editableTable/TableMode.tsx @@ -3,7 +3,7 @@ import { DataTable } from '@epam/uui'; import { DataRowProps, DataSourceListProps, DataTableState, IControlled } from '@epam/uui-core'; import { DataTableFocusManager } from '@epam/uui-components'; import { ColumnsProps, Task } from './types'; -import { getColumnsTableMode } from './columns'; +import { getColumnsTableMode, groups } from './columns'; export interface TableModeProps extends ColumnsProps { rows: DataRowProps[]; @@ -25,6 +25,7 @@ export function TableMode({ >[] = [ { @@ -35,6 +42,7 @@ export function getColumnsTableMode(columnsProps: ColumnsProps) { }, { key: 'estimate', + group: 'general', textAlign: 'right', caption: 'Estimate', info: 'Estimate in man/days', @@ -62,6 +70,7 @@ export function getColumnsTableMode(columnsProps: ColumnsProps) { }, { key: 'status', + group: 'general', caption: 'Status', width: 160, minWidth: 150, diff --git a/uui-components/src/table/DataTableCellContainer.tsx b/uui-components/src/table/DataTableCellContainer.tsx index de49ccd56f..98a51dd34b 100644 --- a/uui-components/src/table/DataTableCellContainer.tsx +++ b/uui-components/src/table/DataTableCellContainer.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { DataColumnProps, IClickable, IHasCX, IHasRawProps } from '@epam/uui-core'; +import { DataColumnGroupProps, DataColumnProps, IClickable, IHasCX, IHasRawProps } from '@epam/uui-core'; import { FlexCell } from '../layout'; import css from './DataTableCellContainer.module.scss'; @@ -15,7 +15,7 @@ export interface DataTableCellContainerProps extends /** * DataTable column configuration. */ - column: DataColumnProps; + column: DataColumnProps | DataColumnGroupProps; /** * CSS text-align property. */ @@ -38,7 +38,7 @@ export const DataTableCellContainer = React.forwardRef extends React.Component { + const isFirstCell = firstColumnIdx === 0; + const isLastCell = lastColumnIdx === this.props.columns.length - 1; + return this.props.renderGroupCell({ + key: `${group.key}-${idx}`, + group, + isFirstCell, + isLastCell, + value: this.props.value, + onValueChange: this.props.onValueChange, + }); + }; + render() { return ( diff --git a/uui-components/src/table/DataTableRowContainer.module.scss b/uui-components/src/table/DataTableRowContainer.module.scss index e403dda49b..5ad53b770e 100644 --- a/uui-components/src/table/DataTableRowContainer.module.scss +++ b/uui-components/src/table/DataTableRowContainer.module.scss @@ -76,3 +76,13 @@ :global(.uui-scroll-shadow-right) { @include scroll-shadow('inset-inline-end'); } + +.groupColumnsWrapper { + display: flex; + flex-direction: row; +} + +.groupWrapper { + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/uui-components/src/table/DataTableRowContainer.tsx b/uui-components/src/table/DataTableRowContainer.tsx index e1fe8326cb..e8b7d82b62 100644 --- a/uui-components/src/table/DataTableRowContainer.tsx +++ b/uui-components/src/table/DataTableRowContainer.tsx @@ -2,17 +2,21 @@ import React from 'react'; import { DataColumnProps, IClickable, IHasCX, IHasRawProps, uuiMarkers, Link, cx, DndEventHandlers, + DataColumnGroupProps, } from '@epam/uui-core'; import { FlexRow } from '../layout'; import { Anchor } from '../navigation'; import css from './DataTableRowContainer.module.scss'; +import { getGroupsWithColumns, isGroupOfColumns } from './columnsConfigurationModal/columnsGroupsUtils'; export interface DataTableRowContainerProps extends IClickable, IHasCX, IHasRawProps> { + groups?: DataColumnGroupProps[]; columns?: DataColumnProps[]; renderCell?(column: DataColumnProps, idx: number, eventHandlers?: DndEventHandlers): React.ReactNode; + renderGroupCell?(group: DataColumnGroupProps, idx: number, firstColumnIdx: number, lastColumnIdx: number): React.ReactNode; renderConfigButton?(): React.ReactNode; overlays?: React.ReactNode; link?: Link; @@ -52,8 +56,8 @@ function getSectionStyle(columns: DataColumnProps[], minGrow = 0) { grow = Math.max(grow, minGrow); return { - flex: `${grow} 0 ${width}px`, - minWidth: `${width}px`, + flex: `${grow} 0 ${width + 1}px`, + minWidth: `${width + 1}px`, '--uui-dt-cell-border-width': `${CELL_BORDER_WIDTH}px`, }; } @@ -61,12 +65,40 @@ function getSectionStyle(columns: DataColumnProps[], minGrow = 0) { export const DataTableRowContainer = React.forwardRef( (props: DataTableRowContainerProps, ref: React.ForwardedRef) => { const { onPointerDown, onTouchStart, ...restRawProps } = props.rawProps ?? {}; + function renderCells(columns: DataColumnProps[]) { - return columns.reduce((cells, column) => { - const idx = props.columns?.indexOf(column) || 0; - cells.push(props.renderCell(column, idx, { onPointerDown, onTouchStart })); - return cells; - }, []); + if (!props.groups) { + return columns.map((column) => { + const idx = props.columns?.indexOf(column) || 0; + return props.renderCell(column, idx, { onPointerDown, onTouchStart }); + }); + } + + const columnsWithGroups = getGroupsWithColumns(props.groups, columns); + return columnsWithGroups.map((item, index) => { + if (isGroupOfColumns(item)) { + const firstColumnIdx = props.columns?.indexOf(item.columns[0]) || 0; + const lastColumnIdx = props.columns?.indexOf(item.columns[item.columns.length - 1]) || 0; + + return ( +
+ +
{props.renderGroupCell(item.group, index, firstColumnIdx, lastColumnIdx)}
+
+ { + item.columns.map((column) => { + const idx = props.columns?.indexOf(column) || 0; + return props.renderCell(column, idx, { onPointerDown, onTouchStart }); + }) + } +
+
+ ); + } + + const idx = props.columns?.indexOf(item) || 0; + return props.renderCell(item, idx, { onPointerDown, onTouchStart }); + }); } function wrapFixedSection(columns: DataColumnProps[], direction: 'left' | 'right', hasScrollingSection: boolean) { diff --git a/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts b/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts new file mode 100644 index 0000000000..8225c5b84c --- /dev/null +++ b/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts @@ -0,0 +1,40 @@ +import { DataColumnGroupProps, DataColumnProps } from '@epam/uui-core'; + +interface GroupOfColumns { + type: 'group'; + group: DataColumnGroupProps; + columns: DataColumnProps[]; +} + +const getGroupsByKey = (groups: DataColumnGroupProps[]) => { + if (!groups) { + return null; + } + const gRec = groups.reduce>( + (g, group) => ({ ...g, [group.key]: group }), + {}, + ); + return !Object.keys(gRec).length ? null : gRec; +}; + +export const isGroupOfColumns = ( + item: GroupOfColumns | DataColumnProps, +): item is GroupOfColumns => 'type' in item && item.type === 'group'; + +export const getGroupsWithColumns = (groups: DataColumnGroupProps[], columns: DataColumnProps[]) => { + const groupsByKey = getGroupsByKey(groups); + return columns.reduce | DataColumnProps>>((columnsAndGroups, column) => { + if (column.group) { + const lastItem = columnsAndGroups[columnsAndGroups.length - 1]; + if (lastItem && isGroupOfColumns(lastItem) && lastItem.group.key === column.group) { + lastItem.columns.push(column); + return columnsAndGroups; + } + columnsAndGroups.push({ type: 'group', group: groupsByKey[column.group], columns: [column] }); + return columnsAndGroups; + } + + columnsAndGroups.push(column); + return columnsAndGroups; + }, []); +}; diff --git a/uui-core/src/constants/selectors.ts b/uui-core/src/constants/selectors.ts index 23385f347f..4343066f83 100644 --- a/uui-core/src/constants/selectors.ts +++ b/uui-core/src/constants/selectors.ts @@ -82,6 +82,13 @@ export const uuiDataTableHeaderCell = { uuiTableHeaderFoldAllIcon: 'uui-table-header-fold-all-icon', } as const; +export const uuiDataTableHeaderGroupCell = { + uuiTableHeaderGroupCell: 'uui-table-header-group-cell', + uuiTableHeaderGroupCaption: 'uui-table-header-group-caption', + uuiTableHeaderGroupCaptionWrapper: 'uui-table-header-group-caption-wrapper', + uuiTableHeaderGroupCaptionTooltip: 'uui-table-header-group-caption-tooltip', +} as const; + export const uuiScrollShadows = { top: 'uui-scroll-shadow-top', topVisible: 'uui-scroll-shadow-top-visible', diff --git a/uui-core/src/hooks/index.ts b/uui-core/src/hooks/index.ts index bada89fe8d..084ad4fe7b 100644 --- a/uui-core/src/hooks/index.ts +++ b/uui-core/src/hooks/index.ts @@ -9,3 +9,4 @@ export * from './useLayer'; export * from './usePrevious'; export * from './useResizeObserver'; export * from './useDocumentDir'; +export * from './useColumnGroups'; diff --git a/uui-core/src/hooks/useColumnGroups.ts b/uui-core/src/hooks/useColumnGroups.ts new file mode 100644 index 0000000000..83b55b1d6d --- /dev/null +++ b/uui-core/src/hooks/useColumnGroups.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react'; +import { DataColumnGroupProps, DataColumnProps } from '../types'; + +export function useColumnGroups(groups: DataColumnGroupProps[], columns: DataColumnProps[]) { + const groupsRecord = useMemo(() => (groups ?? []) + .reduce>( + (groupsRec, group) => ({ ...groupsRec, [group.key]: group }), + {}, + ), [groups]); + + const visiblGroups = useMemo(() => Object.values( + columns.reduce>((vGroups, column) => { + if (column.group) { + vGroups[column.group] = groupsRecord[column.group]; + return vGroups; + } + return vGroups; + }, {}), + ), [groupsRecord, columns]); + + return { groups: visiblGroups }; +} diff --git a/uui-core/src/types/tables.ts b/uui-core/src/types/tables.ts index a26cc490f2..9312169d3d 100644 --- a/uui-core/src/types/tables.ts +++ b/uui-core/src/types/tables.ts @@ -31,6 +31,39 @@ export type ICanBeFixed = { fix?: 'left' | 'right'; }; +export interface DataColumnGroupProps extends IHasCX, IClickable { + /** + * Unique key to identify the column. Used to reference columns, e.g. in ColumnsConfig. + * Also, used as React key for cells, header cells, and other components inside tables. + */ + key: string; + + /** Column caption. Can be a plain text, or any React Component */ + caption?: React.ReactNode; + + /** Aligns cell and header content horizontally */ + textAlign?: 'left' | 'center' | 'right'; + + /** Info tooltip displayed in the table header */ + info?: React.ReactNode; + + /** Overrides rendering of the whole cell */ + renderCell?(column: DataColumnGroupProps): any; + + /** Render callback for column header tooltip. + * This tooltip will appear on cell hover with 600ms delay. + * + * If omitted, default implementation with column.caption + column.info will be rendered. + * Pass `() => null` to disable tooltip rendering. + */ + renderTooltip?(column: DataColumnGroupProps): React.ReactNode; + + /** + * Overrides rendering of the whole header cell. + */ + renderHeaderCell?(cellProps: DataTableHeaderGroupCellProps): any; +} + export interface DataColumnProps extends ICanBeFixed, IHasCX, IClickable, IHasRawProps, Attributes { /** * Unique key to identify the column. Used to reference columns, e.g. in ColumnsConfig. @@ -38,6 +71,8 @@ export interface DataColumnProps extends */ key: string; + group?: string; + /** Column caption. Can be a plain text, or any React Component */ caption?: React.ReactNode; @@ -159,6 +194,13 @@ export interface DataTableHeaderCellProps extends IEdita renderFilter?: (dropdownProps: IDropdownBodyProps) => React.ReactNode; } +export interface DataTableHeaderGroupCellProps extends IHasCX, IEditable { + key: string; + group: DataColumnGroupProps; + isFirstCell: boolean; + isLastCell: boolean; +} + export type DataTableConfigModalParams = IEditable & { /** Array of all table columns */ columns: DataColumnProps[]; @@ -166,6 +208,7 @@ export type DataTableConfigModalParams = IEditable & { export interface DataTableHeaderRowProps extends IEditable, IHasCX, DataTableColumnsConfigOptions { columns: DataColumnProps[]; + groups?: DataColumnGroupProps[]; selectAll?: ICheckable; /** * Enables collapse/expand all functionality. @@ -173,6 +216,7 @@ export interface DataTableHeaderRowProps extends IEditab showFoldAll?: boolean; onConfigButtonClick?: (params: DataTableConfigModalParams) => any; renderCell?: (props: DataTableHeaderCellProps) => React.ReactNode; + renderGroupCell?: (props: DataTableHeaderGroupCellProps) => React.ReactNode; renderConfigButton?: () => React.ReactNode; } diff --git a/uui/components/tables/DataTable.tsx b/uui/components/tables/DataTable.tsx index 70e91ea85d..d3fb8482a3 100644 --- a/uui/components/tables/DataTable.tsx +++ b/uui/components/tables/DataTable.tsx @@ -4,6 +4,8 @@ import { useColumnsWithFilters } from '../../helpers'; import { ColumnsConfig, DataRowProps, useUuiContext, uuiScrollShadows, useColumnsConfig, IEditable, DataTableState, DataTableColumnsConfigOptions, DataSourceListProps, DataColumnProps, cx, TableFiltersConfig, DataTableRowProps, DataTableSelectedCellData, Overwrite, + DataColumnGroupProps, + useColumnGroups, } from '@epam/uui-core'; import { DataTableHeaderRow, DataTableHeaderRowProps } from './DataTableHeaderRow'; import { DataTableRow, DataTableRowProps as UuiDataTableRowProps } from './DataTableRow'; @@ -26,6 +28,8 @@ export interface DataTableProps extends IEditable[]; + groups?: DataColumnGroupProps[]; + /** Array of all possible columns for the table */ columns: DataColumnProps[]; @@ -73,6 +77,7 @@ export function DataTable(props: React.PropsWithChildren(); const columnsWithFilters = useColumnsWithFilters(props.columns, props.filters); const { columns, config, defaultConfig } = useColumnsConfig(columnsWithFilters, props.value?.columnsConfig); + const { groups } = useColumnGroups(props.groups, columns); const defaultRenderRow = React.useCallback((rowProps: DataRowProps & DataTableRowMods) => { return ( @@ -140,6 +145,7 @@ export function DataTable(props: React.PropsWithChildren + > { + getTooltipContent = (column: DataColumnGroupProps) => ( +
+ + { column.caption } + + { column.info && ( + + { column.info } + + ) } +
+ ); + + getColumnCaption = () => { + const renderTooltip = this.props.group.renderTooltip || this.getTooltipContent; + const captionCx = [ + css.caption, + this.props.textCase === 'upper' && css.upperCase, + uuiDataTableHeaderGroupCell.uuiTableHeaderGroupCaption, + settings.sizes.dataTable.header.row.groupCell.truncate.includes(this.props.size) && css.truncate, + ]; + + return ( +
+ + + { this.props.group.caption } + + +
+ ); + }; + + getLeftPadding = () => { + const { columnsGap, isFirstCell } = this.props; + + if (columnsGap) return isFirstCell ? columnsGap : +columnsGap / 2; + return isFirstCell ? settings.sizes.dataTable.header.row.groupCell.defaults.paddingEdge : settings.sizes.dataTable.header.row.groupCell.defaults.padding; + }; + + getRightPadding = () => { + const { columnsGap, isLastCell } = this.props; + + if (columnsGap) return isLastCell ? columnsGap : +columnsGap / 2; + return isLastCell ? settings.sizes.dataTable.header.row.groupCell.defaults.paddingEdge : settings.sizes.dataTable.header.row.groupCell.defaults.padding; + }; + + renderCellContent = (props: HeaderCellContentProps) => { + const computeStyles = { + '--uui-dt-header-group-cell-padding-start': `${this.getLeftPadding()}px`, + '--uui-dt-header-group-cell-padding-end': `${this.getRightPadding()}px`, + } as React.CSSProperties; + + return ( + { + (props.ref as React.RefCallback)(ref); + } } + cx={ cx( + uuiDataTableHeaderGroupCell.uuiTableHeaderGroupCell, + css.root, + `uui-size-${this.props.size || settings.sizes.dataTable.header.row.groupCell.defaults.size}`, + this.props.isFirstCell && 'uui-dt-header-first-column', + this.props.isLastCell && 'uui-dt-header-last-column', + ) } + rawProps={ { + role: 'columnheader', + ...props.eventHandlers, + } } + style={ computeStyles } + > + { this.getColumnCaption() } + + ); + }; + + render() { + if (this.props.group.renderHeaderCell) { + return this.props.group.renderHeaderCell(this.props); + } + + const computeStyles = { + '--uui-dt-header-group-cell-padding-start': `${this.getLeftPadding()}px`, + '--uui-dt-header-group-cell-padding-end': `${this.getRightPadding()}px`, + width: '100%', + } as React.CSSProperties; + + return ( + + { this.getColumnCaption() } + + ); + } +} diff --git a/uui/components/tables/DataTableHeaderRow.tsx b/uui/components/tables/DataTableHeaderRow.tsx index e7afd97939..e842bf748a 100644 --- a/uui/components/tables/DataTableHeaderRow.tsx +++ b/uui/components/tables/DataTableHeaderRow.tsx @@ -9,6 +9,7 @@ import { settings } from '../../settings'; import './variables.scss'; import css from './DataTableHeaderRow.module.scss'; +import { DataTableHeaderGroupCell } from './DataTableHeaderGroupCell'; export type DataTableHeaderRowProps = CoreDataTableHeaderRowProps & DataTableHeaderRowMods; export const DataTableHeaderRow = withMods( @@ -24,6 +25,15 @@ export const DataTableHeaderRow = withMods ), + renderGroupCell: (props) => ( + + ), renderConfigButton: () => ( Date: Fri, 1 Nov 2024 16:07:09 +0200 Subject: [PATCH 2/6] [DataTable]: fixed tests. --- uui-components/src/table/DataTableRowContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uui-components/src/table/DataTableRowContainer.tsx b/uui-components/src/table/DataTableRowContainer.tsx index e8b7d82b62..faa35ceb96 100644 --- a/uui-components/src/table/DataTableRowContainer.tsx +++ b/uui-components/src/table/DataTableRowContainer.tsx @@ -56,8 +56,8 @@ function getSectionStyle(columns: DataColumnProps[], minGrow = 0) { grow = Math.max(grow, minGrow); return { - flex: `${grow} 0 ${width + 1}px`, - minWidth: `${width + 1}px`, + flex: `${grow} 0 ${width}px`, + minWidth: `${width}px`, '--uui-dt-cell-border-width': `${CELL_BORDER_WIDTH}px`, }; } From b389ebf6a203ae91ef416a5a4cffe4a1fef00b8f Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 1 Nov 2024 16:32:21 +0200 Subject: [PATCH 3/6] [DataTable]: changelog updated. --- changelog.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 817f4e8830..8dc39bde93 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ * [TabButton][VerticalTabButton]: decreased paddings, added gaps `3px` between internal items for all sizes according to design * [Tag]: changed layout - added gaps between internal items, changed padding * [Data Sources]: cursor-based pagination support. More details [here](http://uui.epam.com/documents?id=dataSources-lazy-dataSource&mode=doc&category=dataSources&theme=loveship#using_cursor-based_pagination) +* [DataTable]: groups of columns. **What's Fixed** * [VirtualList]: fixed estimatedHeight calculations in components with pagination @@ -35,9 +36,9 @@ **What's New** * [DataTable]: * [Breaking change]: reworked `isAwaysVisible` column configuration prop. Now it's not make column fixed by default and doesn't forbid to unpin or reorder, it's only disallow to hide this column from table. If you need previous behavior, please use `isLocked` prop. - * Added `isLocked` prop for column configuration. If `true` value provided, makes this column locked, which means that you can't hide, unpin or reorder this column. This column should always be pined. + * Added `isLocked` prop for column configuration. If `true` value provided, makes this column locked, which means that you can't hide, unpin or reorder this column. This column should always be pined. * [DataTable]: `ColumnsConfigurationModal` updated modal width from 420px to 560px according design, 'disabled' state for locked columns is changed to 'readonly', added vertical paddings to multiline column names. -* [PickerInput]: +* [PickerInput]: * Added support of `minCharsToSearch` > 0 with `searchPosition = 'body'`. * Added renderEmpty prop to render custom empty block for depends on various reasons. * `renderNotFonud` prop is deprecated, please use `renderEmpty` instead From 987e8e60cb8a14dae7a4735ebaf86654ab6691ac Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Mon, 4 Nov 2024 17:30:54 +0200 Subject: [PATCH 4/6] [DataTable]: refactored. --- .../demo/tables/editableTable/TableMode.tsx | 4 ++-- app/src/demo/tables/editableTable/columns.tsx | 11 +--------- .../src/table/DataTableHeaderRow.tsx | 2 +- .../src/table/DataTableRowContainer.tsx | 8 ++++--- .../columnsGroupsUtils.ts | 15 ++++++++++--- uui-core/src/hooks/index.ts | 1 - uui-core/src/hooks/useColumnGroups.ts | 22 ------------------- uui-core/src/types/tables.ts | 17 +++++++------- uui/components/tables/DataTable.tsx | 7 +++--- .../DataTableHeaderGroupCell.module.scss | 4 ---- 10 files changed, 32 insertions(+), 59 deletions(-) delete mode 100644 uui-core/src/hooks/useColumnGroups.ts diff --git a/app/src/demo/tables/editableTable/TableMode.tsx b/app/src/demo/tables/editableTable/TableMode.tsx index f4ba0be40c..c2b17ad729 100644 --- a/app/src/demo/tables/editableTable/TableMode.tsx +++ b/app/src/demo/tables/editableTable/TableMode.tsx @@ -3,7 +3,7 @@ import { DataTable } from '@epam/uui'; import { DataRowProps, DataSourceListProps, DataTableState, IControlled } from '@epam/uui-core'; import { DataTableFocusManager } from '@epam/uui-components'; import { ColumnsProps, Task } from './types'; -import { getColumnsTableMode, groups } from './columns'; +import { getColumnsTableMode, columnGroups } from './columns'; export interface TableModeProps extends ColumnsProps { rows: DataRowProps[]; @@ -25,7 +25,7 @@ export function TableMode({ >[] = [ { @@ -42,7 +35,6 @@ export function getColumnsTableMode(columnsProps: ColumnsProps) { }, { key: 'estimate', - group: 'general', textAlign: 'right', caption: 'Estimate', info: 'Estimate in man/days', @@ -70,7 +62,6 @@ export function getColumnsTableMode(columnsProps: ColumnsProps) { }, { key: 'status', - group: 'general', caption: 'Status', width: 160, minWidth: 150, diff --git a/uui-components/src/table/DataTableHeaderRow.tsx b/uui-components/src/table/DataTableHeaderRow.tsx index 40cc6a31d9..c7a72b2043 100644 --- a/uui-components/src/table/DataTableHeaderRow.tsx +++ b/uui-components/src/table/DataTableHeaderRow.tsx @@ -99,7 +99,7 @@ export class DataTableHeaderRow extends React.Component extends IClickable, IHasCX, IHasRawProps> { - groups?: DataColumnGroupProps[]; + /** Columns groups configuration */ + columnGroups?: DataColumnGroupProps[]; columns?: DataColumnProps[]; renderCell?(column: DataColumnProps, idx: number, eventHandlers?: DndEventHandlers): React.ReactNode; + /** Columns group cell render function. */ renderGroupCell?(group: DataColumnGroupProps, idx: number, firstColumnIdx: number, lastColumnIdx: number): React.ReactNode; renderConfigButton?(): React.ReactNode; overlays?: React.ReactNode; @@ -67,14 +69,14 @@ export const DataTableRowContainer = React.forwardRef( const { onPointerDown, onTouchStart, ...restRawProps } = props.rawProps ?? {}; function renderCells(columns: DataColumnProps[]) { - if (!props.groups) { + if (!props.columnGroups) { return columns.map((column) => { const idx = props.columns?.indexOf(column) || 0; return props.renderCell(column, idx, { onPointerDown, onTouchStart }); }); } - const columnsWithGroups = getGroupsWithColumns(props.groups, columns); + const columnsWithGroups = getGroupsWithColumns(props.columnGroups, columns); return columnsWithGroups.map((item, index) => { if (isGroupOfColumns(item)) { const firstColumnIdx = props.columns?.indexOf(item.columns[0]) || 0; diff --git a/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts b/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts index 8225c5b84c..4ce1a4186e 100644 --- a/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts +++ b/uui-components/src/table/columnsConfigurationModal/columnsGroupsUtils.ts @@ -21,16 +21,25 @@ export const isGroupOfColumns = ( item: GroupOfColumns | DataColumnProps, ): item is GroupOfColumns => 'type' in item && item.type === 'group'; -export const getGroupsWithColumns = (groups: DataColumnGroupProps[], columns: DataColumnProps[]) => { - const groupsByKey = getGroupsByKey(groups); +export const getGroupsWithColumns = (columnGroups: DataColumnGroupProps[], columns: DataColumnProps[]) => { + const columnGroupsByKey = getGroupsByKey(columnGroups); + if (!columnGroupsByKey) { + return columns; + } + return columns.reduce | DataColumnProps>>((columnsAndGroups, column) => { if (column.group) { + const group = columnGroupsByKey[column.group]; + if (!group) { + throw new Error(`The '${column.group}' group mentioned in the '${column.key}' column is undefined.`); + } const lastItem = columnsAndGroups[columnsAndGroups.length - 1]; if (lastItem && isGroupOfColumns(lastItem) && lastItem.group.key === column.group) { lastItem.columns.push(column); return columnsAndGroups; } - columnsAndGroups.push({ type: 'group', group: groupsByKey[column.group], columns: [column] }); + + columnsAndGroups.push({ type: 'group', group: columnGroupsByKey[column.group], columns: [column] }); return columnsAndGroups; } diff --git a/uui-core/src/hooks/index.ts b/uui-core/src/hooks/index.ts index 084ad4fe7b..bada89fe8d 100644 --- a/uui-core/src/hooks/index.ts +++ b/uui-core/src/hooks/index.ts @@ -9,4 +9,3 @@ export * from './useLayer'; export * from './usePrevious'; export * from './useResizeObserver'; export * from './useDocumentDir'; -export * from './useColumnGroups'; diff --git a/uui-core/src/hooks/useColumnGroups.ts b/uui-core/src/hooks/useColumnGroups.ts deleted file mode 100644 index 83b55b1d6d..0000000000 --- a/uui-core/src/hooks/useColumnGroups.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useMemo } from 'react'; -import { DataColumnGroupProps, DataColumnProps } from '../types'; - -export function useColumnGroups(groups: DataColumnGroupProps[], columns: DataColumnProps[]) { - const groupsRecord = useMemo(() => (groups ?? []) - .reduce>( - (groupsRec, group) => ({ ...groupsRec, [group.key]: group }), - {}, - ), [groups]); - - const visiblGroups = useMemo(() => Object.values( - columns.reduce>((vGroups, column) => { - if (column.group) { - vGroups[column.group] = groupsRecord[column.group]; - return vGroups; - } - return vGroups; - }, {}), - ), [groupsRecord, columns]); - - return { groups: visiblGroups }; -} diff --git a/uui-core/src/types/tables.ts b/uui-core/src/types/tables.ts index 9312169d3d..a2163e9ab2 100644 --- a/uui-core/src/types/tables.ts +++ b/uui-core/src/types/tables.ts @@ -33,33 +33,32 @@ export type ICanBeFixed = { export interface DataColumnGroupProps extends IHasCX, IClickable { /** - * Unique key to identify the column. Used to reference columns, e.g. in ColumnsConfig. - * Also, used as React key for cells, header cells, and other components inside tables. + * Unique key to identify the columns group. Used to reference columns group. */ key: string; - /** Column caption. Can be a plain text, or any React Component */ + /** Columns group caption. Can be a plain text, or any React Component */ caption?: React.ReactNode; - /** Aligns cell and header content horizontally */ + /** Aligns columns group header content horizontally */ textAlign?: 'left' | 'center' | 'right'; /** Info tooltip displayed in the table header */ info?: React.ReactNode; - /** Overrides rendering of the whole cell */ + /** Overrides rendering of the whole columns group cell */ renderCell?(column: DataColumnGroupProps): any; - /** Render callback for column header tooltip. + /** Render callback for columns group header tooltip. * This tooltip will appear on cell hover with 600ms delay. * - * If omitted, default implementation with column.caption + column.info will be rendered. + * If omitted, default implementation with columnGroup.caption + columnGroup.info will be rendered. * Pass `() => null` to disable tooltip rendering. */ renderTooltip?(column: DataColumnGroupProps): React.ReactNode; /** - * Overrides rendering of the whole header cell. + * Overrides rendering of the whole columns group header cell. */ renderHeaderCell?(cellProps: DataTableHeaderGroupCellProps): any; } @@ -208,7 +207,7 @@ export type DataTableConfigModalParams = IEditable & { export interface DataTableHeaderRowProps extends IEditable, IHasCX, DataTableColumnsConfigOptions { columns: DataColumnProps[]; - groups?: DataColumnGroupProps[]; + columnGroups?: DataColumnGroupProps[]; selectAll?: ICheckable; /** * Enables collapse/expand all functionality. diff --git a/uui/components/tables/DataTable.tsx b/uui/components/tables/DataTable.tsx index d3fb8482a3..4909482a4a 100644 --- a/uui/components/tables/DataTable.tsx +++ b/uui/components/tables/DataTable.tsx @@ -5,7 +5,6 @@ import { ColumnsConfig, DataRowProps, useUuiContext, uuiScrollShadows, useColumnsConfig, IEditable, DataTableState, DataTableColumnsConfigOptions, DataSourceListProps, DataColumnProps, cx, TableFiltersConfig, DataTableRowProps, DataTableSelectedCellData, Overwrite, DataColumnGroupProps, - useColumnGroups, } from '@epam/uui-core'; import { DataTableHeaderRow, DataTableHeaderRowProps } from './DataTableHeaderRow'; import { DataTableRow, DataTableRowProps as UuiDataTableRowProps } from './DataTableRow'; @@ -28,7 +27,8 @@ export interface DataTableProps extends IEditable[]; - groups?: DataColumnGroupProps[]; + /** Array of all possible column groups for the table */ + columnGroups?: DataColumnGroupProps[]; /** Array of all possible columns for the table */ columns: DataColumnProps[]; @@ -77,7 +77,6 @@ export function DataTable(props: React.PropsWithChildren(); const columnsWithFilters = useColumnsWithFilters(props.columns, props.filters); const { columns, config, defaultConfig } = useColumnsConfig(columnsWithFilters, props.value?.columnsConfig); - const { groups } = useColumnGroups(props.groups, columns); const defaultRenderRow = React.useCallback((rowProps: DataRowProps & DataTableRowMods) => { return ( @@ -145,7 +144,7 @@ export function DataTable(props: React.PropsWithChildren Date: Mon, 4 Nov 2024 17:32:17 +0200 Subject: [PATCH 5/6] [DataTable]: one more fix. --- app/src/demo/tables/editableTable/TableMode.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/demo/tables/editableTable/TableMode.tsx b/app/src/demo/tables/editableTable/TableMode.tsx index c2b17ad729..e7b8b3f6aa 100644 --- a/app/src/demo/tables/editableTable/TableMode.tsx +++ b/app/src/demo/tables/editableTable/TableMode.tsx @@ -3,7 +3,7 @@ import { DataTable } from '@epam/uui'; import { DataRowProps, DataSourceListProps, DataTableState, IControlled } from '@epam/uui-core'; import { DataTableFocusManager } from '@epam/uui-components'; import { ColumnsProps, Task } from './types'; -import { getColumnsTableMode, columnGroups } from './columns'; +import { getColumnsTableMode } from './columns'; export interface TableModeProps extends ColumnsProps { rows: DataRowProps[]; @@ -25,7 +25,6 @@ export function TableMode({ Date: Mon, 4 Nov 2024 17:40:49 +0200 Subject: [PATCH 6/6] [DataTable]: added docs. --- uui-core/src/types/tables.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/uui-core/src/types/tables.ts b/uui-core/src/types/tables.ts index a2163e9ab2..7253c54a56 100644 --- a/uui-core/src/types/tables.ts +++ b/uui-core/src/types/tables.ts @@ -31,6 +31,9 @@ export type ICanBeFixed = { fix?: 'left' | 'right'; }; +/** + * Columns group configuration. + */ export interface DataColumnGroupProps extends IHasCX, IClickable { /** * Unique key to identify the columns group. Used to reference columns group. @@ -70,6 +73,9 @@ export interface DataColumnProps extends */ key: string; + /** + * A unique identifier for a group of columns that establishes a connection between the column and the group of columns. + */ group?: string; /** Column caption. Can be a plain text, or any React Component */ @@ -193,10 +199,26 @@ export interface DataTableHeaderCellProps extends IEdita renderFilter?: (dropdownProps: IDropdownBodyProps) => React.ReactNode; } +/** + * DataTable columns group header cell props. + */ export interface DataTableHeaderGroupCellProps extends IHasCX, IEditable { + /** + * A unique identifier for a group. + */ key: string; + /** + * Columns group configuration. + */ group: DataColumnGroupProps; + /** + * Defines if first column of the group is the first one in the table header. + */ isFirstCell: boolean; + + /** + * Defines if last column of the group is the last one in the table header. + */ isLastCell: boolean; } @@ -207,6 +229,9 @@ export type DataTableConfigModalParams = IEditable & { export interface DataTableHeaderRowProps extends IEditable, IHasCX, DataTableColumnsConfigOptions { columns: DataColumnProps[]; + /** + * Columns group configuration. + */ columnGroups?: DataColumnGroupProps[]; selectAll?: ICheckable; /** @@ -215,6 +240,9 @@ export interface DataTableHeaderRowProps extends IEditab showFoldAll?: boolean; onConfigButtonClick?: (params: DataTableConfigModalParams) => any; renderCell?: (props: DataTableHeaderCellProps) => React.ReactNode; + /** + * Columns group cell render function. + */ renderGroupCell?: (props: DataTableHeaderGroupCellProps) => React.ReactNode; renderConfigButton?: () => React.ReactNode; }