Skip to content

Commit 03d973e

Browse files
authored
feat(AnalyticalTable): allow passing custom header popovers (#6576)
1 parent 2a4049c commit 03d973e

File tree

14 files changed

+213
-159
lines changed

14 files changed

+213
-159
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
22
import { ThemingParameters } from '@ui5/webcomponents-react-base';
33
import { useCallback, useEffect, useMemo, useRef, useState, version as reactVersion } from 'react';
4-
import type { AnalyticalTableDomRef, AnalyticalTablePropTypes } from '../..';
4+
import type {
5+
AnalyticalTableColumnDefinition,
6+
AnalyticalTableDomRef,
7+
AnalyticalTablePropTypes,
8+
PopoverDomRef
9+
} from '../..';
510
import {
11+
Popover,
612
AnalyticalTable,
713
AnalyticalTableHooks,
814
AnalyticalTableScaleWidthMode,
@@ -17,6 +23,7 @@ import {
1723
import { useManualRowSelect } from './pluginHooks/useManualRowSelect';
1824
import { useRowDisableSelection } from './pluginHooks/useRowDisableSelection';
1925
import { cssVarToRgb, cypressPassThroughTestsFactory } from '@/cypress/support/utils';
26+
import { getUi5TagWithSuffix } from '@/packages/main/src/internal/utils.js';
2027

2128
const generateMoreData = (count) => {
2229
return new Array(count).fill('').map((item, index) => ({
@@ -3233,6 +3240,62 @@ describe('AnalyticalTable', () => {
32333240
cy.get('[data-visible-row-index="2"][data-visible-column-index="0"]').should('have.attr', 'aria-label', 'Name A ');
32343241
});
32353242

3243+
it('custom header popover', () => {
3244+
const columns: AnalyticalTableColumnDefinition[] = [
3245+
{ Header: 'Name', accessor: 'name' },
3246+
{
3247+
Header: 'Custom Popover',
3248+
accessor: 'age',
3249+
Popover: (instance) => {
3250+
const ref = useRef<PopoverDomRef>(null);
3251+
const { popoverProps } = instance;
3252+
const { setOpen, openerRef } = popoverProps;
3253+
3254+
useEffect(() => {
3255+
if (ref.current && openerRef.current) {
3256+
void customElements.whenDefined(getUi5TagWithSuffix('ui5-popover')).then(() => {
3257+
ref.current.opener = openerRef.current;
3258+
ref.current.open = true;
3259+
});
3260+
}
3261+
}, []);
3262+
3263+
return (
3264+
<Popover
3265+
ref={ref}
3266+
data-testid="popover"
3267+
onClose={() => {
3268+
setOpen(false);
3269+
}}
3270+
>
3271+
<button
3272+
onClick={(e) => {
3273+
e.stopPropagation();
3274+
setOpen(false);
3275+
}}
3276+
>
3277+
Close Popover
3278+
</button>
3279+
</Popover>
3280+
);
3281+
}
3282+
}
3283+
];
3284+
cy.mount(<AnalyticalTable data={groupableData} columns={columns} sortable />);
3285+
3286+
cy.findByText('Name').click();
3287+
cy.get('[data-component-name="ATHeaderPopover"]').should('be.visible');
3288+
cy.findByTestId('popover').should('not.exist');
3289+
3290+
cy.findByText('Custom Popover').click();
3291+
cy.get('[data-component-name="ATHeaderPopover"]').should('not.exist');
3292+
cy.findByTestId('popover').should('be.visible');
3293+
3294+
cy.findByText('Close Popover').click();
3295+
cy.findByTestId('popover').should('not.exist');
3296+
cy.get('[data-component-name="ATHeaderPopover"]').should('not.exist');
3297+
});
3298+
32363299
cypressPassThroughTestsFactory(AnalyticalTable, { data, columns });
32373300
});
32383301

packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ import * as ComponentStories from './AnalyticalTable.stories';
152152
153153
**Required Attributes**
154154
155-
| Attribute | Type | Description |
156-
| ---------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
157-
| `accessor` | `string OR ((row: any, rowIndex: number) => any)` | This `string`/`function` is used to build the data model for your column.<br /><br />**Note**: You can also specify deeply nested values with accessors like `info.hobby` or even `address[0].street`<br /> |
158-
| `id` | `string` | Defines the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.<br /><br />**Note:** If no `accessor` is set, or the `accessor` is a function, the `id` property has to be set. |
155+
| Attribute | Type | Description |
156+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
157+
| `accessor` | `string` OR <br />`(originalRow: Record<string, any>, rowIndex: number, row: RowType,` <br />`parentRows: RowType[], data: Record<string, any>[]) => any` | This `string`/`function` is used to build the data model for your column.<br /><br />**Note**: You can also specify deeply nested values with accessors like `info.hobby` or even `address[0].street`.<br /> |
158+
| `id` | `string` | Defines the unique ID for the column. It is used by reference in things like sorting, grouping, filtering, etc.<br /><br />**Note:** If no `accessor` is set, or the `accessor` is a function, the `id` property is mandatory. |
159159
160160
**Optional Properties**
161161

packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import type { Virtualizer } from '@tanstack/react-virtual';
22
import { useStylesheet } from '@ui5/webcomponents-react-base';
33
import { forwardRef, Fragment } from 'react';
44
import type { DivWithCustomScrollProp } from '../types/index.js';
5+
import { RenderColumnTypes } from '../types/index.js';
56
import { classNames, styleData } from './Resizer.module.css.js';
67
import { ColumnHeader } from './index.js';
78

89
interface ColumnHeaderContainerProps {
910
headerProps: Record<string, any>;
1011
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1112
headerGroup: Record<string, any>;
12-
onSort: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void;
13-
onGroupByChanged: (e: CustomEvent<{ column?: Record<string, unknown>; isGrouped?: boolean }>) => void;
1413
resizeInfo: Record<string, unknown>;
1514
isRtl: boolean;
1615
columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
@@ -19,17 +18,7 @@ interface ColumnHeaderContainerProps {
1918
}
2019

2120
export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderContainerProps>((props, ref) => {
22-
const {
23-
headerProps,
24-
headerGroup,
25-
onSort,
26-
onGroupByChanged,
27-
resizeInfo,
28-
isRtl,
29-
columnVirtualizer,
30-
uniqueId,
31-
showVerticalEndBorder
32-
} = props;
21+
const { headerProps, headerGroup, resizeInfo, isRtl, columnVirtualizer, uniqueId, showVerticalEndBorder } = props;
3322

3423
useStylesheet(styleData, 'Resizer');
3524

@@ -77,15 +66,13 @@ export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderCont
7766
id={`${uniqueId}${rest?.id ?? ''}`}
7867
columnId={rest.id}
7968
visibleColumnIndex={index}
80-
onSort={onSort}
81-
onGroupBy={onGroupByChanged}
8269
headerTooltip={column.headerTooltip}
8370
isDraggable={!column.disableDragAndDrop && !resizeInfo.isResizingColumn}
8471
virtualColumn={virtualColumn}
8572
columnVirtualizer={columnVirtualizer}
8673
isRtl={isRtl}
8774
>
88-
{column.render('Header')}
75+
{column.render(RenderColumnTypes.Header)}
8976
</ColumnHeader>
9077
</Fragment>
9178
);

packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@ import type {
1616
import { useRef, useState } from 'react';
1717
import { Icon } from '../../../webComponents/Icon/index.js';
1818
import { Text } from '../../../webComponents/Text/index.js';
19-
import type { ColumnType } from '../types/ColumnType.js';
20-
import type { DivWithCustomScrollProp } from '../types/index.js';
19+
import type { ColumnType, DivWithCustomScrollProp } from '../types/index.js';
20+
import { RenderColumnTypes } from '../types/index.js';
2121
import { classNames, styleData } from './ColumnHeader.module.css.js';
22-
import { ColumnHeaderModal } from './ColumnHeaderModal.js';
2322

2423
export interface ColumnHeaderProps {
2524
visibleColumnIndex: number;
26-
onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void;
27-
onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void;
2825
onDragStart: DragEventHandler<HTMLDivElement>;
2926
onDragOver: DragEventHandler<HTMLDivElement>;
3027
onDrop: DragEventHandler<HTMLDivElement>;
@@ -64,8 +61,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
6461
columnId,
6562
className,
6663
style,
67-
onSort,
68-
onGroupBy,
6964
onDragEnter,
7065
onDragOver,
7166
onDragStart,
@@ -110,7 +105,7 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
110105
const style: CSSProperties = {};
111106

112107
if (column.hAlign) {
113-
style.textAlign = column.hAlign.toLowerCase() as any;
108+
style.textAlign = column.hAlign.toLowerCase();
114109
}
115110

116111
if (column.isSorted) margin++;
@@ -246,17 +241,15 @@ export const ColumnHeader = (props: ColumnHeaderProps) => {
246241
{column.isGrouped && <Icon name={iconGroup} aria-hidden />}
247242
</div>
248243
</div>
249-
{hasPopover && popoverOpen && (
250-
<ColumnHeaderModal
251-
isRtl={isRtl}
252-
column={column}
253-
onSort={onSort}
254-
onGroupBy={onGroupBy}
255-
openerRef={columnHeaderRef}
256-
open={popoverOpen}
257-
setPopoverOpen={setPopoverOpen}
258-
/>
259-
)}
244+
{hasPopover &&
245+
popoverOpen &&
246+
// render the popover and add the props to the table instance
247+
column.render(RenderColumnTypes.Popover, {
248+
popoverProps: {
249+
openerRef: columnHeaderRef,
250+
setOpen: setPopoverOpen
251+
}
252+
})}
260253
</div>
261254
</div>
262255
);

packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import type {
1010
TableInstance,
1111
TriggerScrollState
1212
} from '../types/index.js';
13+
import { RenderColumnTypes } from '../types/index.js';
1314
import { getSubRowsByString } from '../util/index.js';
1415
import { EmptyRow } from './EmptyRow.js';
1516
import { RowSubComponent as SubComponent } from './RowSubComponent.js';
1617

1718
interface VirtualTableBodyProps {
1819
classes: Record<string, string>;
19-
prepareRow: (row: unknown) => void;
20+
prepareRow: TableInstance['prepareRow'];
2021
rows: TableInstance['rows'];
2122
isTreeTable?: AnalyticalTablePropTypes['isTreeTable'];
2223
internalRowHeight: number;
@@ -244,29 +245,29 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
244245
...directionStyles
245246
}
246247
};
247-
let contentToRender;
248+
let contentToRender: RenderColumnTypes;
248249
if (
249250
cell.column.id === '__ui5wcr__internal_highlight_column' ||
250251
cell.column.id === '__ui5wcr__internal_selection_column' ||
251252
cell.column.id === '__ui5wcr__internal_navigation_column'
252253
) {
253-
contentToRender = 'Cell';
254+
contentToRender = RenderColumnTypes.Cell;
254255
} else if (isTreeTable || (!alwaysShowSubComponent && RowSubComponent)) {
255-
contentToRender = 'Expandable';
256+
contentToRender = RenderColumnTypes.Expandable;
256257
} else if (
257258
cell.isGrouped ||
258259
(manualGroupBy &&
259260
cell.column.isGrouped &&
260261
getSubRowsByString(subRowsKey, row.original) != null &&
261262
cell.value !== undefined)
262263
) {
263-
contentToRender = 'Grouped';
264+
contentToRender = RenderColumnTypes.Grouped;
264265
} else if (cell.isAggregated) {
265-
contentToRender = 'Aggregated';
266+
contentToRender = RenderColumnTypes.Aggregated;
266267
} else if (cell.isPlaceholder) {
267-
contentToRender = 'RepeatedValue';
268+
contentToRender = RenderColumnTypes.RepeatedValue;
268269
} else {
269-
contentToRender = 'Cell';
270+
contentToRender = RenderColumnTypes.Cell;
270271
}
271272

272273
return (
@@ -276,7 +277,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
276277
data-selection-cell={cell.column.id === '__ui5wcr__internal_selection_column'}
277278
>
278279
{popInRowHeight !== internalRowHeight && popInColumn.id === cell.column.id
279-
? cell.render('PopIn', { contentToRender, internalRowHeight })
280+
? cell.render(RenderColumnTypes.PopIn, { contentToRender, internalRowHeight })
280281
: cell.render(contentToRender, isNavigatedCell === true ? { isNavigatedCell } : {})}
281282
</div>
282283
);

0 commit comments

Comments
 (0)