diff --git a/semcore/data-table/src/components/Body/Body.tsx b/semcore/data-table/src/components/Body/Body.tsx index 0e4e56db03..dc69f7667a 100644 --- a/semcore/data-table/src/components/Body/Body.tsx +++ b/semcore/data-table/src/components/Body/Body.tsx @@ -92,21 +92,22 @@ class BodyRoot extends Component) => (node: HTMLElement | null) => { - if (node) { - this.rowsHeightMap.set(index, [0, 0, node]); - this.setRowHeight(index, row); - } - }; - - handleComponentRef = (row: DTRow) => (component: RowRoot | null) => { - requestAnimationFrame(() => { - if (component) { - this.rowsComponentsMap.set(row[UNIQ_ROW_KEY], component); - } else { - this.rowsComponentsMap.delete(row[UNIQ_ROW_KEY]); + handleComponentRef = (index: number, row: DTRow, component: RowRoot | null) => { + const id = row[UNIQ_ROW_KEY]; + if (component) { + if (this.asProps.virtualScroll) { + const node = component.rowElementRef.current; + + if (node) { + this.rowsHeightMap.set(index, [0, 0, node]); + this.setRowHeight(index, row); + } } - }); + + this.rowsComponentsMap.set(id, component); + } else { + this.rowsComponentsMap.delete(id); + } }; getPropsToRow = (props: { row: DTRow; mergedRow?: boolean }): DataTableRowProps => { @@ -380,7 +381,6 @@ class BodyRoot extends Component @@ -391,8 +391,7 @@ class BodyRoot extends Component ); })} diff --git a/semcore/data-table/src/components/Body/Row.tsx b/semcore/data-table/src/components/Body/Row.tsx index c43cce5463..c1e9df1c04 100644 --- a/semcore/data-table/src/components/Body/Row.tsx +++ b/semcore/data-table/src/components/Body/Row.tsx @@ -49,7 +49,7 @@ class RowRoot extends Component< private openAccordionTimeout = 0; private readonly cellStyle = new Map(); - rowElementRef = React.createRef(); + public rowElementRef = React.createRef(); state: State = { expandedForAnimation: false, @@ -66,8 +66,8 @@ class RowRoot extends Component< } componentDidMount() { - const { componentRef } = this.asProps; - componentRef?.(this); + const { rowIndex, componentRef, row } = this.asProps; + componentRef?.(rowIndex, row, this); this.setAccordion(); @@ -83,7 +83,8 @@ class RowRoot extends Component< } componentWillUnmount() { - this.asProps.componentRef?.(null); + const { rowIndex, componentRef, row } = this.asProps; + componentRef?.(rowIndex, row, null); this.cellStyle.clear(); } diff --git a/semcore/data-table/src/components/Body/Row.types.ts b/semcore/data-table/src/components/Body/Row.types.ts index a77018a40e..606a2e32f2 100644 --- a/semcore/data-table/src/components/Body/Row.types.ts +++ b/semcore/data-table/src/components/Body/Row.types.ts @@ -54,7 +54,7 @@ export type DataTableRowProps = { 'accordionRowIndex'?: DataTableCellProps['accordionRowIndex']; 'isNonInteractive'?: boolean; - 'componentRef'?: (component: RowRoot | null) => void; + 'componentRef'?: (index: number, row: DTRow, component: RowRoot | null) => void; 'accordionIndex'?: number; diff --git a/semcore/data-table/src/components/Body/RowGroup.tsx b/semcore/data-table/src/components/Body/RowGroup.tsx index 51d674f7d6..e57485aee2 100644 --- a/semcore/data-table/src/components/Body/RowGroup.tsx +++ b/semcore/data-table/src/components/Body/RowGroup.tsx @@ -19,8 +19,7 @@ type RowGroupProps = { startIndex: number; rowIndex: number; shadowVertical?: '' | 'end' | 'start' | 'median'; - handleRef: (index: number, row: DTRow) => (node: HTMLElement | null) => void; - handleComponentRef: (row: DTRow) => (component: RowRoot | null) => void; + handleComponentRef: (index: number, row: DTRow, component: RowRoot | null) => void; getPropsToRow: (props: { row: DTRow; mergedRow?: boolean }) => DataTableRowProps; }; @@ -44,13 +43,12 @@ export class RowGroup extends React.Pur {rows.map((item, i) => { return ( 0 ? true : false })} shadowVertical={shadowVertical} /> diff --git a/semcore/data-table/src/components/DataTable/DataTable.tsx b/semcore/data-table/src/components/DataTable/DataTable.tsx index 9eab396f34..e38f5e05b9 100644 --- a/semcore/data-table/src/components/DataTable/DataTable.tsx +++ b/semcore/data-table/src/components/DataTable/DataTable.tsx @@ -970,7 +970,7 @@ class DataTableRoot< let gridTemplateRows: string | undefined = undefined; if (virtualScroll && typeof virtualScroll !== 'boolean' && 'rowHeight' in virtualScroll) { - gridTemplateRows = `auto auto repeat(${this.totalRows}, minmax(${virtualScroll.rowHeight}px, auto))`; + gridTemplateRows = `${this.hasGroups ? 'auto auto' : 'auto'} repeat(${this.totalRows}, ${virtualScroll.rowHeight}px)`; } return sstyled(styles)( diff --git a/stories/components/data-table/advanced/data-table.advanced.stories.tsx b/stories/components/data-table/advanced/data-table.advanced.stories.tsx index 2ea407a8fb..58c3e32a28 100644 --- a/stories/components/data-table/advanced/data-table.advanced.stories.tsx +++ b/stories/components/data-table/advanced/data-table.advanced.stories.tsx @@ -24,6 +24,7 @@ import type { DemoProps as SelectableWithMergedRowsProps } from './examples/sele import SideIndentsExample from './examples/side-indents'; import type { AccordionInTableProps } from './examples/table_perf/table_perf'; import TablePerfExample, { accordionInsideTableDefaultProps } from './examples/table_perf/table_perf'; +import TablePerfVirtualizationExample from './examples/table_perf_virtualization'; import VirtualScrollWithH100Example from './examples/virtual-scroll-with-h100'; const meta: Meta = { @@ -217,3 +218,24 @@ export const TablePerf: StoryObj = { }, args: accordionInsideTableDefaultProps, }; + +export const TablePerfVirtualization: StoryObj = { + render: TablePerfVirtualizationExample, + argTypes: { + loading: { control: 'boolean' }, + cropPosition: { + control: 'select', + options: ['end', 'middle'], + }, + // UIK-4923: hintProps=false skips ellipsis JS calculation for cropPosition=end + hintProps: { + control: 'select', + options: ['default', 'false'], + mapping: { + default: undefined, + false: false, + }, + }, + }, + args: accordionInsideTableDefaultProps, +}; diff --git a/stories/components/data-table/advanced/examples/table_perf/table_perf.tsx b/stories/components/data-table/advanced/examples/table_perf/table_perf.tsx index ab0d8097be..1ea1aae863 100644 --- a/stories/components/data-table/advanced/examples/table_perf/table_perf.tsx +++ b/stories/components/data-table/advanced/examples/table_perf/table_perf.tsx @@ -20,9 +20,9 @@ export type AccordionInTableProps = { cropPosition?: 'end' | 'middle'; }; -const refsMap: Record = {}; +export const refsMap: Record = {}; -const COLUMNS_CONFIG = [ +export const COLUMNS_CONFIG = [ { id: 'actions', defaultActive: true, @@ -182,7 +182,7 @@ const COLUMNS_CONFIG = [ }, ]; -const cols = COLUMNS_CONFIG.map((c) => ({ +export const cols = COLUMNS_CONFIG.map((c) => ({ name: c.id, children: c.id, gtcWidth: c.wMin ? `minmax(${c.wMin}px, 1fr)` : 'max-content', @@ -198,12 +198,12 @@ const cols = COLUMNS_CONFIG.map((c) => ({ // ); // no memo -const componentsMap = Object.fromEntries( +export const componentsMap = Object.fromEntries( COLUMNS_CONFIG.map((c) => [c.id, c.Component]), ); -const DEFAULT_LOCALE = 'en-US'; -const DEFAULT_MESSAGES = {}; +export const DEFAULT_LOCALE = 'en-US'; +export const DEFAULT_MESSAGES = {}; const Demo = (demoProps: AccordionInTableProps) => { const cropPos = demoProps.cropPosition ?? 'middle'; @@ -268,13 +268,13 @@ export const accordionInsideTableDefaultProps: AccordionInTableProps = { Demo.defaultProps = accordionInsideTableDefaultProps; -const ChartExample = () => { +export const ChartExample = () => { return (
); }; -const data: DataTableData = [ +export const data: DataTableData = [ { payment_intent_id: '300749e8-77a6-4637-a36b-285e33af4c0a', affected_payment_intent_id: '5f39d854-f1e3-4c20-a0f8-de290e23be5a', diff --git a/stories/components/data-table/advanced/examples/table_perf_virtualization.tsx b/stories/components/data-table/advanced/examples/table_perf_virtualization.tsx new file mode 100644 index 0000000000..78b7a91910 --- /dev/null +++ b/stories/components/data-table/advanced/examples/table_perf_virtualization.tsx @@ -0,0 +1,78 @@ +import { DataTable } from '@semcore/ui/data-table'; +import { Text } from '@semcore/ui/typography'; +import React from 'react'; +import { IntlProvider } from 'react-intl'; + +import type { AccordionInTableProps } from './table_perf/table_perf'; +import { accordionInsideTableDefaultProps, cols, componentsMap, DEFAULT_LOCALE, DEFAULT_MESSAGES, data, refsMap } from './table_perf/table_perf'; + +const data2 = Array.from({ length: 1000 }, (_, r) => { + const index = r % data.length; + + // Object.entries skips symbol keys, so [ACCORDION] is dropped: accordion is not supported with fixed rowHeight + const d = Object.entries(data[index]).reduce>((acc, [key, value]) => { + if (key === 'payment_intent_id') { + acc[key] = `${r}__${value}`; + } else { + acc[key] = value; + } + + return acc; + }, {}); + + return d; +}); + +const Demo = (demoProps: AccordionInTableProps) => { + const cropPos = demoProps.cropPosition ?? 'middle'; + + return ( + + { + if (props.dataKey === 'description_ellipsis') { + return { + children: ( + + {String(props.row.payment_description)} + + ), + }; + } + + // @ts-ignore + const Component = componentsMap[props.columnName]; + if (Component) { + return { + children: , + }; + } + + return { + children: {props.value}, + }; + }} + /> + + ); +}; + +Demo.defaultProps = accordionInsideTableDefaultProps; + +export default Demo;