Skip to content

Commit

Permalink
Merge pull request #2599 from epam/feature/data-table-columns-group
Browse files Browse the repository at this point in the history
[DataTable]: columns group.
  • Loading branch information
Kuznietsov authored Nov 5, 2024
2 parents 7b5f2da + c240c34 commit 2234d37
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 9 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [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)
* [TimelineScale]: added bottom/top month text customisation.
* [TimelineScale]: customisation of today line height was added.
* [DataTable]: groups of columns.

**What's Fixed**
* [VirtualList]: fixed estimatedHeight calculations in components with pagination
Expand Down
6 changes: 3 additions & 3 deletions uui-components/src/table/DataTableCellContainer.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,7 +15,7 @@ export interface DataTableCellContainerProps extends
/**
* DataTable column configuration.
*/
column: DataColumnProps;
column: DataColumnProps | DataColumnGroupProps;
/**
* CSS text-align property.
*/
Expand All @@ -38,7 +38,7 @@ export const DataTableCellContainer = React.forwardRef<HTMLDivElement, DataTable
return (
<FlexCell
{ ...props.column }
minWidth={ props.column.width }
minWidth={ 'width' in props.column ? props.column.width : undefined }
rawProps={ props.rawProps }
cx={ ['uui-dt-vars', css.root, props.column.cx, props.cx] }
onClick={ props.onClick }
Expand Down
16 changes: 16 additions & 0 deletions uui-components/src/table/DataTableHeaderRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {
DataSourceState, DataColumnProps, DataTableHeaderRowProps, DropdownBodyProps, Lens, DropParams, getOrderBetween, DataTableState,
DataColumnGroupProps,
} from '@epam/uui-core';
import { DataTableRowContainer } from './DataTableRowContainer';
import css from './DataTableHeaderRow.module.scss';
Expand Down Expand Up @@ -79,14 +80,29 @@ export class DataTableHeaderRow<TItem, TId> extends React.Component<DataTableHea
});
};

renderGroupCell = (group: DataColumnGroupProps, idx: number, firstColumnIdx: number, lastColumnIdx: number) => {
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 (
<DataTableRowContainer
cx={ [
css.root, this.props.cx, uuiDataTableHeaderRow.uuiTableHeaderRow,
] }
columnGroups={ this.props.columnGroups }
columns={ this.props.columns }
renderCell={ this.renderCell }
renderGroupCell={ this.renderGroupCell }
rawProps={ { role: 'row' } }
renderConfigButton={ this.props.onConfigButtonClick && this.props.renderConfigButton }
/>
Expand Down
10 changes: 10 additions & 0 deletions uui-components/src/table/DataTableRowContainer.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
44 changes: 39 additions & 5 deletions uui-components/src/table/DataTableRowContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ 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<TItem, TId, TFilter>
extends IClickable,
IHasCX,
IHasRawProps<React.HTMLAttributes<HTMLAnchorElement | HTMLDivElement | HTMLButtonElement>> {
/** Columns groups configuration */
columnGroups?: DataColumnGroupProps[];
columns?: DataColumnProps<TItem, TId, TFilter>[];
renderCell?(column: DataColumnProps<TItem, TId, TFilter>, 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;
link?: Link;
Expand Down Expand Up @@ -61,12 +67,40 @@ function getSectionStyle(columns: DataColumnProps[], minGrow = 0) {
export const DataTableRowContainer = React.forwardRef(
<TItem, TId, TFilter>(props: DataTableRowContainerProps<TItem, TId, TFilter>, ref: React.ForwardedRef<HTMLDivElement>) => {
const { onPointerDown, onTouchStart, ...restRawProps } = props.rawProps ?? {};

function renderCells(columns: DataColumnProps<TItem, TId, TFilter>[]) {
return columns.reduce<React.ReactNode[]>((cells, column) => {
const idx = props.columns?.indexOf(column) || 0;
cells.push(props.renderCell(column, idx, { onPointerDown, onTouchStart }));
return cells;
}, []);
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.columnGroups, 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 (
<div style={ getSectionStyle(item.columns) } className={ cx({ [css.section]: true, [css.groupWrapper]: true }) }>

<div>{props.renderGroupCell(item.group, index, firstColumnIdx, lastColumnIdx)}</div>
<div className={ css.groupColumnsWrapper }>
{
item.columns.map((column) => {
const idx = props.columns?.indexOf(column) || 0;
return props.renderCell(column, idx, { onPointerDown, onTouchStart });
})
}
</div>
</div>
);
}

const idx = props.columns?.indexOf(item) || 0;
return props.renderCell(item, idx, { onPointerDown, onTouchStart });
});
}

function wrapFixedSection(columns: DataColumnProps<TItem, TId, TFilter>[], direction: 'left' | 'right', hasScrollingSection: boolean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { DataColumnGroupProps, DataColumnProps } from '@epam/uui-core';

interface GroupOfColumns<TItem, TId> {
type: 'group';
group: DataColumnGroupProps;
columns: DataColumnProps<TItem, TId>[];
}

const getGroupsByKey = (groups: DataColumnGroupProps[]) => {
if (!groups) {
return null;
}
const gRec = groups.reduce<Record<string, DataColumnGroupProps>>(
(g, group) => ({ ...g, [group.key]: group }),
{},
);
return !Object.keys(gRec).length ? null : gRec;
};

export const isGroupOfColumns = <TItem, TId>(
item: GroupOfColumns<TItem, TId> | DataColumnProps<TItem, TId>,
): item is GroupOfColumns<TItem, TId> => 'type' in item && item.type === 'group';

export const getGroupsWithColumns = <TItem, TId>(columnGroups: DataColumnGroupProps[], columns: DataColumnProps<TItem, TId>[]) => {
const columnGroupsByKey = getGroupsByKey(columnGroups);
if (!columnGroupsByKey) {
return columns;
}

return columns.reduce<Array<GroupOfColumns<TItem, TId> | DataColumnProps<TItem, TId>>>((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: columnGroupsByKey[column.group], columns: [column] });
return columnsAndGroups;
}

columnsAndGroups.push(column);
return columnsAndGroups;
}, []);
};
7 changes: 7 additions & 0 deletions uui-core/src/constants/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
71 changes: 71 additions & 0 deletions uui-core/src/types/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,53 @@ 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.
*/
key: string;

/** Columns group caption. Can be a plain text, or any React Component */
caption?: React.ReactNode;

/** 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 columns group cell */
renderCell?(column: DataColumnGroupProps): any;

/** Render callback for columns group header tooltip.
* This tooltip will appear on cell hover with 600ms delay.
*
* 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 columns group header cell.
*/
renderHeaderCell?(cellProps: DataTableHeaderGroupCellProps): any;
}

export interface DataColumnProps<TItem = any, TId = any, TFilter = any> extends ICanBeFixed, IHasCX, IClickable, IHasRawProps<HTMLDivElement>, Attributes {
/**
* 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;

/**
* 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 */
caption?: React.ReactNode;

Expand Down Expand Up @@ -159,20 +199,51 @@ export interface DataTableHeaderCellProps<TItem = any, TId = any> extends IEdita
renderFilter?: (dropdownProps: IDropdownBodyProps) => React.ReactNode;
}

/**
* DataTable columns group header cell props.
*/
export interface DataTableHeaderGroupCellProps extends IHasCX, IEditable<DataTableState> {
/**
* 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;
}

export type DataTableConfigModalParams = IEditable<DataSourceState> & {
/** Array of all table columns */
columns: DataColumnProps[];
};

export interface DataTableHeaderRowProps<TItem = any, TId = any> extends IEditable<DataTableState>, IHasCX, DataTableColumnsConfigOptions {
columns: DataColumnProps<TItem, TId>[];
/**
* Columns group configuration.
*/
columnGroups?: DataColumnGroupProps[];
selectAll?: ICheckable;
/**
* Enables collapse/expand all functionality.
* */
showFoldAll?: boolean;
onConfigButtonClick?: (params: DataTableConfigModalParams) => any;
renderCell?: (props: DataTableHeaderCellProps<TItem, TId>) => React.ReactNode;
/**
* Columns group cell render function.
*/
renderGroupCell?: (props: DataTableHeaderGroupCellProps) => React.ReactNode;
renderConfigButton?: () => React.ReactNode;
}

Expand Down
5 changes: 5 additions & 0 deletions uui/components/tables/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useColumnsWithFilters } from '../../helpers';
import {
ColumnsConfig, DataRowProps, useUuiContext, uuiScrollShadows, useColumnsConfig, IEditable, DataTableState, DataTableColumnsConfigOptions,
DataSourceListProps, DataColumnProps, cx, TableFiltersConfig, DataTableRowProps, DataTableSelectedCellData, Overwrite,
DataColumnGroupProps,
} from '@epam/uui-core';
import { DataTableHeaderRow, DataTableHeaderRowProps } from './DataTableHeaderRow';
import { DataTableRow, DataTableRowProps as UuiDataTableRowProps } from './DataTableRow';
Expand All @@ -26,6 +27,9 @@ export interface DataTableProps<TItem, TId, TFilter = any> extends IEditable<Dat
/** Rows that should be rendered in table */
rows?: DataRowProps<TItem, TId>[];

/** Array of all possible column groups for the table */
columnGroups?: DataColumnGroupProps[];

/** Array of all possible columns for the table */
columns: DataColumnProps<TItem, TId>[];

Expand Down Expand Up @@ -140,6 +144,7 @@ export function DataTable<TItem, TId>(props: React.PropsWithChildren<DataTablePr
<div className={ css.stickyHeader } ref={ headerRef }>
<DataTableHeaderRow
columns={ columns }
columnGroups={ props.columnGroups }
onConfigButtonClick={ props.showColumnsConfig && onConfigurationButtonClick }
selectAll={ props.selectAll }
size={ props.headerSize || settings.sizes.dataTable.header.row.default as DataTableHeaderRowProps['size'] }
Expand Down
2 changes: 1 addition & 1 deletion uui/components/tables/DataTableHeaderCell.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
padding-inline-end: var(--uui-dt-header-cell-padding-end);
width: 0;
background-clip: padding-box;
height: var(--uui-dt-header-cell-height);
min-height: var(--uui-dt-header-cell-height);

.caption-wrapper {
column-gap: var(--uui-dt-header-cell-caption-column-gap);
Expand Down
Loading

0 comments on commit 2234d37

Please sign in to comment.