diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx index c1c8e65cec4..8bd08acfa68 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx @@ -1,8 +1,14 @@ import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js'; import { ThemingParameters } from '@ui5/webcomponents-react-base'; import { useCallback, useEffect, useMemo, useRef, useState, version as reactVersion } from 'react'; -import type { AnalyticalTableDomRef, AnalyticalTablePropTypes } from '../..'; +import type { + AnalyticalTableColumnDefinition, + AnalyticalTableDomRef, + AnalyticalTablePropTypes, + PopoverDomRef +} from '../..'; import { + Popover, AnalyticalTable, AnalyticalTableHooks, AnalyticalTableScaleWidthMode, @@ -17,6 +23,7 @@ import { import { useManualRowSelect } from './pluginHooks/useManualRowSelect'; import { useRowDisableSelection } from './pluginHooks/useRowDisableSelection'; import { cssVarToRgb, cypressPassThroughTestsFactory } from '@/cypress/support/utils'; +import { getUi5TagWithSuffix } from '@/packages/main/src/internal/utils.js'; const generateMoreData = (count) => { return new Array(count).fill('').map((item, index) => ({ @@ -3233,6 +3240,62 @@ describe('AnalyticalTable', () => { cy.get('[data-visible-row-index="2"][data-visible-column-index="0"]').should('have.attr', 'aria-label', 'Name A '); }); + it('custom header popover', () => { + const columns: AnalyticalTableColumnDefinition[] = [ + { Header: 'Name', accessor: 'name' }, + { + Header: 'Custom Popover', + accessor: 'age', + Popover: (instance) => { + const ref = useRef(null); + const { popoverProps } = instance; + const { setOpen, openerRef } = popoverProps; + + useEffect(() => { + if (ref.current && openerRef.current) { + void customElements.whenDefined(getUi5TagWithSuffix('ui5-popover')).then(() => { + ref.current.opener = openerRef.current; + ref.current.open = true; + }); + } + }, []); + + return ( + { + setOpen(false); + }} + > + + + ); + } + } + ]; + cy.mount(); + + cy.findByText('Name').click(); + cy.get('[data-component-name="ATHeaderPopover"]').should('be.visible'); + cy.findByTestId('popover').should('not.exist'); + + cy.findByText('Custom Popover').click(); + cy.get('[data-component-name="ATHeaderPopover"]').should('not.exist'); + cy.findByTestId('popover').should('be.visible'); + + cy.findByText('Close Popover').click(); + cy.findByTestId('popover').should('not.exist'); + cy.get('[data-component-name="ATHeaderPopover"]').should('not.exist'); + }); + cypressPassThroughTestsFactory(AnalyticalTable, { data, columns }); }); diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx index d90c7a51d5e..e1ef69a3c78 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx @@ -152,10 +152,10 @@ import * as ComponentStories from './AnalyticalTable.stories'; **Required Attributes** -| Attribute | Type | Description | -| ---------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `accessor` | `string OR ((row: any, rowIndex: number) => any)` | This `string`/`function` is used to build the data model for your column.

**Note**: You can also specify deeply nested values with accessors like `info.hobby` or even `address[0].street`
| -| `id` | `string` | Defines the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.

**Note:** If no `accessor` is set, or the `accessor` is a function, the `id` property has to be set. | +| Attribute | Type | Description | +| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `accessor` | `string` OR
`(originalRow: Record, rowIndex: number, row: RowType,`
`parentRows: RowType[], data: Record[]) => any` | This `string`/`function` is used to build the data model for your column.

**Note**: You can also specify deeply nested values with accessors like `info.hobby` or even `address[0].street`.
| +| `id` | `string` | Defines the unique ID for the column. It is used by reference in things like sorting, grouping, filtering, etc.

**Note:** If no `accessor` is set, or the `accessor` is a function, the `id` property is mandatory. | **Optional Properties** diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx index 99106d78a8c..5be89db7cd1 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx @@ -2,6 +2,7 @@ import type { Virtualizer } from '@tanstack/react-virtual'; import { useStylesheet } from '@ui5/webcomponents-react-base'; import { forwardRef, Fragment } from 'react'; import type { DivWithCustomScrollProp } from '../types/index.js'; +import { RenderColumnTypes } from '../types/index.js'; import { classNames, styleData } from './Resizer.module.css.js'; import { ColumnHeader } from './index.js'; @@ -9,8 +10,6 @@ interface ColumnHeaderContainerProps { headerProps: Record; // eslint-disable-next-line @typescript-eslint/no-explicit-any headerGroup: Record; - onSort: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void; - onGroupByChanged: (e: CustomEvent<{ column?: Record; isGrouped?: boolean }>) => void; resizeInfo: Record; isRtl: boolean; columnVirtualizer: Virtualizer; @@ -19,17 +18,7 @@ interface ColumnHeaderContainerProps { } export const ColumnHeaderContainer = forwardRef((props, ref) => { - const { - headerProps, - headerGroup, - onSort, - onGroupByChanged, - resizeInfo, - isRtl, - columnVirtualizer, - uniqueId, - showVerticalEndBorder - } = props; + const { headerProps, headerGroup, resizeInfo, isRtl, columnVirtualizer, uniqueId, showVerticalEndBorder } = props; useStylesheet(styleData, 'Resizer'); @@ -77,15 +66,13 @@ export const ColumnHeaderContainer = forwardRef - {column.render('Header')} + {column.render(RenderColumnTypes.Header)} ); diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx index 01e14ed0748..0bbceff6f71 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx @@ -16,15 +16,12 @@ import type { import { useRef, useState } from 'react'; import { Icon } from '../../../webComponents/Icon/index.js'; import { Text } from '../../../webComponents/Text/index.js'; -import type { ColumnType } from '../types/ColumnType.js'; -import type { DivWithCustomScrollProp } from '../types/index.js'; +import type { ColumnType, DivWithCustomScrollProp } from '../types/index.js'; +import { RenderColumnTypes } from '../types/index.js'; import { classNames, styleData } from './ColumnHeader.module.css.js'; -import { ColumnHeaderModal } from './ColumnHeaderModal.js'; export interface ColumnHeaderProps { visibleColumnIndex: number; - onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void; - onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void; onDragStart: DragEventHandler; onDragOver: DragEventHandler; onDrop: DragEventHandler; @@ -64,8 +61,6 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { columnId, className, style, - onSort, - onGroupBy, onDragEnter, onDragOver, onDragStart, @@ -110,7 +105,7 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { const style: CSSProperties = {}; if (column.hAlign) { - style.textAlign = column.hAlign.toLowerCase() as any; + style.textAlign = column.hAlign.toLowerCase(); } if (column.isSorted) margin++; @@ -246,17 +241,15 @@ export const ColumnHeader = (props: ColumnHeaderProps) => { {column.isGrouped && } - {hasPopover && popoverOpen && ( - - )} + {hasPopover && + popoverOpen && + // render the popover and add the props to the table instance + column.render(RenderColumnTypes.Popover, { + popoverProps: { + openerRef: columnHeaderRef, + setOpen: setPopoverOpen + } + })} ); diff --git a/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx b/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx index 0789b2b89d4..271bbc69279 100644 --- a/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx +++ b/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBody.tsx @@ -10,13 +10,14 @@ import type { TableInstance, TriggerScrollState } from '../types/index.js'; +import { RenderColumnTypes } from '../types/index.js'; import { getSubRowsByString } from '../util/index.js'; import { EmptyRow } from './EmptyRow.js'; import { RowSubComponent as SubComponent } from './RowSubComponent.js'; interface VirtualTableBodyProps { classes: Record; - prepareRow: (row: unknown) => void; + prepareRow: TableInstance['prepareRow']; rows: TableInstance['rows']; isTreeTable?: AnalyticalTablePropTypes['isTreeTable']; internalRowHeight: number; @@ -244,15 +245,15 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => { ...directionStyles } }; - let contentToRender; + let contentToRender: RenderColumnTypes; if ( cell.column.id === '__ui5wcr__internal_highlight_column' || cell.column.id === '__ui5wcr__internal_selection_column' || cell.column.id === '__ui5wcr__internal_navigation_column' ) { - contentToRender = 'Cell'; + contentToRender = RenderColumnTypes.Cell; } else if (isTreeTable || (!alwaysShowSubComponent && RowSubComponent)) { - contentToRender = 'Expandable'; + contentToRender = RenderColumnTypes.Expandable; } else if ( cell.isGrouped || (manualGroupBy && @@ -260,13 +261,13 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => { getSubRowsByString(subRowsKey, row.original) != null && cell.value !== undefined) ) { - contentToRender = 'Grouped'; + contentToRender = RenderColumnTypes.Grouped; } else if (cell.isAggregated) { - contentToRender = 'Aggregated'; + contentToRender = RenderColumnTypes.Aggregated; } else if (cell.isPlaceholder) { - contentToRender = 'RepeatedValue'; + contentToRender = RenderColumnTypes.RepeatedValue; } else { - contentToRender = 'Cell'; + contentToRender = RenderColumnTypes.Cell; } return ( @@ -276,7 +277,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => { data-selection-cell={cell.column.id === '__ui5wcr__internal_selection_column'} > {popInRowHeight !== internalRowHeight && popInColumn.id === cell.column.id - ? cell.render('PopIn', { contentToRender, internalRowHeight }) + ? cell.render(RenderColumnTypes.PopIn, { contentToRender, internalRowHeight }) : cell.render(contentToRender, isNavigatedCell === true ? { isNavigatedCell } : {})} ); diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.module.css b/packages/main/src/components/AnalyticalTable/defaults/Column/ColumnHeaderModal.module.css similarity index 100% rename from packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.module.css rename to packages/main/src/components/AnalyticalTable/defaults/Column/ColumnHeaderModal.module.css diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx b/packages/main/src/components/AnalyticalTable/defaults/Column/ColumnHeaderModal.tsx similarity index 80% rename from packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx rename to packages/main/src/components/AnalyticalTable/defaults/Column/ColumnHeaderModal.tsx index 1d36e592137..2457bfa39d3 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx +++ b/packages/main/src/components/AnalyticalTable/defaults/Column/ColumnHeaderModal.tsx @@ -8,35 +8,36 @@ import iconGroup from '@ui5/webcomponents-icons/dist/group-2.js'; import iconSortAscending from '@ui5/webcomponents-icons/dist/sort-ascending.js'; import iconSortDescending from '@ui5/webcomponents-icons/dist/sort-descending.js'; import { enrichEventWithDetails, useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base'; -import type { MutableRefObject } from 'react'; import { useEffect, useMemo, useRef } from 'react'; -import { FlexBoxAlignItems, TextAlign } from '../../../enums/index.js'; -import { CLEAR_SORTING, FILTER, GROUP, SORT_ASCENDING, SORT_DESCENDING, UNGROUP } from '../../../i18n/i18n-defaults.js'; -import { stopPropagation } from '../../../internal/stopPropagation.js'; -import { getUi5TagWithSuffix } from '../../../internal/utils.js'; -import { Icon } from '../../../webComponents/Icon/index.js'; -import { List } from '../../../webComponents/List/index.js'; -import { ListItemCustom } from '../../../webComponents/ListItemCustom/index.js'; -import { ListItemStandard } from '../../../webComponents/ListItemStandard/index.js'; -import type { PopoverDomRef } from '../../../webComponents/Popover/index.js'; -import { Popover } from '../../../webComponents/Popover/index.js'; -import { Text } from '../../../webComponents/Text/index.js'; -import { FlexBox } from '../../FlexBox/index.js'; -import type { AnalyticalTableColumnDefinition } from '../types/index.js'; +import { FlexBoxAlignItems } from '../../../../enums/FlexBoxAlignItems.js'; +import { TextAlign } from '../../../../enums/TextAlign.js'; +import { + CLEAR_SORTING, + FILTER, + GROUP, + SORT_ASCENDING, + SORT_DESCENDING, + UNGROUP +} from '../../../../i18n/i18n-defaults.js'; +import { stopPropagation } from '../../../../internal/stopPropagation.js'; +import { getUi5TagWithSuffix } from '../../../../internal/utils.js'; +import { Icon } from '../../../../webComponents/Icon/index.js'; +import { List } from '../../../../webComponents/List/index.js'; +import { ListItemCustom } from '../../../../webComponents/ListItemCustom/index.js'; +import { ListItemStandard } from '../../../../webComponents/ListItemStandard/index.js'; +import type { PopoverDomRef } from '../../../../webComponents/Popover/index.js'; +import { Popover } from '../../../../webComponents/Popover/index.js'; +import { Text } from '../../../../webComponents/Text/index.js'; +import { FlexBox } from '../../../FlexBox/index.js'; +import type { TableInstance } from '../../types/index.js'; import { classNames, styleData } from './ColumnHeaderModal.module.css.js'; -export interface ColumnHeaderModalProperties { - column: AnalyticalTableColumnDefinition; - onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void; - onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void; - open: boolean; - setPopoverOpen: (open: boolean) => void; - isRtl: boolean; - openerRef: MutableRefObject; -} - -export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { - const { column, onSort, onGroupBy, open, setPopoverOpen, isRtl, openerRef } = props; +export const ColumnHeaderModal = (instance: TableInstance) => { + const { setOpen, openerRef } = instance.popoverProps; + const { column, state, webComponentsReactProperties } = instance; + const { isRtl, groupBy } = state; + const { onGroup, onSort } = webComponentsReactProperties; + useStylesheet(styleData, ColumnHeaderModal.displayName); const showFilter = column.canFilter; const showGroup = column.canGroupBy; @@ -44,7 +45,6 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { const ref = useRef(null); const listRef = useRef(null); - const { Filter } = column; const i18nBundle = useI18nBundle('@ui5/webcomponents-react'); @@ -105,10 +105,17 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { case 'group': { const willGroup = !column.isGrouped; column.toggleGroupBy(willGroup); - if (typeof onGroupBy === 'function') { - onGroupBy( + let groupedColumns; + if (willGroup) { + groupedColumns = [...groupBy, column.id]; + } else { + groupedColumns = groupBy.filter((group) => group !== column.id); + } + if (typeof onGroup === 'function') { + onGroup( enrichEventWithDetails(e, { column, + groupedColumns, isGrouped: willGroup }) ); @@ -116,7 +123,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { break; } } - setPopoverOpen(false); + setOpen(false); }; const isSortedAscending = column.isSorted && column.isSortedDesc === false; @@ -124,7 +131,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { const onAfterClose = (e) => { stopPropagation(e); - setPopoverOpen(false); + setOpen(false); }; const onAfterOpen = () => { @@ -150,7 +157,7 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { const handleCustomLiKeyDown = (e) => { if (e.key === 'Enter') { - setPopoverOpen(false); + setOpen(false); } }; @@ -164,13 +171,13 @@ export const ColumnHeaderModal = (props: ColumnHeaderModalProperties) => { if (open && ref.current && openerRef.current) { void customElements.whenDefined(getUi5TagWithSuffix('ui5-popover')).then(() => { ref.current.opener = openerRef.current; + ref.current.open = true; }); } }, [open]); return ( { @@ -85,7 +86,7 @@ export const Expandable = (props) => { )} )} - {cell.render('Cell')} + {cell.render(RenderColumnTypes.Cell)} ); }; diff --git a/packages/main/src/components/AnalyticalTable/defaults/Column/Grouped.tsx b/packages/main/src/components/AnalyticalTable/defaults/Column/Grouped.tsx index bb38ddb8179..13acd11f39c 100644 --- a/packages/main/src/components/AnalyticalTable/defaults/Column/Grouped.tsx +++ b/packages/main/src/components/AnalyticalTable/defaults/Column/Grouped.tsx @@ -3,6 +3,7 @@ import iconNavRightArrow from '@ui5/webcomponents-icons/dist/navigation-right-ar import type { CSSProperties } from 'react'; import { TextAlign } from '../../../../enums/index.js'; import { Icon } from '../../../../webComponents/index.js'; +import { RenderColumnTypes } from '../../types/index.js'; const tableGroupExpandCollapseIcon = { color: 'var(--sapContent_IconColor)', @@ -29,7 +30,7 @@ export const Grouped = (props) => { > - {cell.render('Cell')} + {cell.render(RenderColumnTypes.Cell)} ); }; diff --git a/packages/main/src/components/AnalyticalTable/defaults/Column/PopIn.tsx b/packages/main/src/components/AnalyticalTable/defaults/Column/PopIn.tsx index aa81a0b8fab..61a66c4db89 100644 --- a/packages/main/src/components/AnalyticalTable/defaults/Column/PopIn.tsx +++ b/packages/main/src/components/AnalyticalTable/defaults/Column/PopIn.tsx @@ -3,6 +3,7 @@ import { makeRenderer } from 'react-table'; import { FlexBoxAlignItems, FlexBoxDirection, FlexBoxWrap } from '../../../../enums/index.js'; import { Text } from '../../../../webComponents/Text/index.js'; import { FlexBox } from '../../../FlexBox/index.js'; +import { RenderColumnTypes } from '../../types/index.js'; import { classNames, styleData } from './PopIn.module.css.js'; export const PopIn = (instance) => { @@ -13,7 +14,7 @@ export const PopIn = (instance) => { { > {cell.render(contentToRender)} - {contentToRender !== 'Grouped' && + {contentToRender !== RenderColumnTypes.Grouped && state.popInColumns?.map((item) => { const popInInstanceProps = row.allCells.find((cell) => cell.column.id === item.id); const renderHeader = () => { diff --git a/packages/main/src/components/AnalyticalTable/defaults/Column/index.tsx b/packages/main/src/components/AnalyticalTable/defaults/Column/index.tsx index 4b1e575a92e..13748fb5e2a 100644 --- a/packages/main/src/components/AnalyticalTable/defaults/Column/index.tsx +++ b/packages/main/src/components/AnalyticalTable/defaults/Column/index.tsx @@ -1,6 +1,7 @@ import { VerticalAlign } from '../../../../enums/index.js'; import { DefaultFilterComponent } from '../FilterComponent/index.js'; import { Cell } from './Cell.js'; +import { ColumnHeaderModal } from './ColumnHeaderModal.js'; import { Expandable } from './Expandable.js'; import { Grouped } from './Grouped.js'; import { PopIn } from './PopIn.js'; @@ -16,5 +17,6 @@ export const DefaultColumn = { Cell: Cell, Expandable: Expandable, RepeatedValue: RepeatedValue, - PopIn: PopIn + PopIn: PopIn, + Popover: ColumnHeaderModal }; diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index fb55613653c..7720619cf30 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -82,7 +82,8 @@ import type { AnalyticalTableDomRef, AnalyticalTablePropTypes, AnalyticalTableState, - DivWithCustomScrollProp + DivWithCustomScrollProp, + TableInstance } from './types/index.js'; import { getRowHeight, getSubRowsByString, tagNamesWhichShouldNotSelectARow } from './util/index.js'; import { VerticalResizer } from './VerticalResizer.js'; @@ -178,7 +179,7 @@ const AnalyticalTable = forwardRef getSubRowsByString(subRowsKey, row) || [], [subRowsKey]); const invalidTableA11yText = i18nBundle.getText(INVALID_TABLE); - const tableInstanceRef = useRef>(null); + const tableInstanceRef = useRef(null); const scrollContainerRef = useRef(null); tableInstanceRef.current = useTable( @@ -208,29 +209,31 @@ const AnalyticalTable = forwardRef { - const { column, isGrouped } = e.detail; - let groupedColumns; - if (isGrouped) { - groupedColumns = [...tableState.groupBy, column.id]; - } else { - groupedColumns = tableState.groupBy.filter((group) => group !== column.id); - } - setGroupBy(groupedColumns); - if (typeof onGroup === 'function') { - onGroup( - enrichEventWithDetails(e, { - column, - groupedColumns - }) - ); - } - }, - [tableState.groupBy, onGroup, setGroupBy] - ); - useEffect(() => { if (columnOrder?.length > 0) { setColumnOrder(columnOrder); @@ -771,8 +752,6 @@ const AnalyticalTable = forwardRef }>; - toggleSortBy: (descending: boolean, multi?: any) => void; - clearSortBy: () => void; - toggleGroupBy: (set: boolean) => void; - canFilter: boolean; - canResize: boolean; - filterValue?: string; - canGroupBy: boolean; - isGrouped: boolean; - canSort: boolean; - isSorted: boolean; - isSortedDesc: boolean; - disableGrouping: boolean; - getResizerProps: () => any; - isResizing: boolean; - hAlign: AnalyticalTableColumnDefinition['hAlign']; - totalLeft: number; - totalFlexWidth: number; - enableMultiSort?: boolean; -} diff --git a/packages/main/src/components/AnalyticalTable/types/index.ts b/packages/main/src/components/AnalyticalTable/types/index.ts index 7c069bf95ab..60bef705e60 100644 --- a/packages/main/src/components/AnalyticalTable/types/index.ts +++ b/packages/main/src/components/AnalyticalTable/types/index.ts @@ -1,6 +1,6 @@ import type { ScrollToOptions } from '@tanstack/react-virtual'; import type ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js'; -import type { ComponentType, MutableRefObject, ReactNode, Ref } from 'react'; +import type { ComponentType, Dispatch, MutableRefObject, ReactNode, Ref, SetStateAction } from 'react'; import type { AnalyticalTableScaleWidthMode, AnalyticalTableScrollMode, @@ -14,11 +14,24 @@ import type { } from '../../../enums/index.js'; import type { CommonProps } from '../../../types/index.js'; +export enum RenderColumnTypes { + Filter = 'Filter', + Grouped = 'Grouped', + Cell = 'Cell', + Expandable = 'Expandable', + RepeatedValue = 'RepeatedValue', + PopIn = 'PopIn', + Popover = 'Popover', + Header = 'Header', + Aggregated = 'Aggregated' +} + export interface ColumnType extends Omit { id?: string; Expandable?: any; Grouped?: any; RepeatedValue?: any; + Popover?: any; canFilter?: boolean; canGroupBy?: boolean; canResize?: boolean; @@ -40,7 +53,12 @@ export interface ColumnType extends Omit originalWidth?: number; parent?: any; preFilteredRows?: Record[]; - render?: any; + /** + * Renders the component with the given column type + * @param type + * @param props The props are added to the table instance + */ + render?: (type: RenderColumnTypes | keyof typeof RenderColumnTypes, props: Record) => ReactNode; setFilter?: (val: string) => void; sortDescFirst?: boolean; sortedIndex?: number; @@ -182,6 +200,8 @@ export interface WCRPropertiesType { onAutoResize: AnalyticalTablePropTypes['onAutoResize']; onRowClick: AnalyticalTablePropTypes['onRowClick']; onRowExpandChange: AnalyticalTablePropTypes['onRowExpandChange']; + onSort: AnalyticalTablePropTypes['onSort']; + onGroup: AnalyticalTablePropTypes['onGroup']; isTreeTable: AnalyticalTablePropTypes['isTreeTable']; alternateRowColor: AnalyticalTablePropTypes['alternateRowColor']; scaleWidthMode: AnalyticalTablePropTypes['scaleWidthMode']; @@ -321,6 +341,21 @@ interface ScaleWidthModeOptions { cellString?: string; } +interface PopoverProps { + /** + * Set the state of the popover. If set to `false` the component is unmounted. + */ + setOpen: Dispatch>; + /** + * React Ref that holds the reference to the respective table header element. + */ + openerRef: MutableRefObject; +} + +export interface TableInstanceWithPopoverProps extends TableInstance { + popoverProps: PopoverProps; +} + export interface AnalyticalTableColumnDefinition { // base properties /** @@ -384,6 +419,18 @@ export interface AnalyticalTableColumnDefinition { * Maximum with of the column, e.g. used for resizing. */ maxWidth?: number; + /** + * Custom header Popover renderer. If set, this component replaces the internal header Popover. + * + * The table instance is passed as a prop, which includes the `popoverProps` object containing: + * - `openerRef`: A reference to the header cell that opens the popover. + * - `setOpen`: A React state setter to control the popover's open state. (The component will be unmounted if set to `false`) + * + * __Note:__ Since the component unmounts when closing the popover, the `open` prop of `Popover` components doesn't need to be controlled directly and can be set to `true`. + */ + Popover?: + | ComponentType<{ instance: TableInstanceWithPopoverProps }> + | ((props?: { instance: TableInstanceWithPopoverProps }) => ReactNode); // useFilters /**