Skip to content

Commit 15a1e99

Browse files
authored
feat(AnalyticalTable): add support for subcomponents (#997)
1 parent 9983e7c commit 15a1e99

File tree

7 files changed

+655
-12
lines changed

7 files changed

+655
-12
lines changed

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

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import '@ui5/webcomponents-icons/dist/delete';
1010
import '@ui5/webcomponents-icons/dist/edit';
1111
import '@ui5/webcomponents-icons/dist/settings';
1212
import { Button } from '@ui5/webcomponents-react/lib/Button';
13+
import { Badge } from '@ui5/webcomponents-react/lib/Badge';
14+
import { Text } from '@ui5/webcomponents-react/lib/Text';
1315
import { FlexBox } from '@ui5/webcomponents-react/lib/FlexBox';
16+
import { FlexBoxAlignItems } from '@ui5/webcomponents-react/lib/FlexBoxAlignItems';
17+
import { FlexBoxDirection } from '@ui5/webcomponents-react/lib/FlexBoxDirection';
18+
import { FlexBoxJustifyContent } from '@ui5/webcomponents-react/lib/FlexBoxJustifyContent';
1419
import { TextAlign } from '@ui5/webcomponents-react/lib/TextAlign';
1520
import { DefaultLoadingComponent } from './defaults/LoadingComponent';
1621
import { DefaultNoDataComponent } from './defaults/NoDataComponent';
@@ -333,6 +338,8 @@ The table initially contains 50 rows, when the last 10 rows are reached the tabl
333338
</Story>
334339
</Canvas>
335340

341+
### Code
342+
336343
```jsx
337344
const InfiniteScrollTable = (props) => {
338345
const [data, setData] = useState(props.data.slice(0, 50));
@@ -363,3 +370,110 @@ const InfiniteScrollTable = (props) => {
363370
);
364371
};
365372
```
373+
374+
## AnalyticalTable with subcomponents
375+
376+
Adding custom subcomponents below table rows can be achieved by setting the `renderRowSubComponent` prop.
377+
The prop expects a function with an optional parameter containing the `row` instance, there you can control which row should display subcomponents.
378+
379+
**Note:** When `renderRowSubComponent` is set, `grouping` is disabled.
380+
381+
<Canvas>
382+
<Story name="with subcomponents" args={{ data: generateData(100) }}>
383+
{(args) => {
384+
const renderRowSubComponent = (row) => {
385+
if (row.id === '0') {
386+
return (
387+
<FlexBox
388+
style={{ backgroundColor: 'lightblue', height: '300px' }}
389+
justifyContent={FlexBoxJustifyContent.Center}
390+
alignItems={FlexBoxAlignItems.Center}
391+
direction={FlexBoxDirection.Column}
392+
>
393+
<Badge>height: 300px</Badge>
394+
<Text>This subcomponent will only be displayed below the first row.</Text>
395+
</FlexBox>
396+
);
397+
}
398+
if (row.id === '1') {
399+
return (
400+
<FlexBox
401+
style={{ backgroundColor: 'lightyellow', height: '100px' }}
402+
justifyContent={FlexBoxJustifyContent.Center}
403+
alignItems={FlexBoxAlignItems.Center}
404+
direction={FlexBoxDirection.Column}
405+
>
406+
<Badge>height: 100px</Badge>
407+
<Text>This subcomponent will only be displayed below the second row.</Text>
408+
</FlexBox>
409+
);
410+
}
411+
if (row.id === '2') {
412+
return null;
413+
}
414+
return (
415+
<FlexBox
416+
style={{ backgroundColor: 'lightgrey', height: '50px' }}
417+
justifyContent={FlexBoxJustifyContent.Center}
418+
alignItems={FlexBoxAlignItems.Center}
419+
direction={FlexBoxDirection.Column}
420+
>
421+
<Badge>height: 50px</Badge>
422+
<Text>This subcomponent will be displayed below all rows except the first, second and third.</Text>
423+
</FlexBox>
424+
);
425+
};
426+
return <AnalyticalTable data={args.data} columns={args.columns} renderRowSubComponent={renderRowSubComponent} />;
427+
}}
428+
</Story>
429+
</Canvas>
430+
431+
### Code
432+
433+
```jsx
434+
const TableWithSubcomponents = (props) => {
435+
const renderRowSubComponent = (row) => {
436+
if (row.id === '0') {
437+
return (
438+
<FlexBox
439+
style={{ backgroundColor: 'lightblue', height: '300px' }}
440+
justifyContent={FlexBoxJustifyContent.Center}
441+
alignItems={FlexBoxAlignItems.Center}
442+
direction={FlexBoxDirection.Column}
443+
>
444+
<Badge>height: 300px</Badge>
445+
<Text>This subcomponent will only be displayed below the first row.</Text>
446+
</FlexBox>
447+
);
448+
}
449+
if (row.id === '1') {
450+
return (
451+
<FlexBox
452+
style={{ backgroundColor: 'lightyellow', height: '100px' }}
453+
justifyContent={FlexBoxJustifyContent.Center}
454+
alignItems={FlexBoxAlignItems.Center}
455+
direction={FlexBoxDirection.Column}
456+
>
457+
<Badge>height: 100px</Badge>
458+
<Text>This subcomponent will only be displayed below the second row.</Text>
459+
</FlexBox>
460+
);
461+
}
462+
if (row.id === '2') {
463+
return null;
464+
}
465+
return (
466+
<FlexBox
467+
style={{ backgroundColor: 'lightgrey', height: '50px' }}
468+
justifyContent={FlexBoxJustifyContent.Center}
469+
alignItems={FlexBoxAlignItems.Center}
470+
direction={FlexBoxDirection.Column}
471+
>
472+
<Badge>height: 50px</Badge>
473+
<Text>This subcomponent will be displayed below all rows except the first, second and third.</Text>
474+
</FlexBox>
475+
);
476+
};
477+
return <AnalyticalTable data={props.data} columns={props.columns} renderRowSubComponent={renderRowSubComponent} />;
478+
};
479+
```

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,5 +372,35 @@ describe('AnalyticalTable', () => {
372372
expect(row).toHaveAttribute('data-is-selected');
373373
});
374374

375+
test('render subcomponents', () => {
376+
const renderRowSubComponent = () => {
377+
return <div title="subcomponent">Hi! I'm a subcomponent.</div>;
378+
};
379+
380+
const onlyFirstRowWithSubcomponent = (row) => {
381+
if (row.id === '0') {
382+
return <div title="subcomponent">Hi! I'm a subcomponent.</div>;
383+
}
384+
};
385+
const { asFragment, rerender } = render(
386+
<AnalyticalTable data={data} columns={columns} renderRowSubComponent={renderRowSubComponent} />
387+
);
388+
expect(screen.getAllByTitle('Toggle Row Expanded')).toHaveLength(2);
389+
390+
fireEvent.click(screen.getAllByTitle('Toggle Row Expanded')[0]);
391+
392+
expect(screen.getAllByTitle('subcomponent')).toHaveLength(1);
393+
394+
fireEvent.click(screen.getAllByTitle('Toggle Row Expanded')[1]);
395+
396+
expect(screen.getAllByTitle('subcomponent')).toHaveLength(2);
397+
398+
expect(asFragment()).toMatchSnapshot();
399+
400+
rerender(<AnalyticalTable data={data} columns={columns} renderRowSubComponent={onlyFirstRowWithSubcomponent} />);
401+
402+
expect(screen.getAllByTitle('Toggle Row Expanded')).toHaveLength(1);
403+
});
404+
375405
createPassThroughPropsTest(AnalyticalTable);
376406
});

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

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '@ui5/webcomponents-icons/dist/navigation-down-arrow';
22
import '@ui5/webcomponents-icons/dist/navigation-right-arrow';
33
import { useConsolidatedRef } from '@ui5/webcomponents-react-base/lib/useConsolidatedRef';
4-
import React, { MutableRefObject, useCallback, useRef } from 'react';
4+
import React, { MutableRefObject, ReactNode, useCallback, useRef, useState } from 'react';
55
import { useVirtual } from 'react-virtual';
66

77
interface VirtualTableBodyProps {
@@ -20,6 +20,7 @@ interface VirtualTableBodyProps {
2020
visibleColumnsWidth: any[];
2121
parentRef: MutableRefObject<any>;
2222
overscanCountHorizontal: number;
23+
renderRowSubComponent: (row?: any) => ReactNode;
2324
}
2425

2526
export const VirtualTableBody = (props: VirtualTableBodyProps) => {
@@ -37,17 +38,28 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
3738
tableRef,
3839
visibleColumnsWidth,
3940
parentRef,
40-
overscanCountHorizontal
41+
overscanCountHorizontal,
42+
renderRowSubComponent
4143
} = props;
4244

45+
const rowSubComponentsHeight = useRef({});
46+
4347
const itemCount = Math.max(minRows, rows.length);
4448
const overscan = overscanCount ? overscanCount : Math.floor(visibleRows / 2);
4549

4650
const consolidatedParentRef = useConsolidatedRef(parentRef);
4751
const rowVirtualizer = useVirtual({
4852
size: itemCount,
4953
parentRef: consolidatedParentRef,
50-
estimateSize: React.useCallback(() => internalRowHeight, [internalRowHeight]),
54+
estimateSize: React.useCallback(
55+
(index) => {
56+
if (renderRowSubComponent && rows[index].isExpanded && rowSubComponentsHeight.current.hasOwnProperty(index)) {
57+
return internalRowHeight + (rowSubComponentsHeight.current?.[index] ?? 0);
58+
}
59+
return internalRowHeight;
60+
},
61+
[internalRowHeight, rows, renderRowSubComponent]
62+
),
5163
overscan
5264
});
5365

@@ -154,6 +166,11 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
154166
>
155167
{rowVirtualizer.virtualItems.map((virtualRow) => {
156168
const row = rows[virtualRow.index];
169+
const setSubcomponentsRefs = (el) => {
170+
if (el?.offsetHeight) {
171+
rowSubComponentsHeight.current[virtualRow.index] = el.offsetHeight;
172+
}
173+
};
157174
if (!row) {
158175
return (
159176
<div
@@ -168,6 +185,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
168185
}
169186
prepareRow(row);
170187
const rowProps = row.getRowProps();
188+
const RowSubComponent = typeof renderRowSubComponent === 'function' ? renderRowSubComponent(row) : null;
171189
return (
172190
<div
173191
{...rowProps}
@@ -177,6 +195,18 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
177195
position: 'absolute'
178196
}}
179197
>
198+
{RowSubComponent && row.isExpanded && (
199+
<div
200+
ref={setSubcomponentsRefs}
201+
style={{
202+
transform: `translateY(${internalRowHeight}px)`,
203+
position: 'absolute',
204+
width: '100%'
205+
}}
206+
>
207+
{RowSubComponent}
208+
</div>
209+
)}
180210
{columnVirtualizer.virtualItems.map((virtualColumn) => {
181211
const cell = row.cells[virtualColumn.index];
182212
if (!cell) {
@@ -192,7 +222,7 @@ export const VirtualTableBody = (props: VirtualTableBodyProps) => {
192222
cell.column.id === '__ui5wcr__internal_selection_column'
193223
) {
194224
contentToRender = 'Cell';
195-
} else if (isTreeTable) {
225+
} else if (isTreeTable || RowSubComponent) {
196226
contentToRender = 'Expandable';
197227
} else if (cell.isGrouped) {
198228
contentToRender = 'Grouped';

0 commit comments

Comments
 (0)