diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
index 370e5ab44d7..02b8967c8b5 100644
--- a/.storybook/preview-head.html
+++ b/.storybook/preview-head.html
@@ -109,6 +109,14 @@
padding-block: 0.125rem !important;
}
+ /*ui5-table: height for virtualized table according to content-density mode*/
+ .tableHeightContentDensity {
+ height: 396px;
+ }
+ .ui5-content-density-compact .tableHeightContentDensity {
+ height: 288px;
+ }
+
/* TODO remove this workaround as soon as https://github.com/storybookjs/storybook/issues/20497 is fixed */
.docs-story > div > div[scale] {
min-height: 20px;
diff --git a/packages/main/src/webComponents/Table/Table.mdx b/packages/main/src/webComponents/Table/Table.mdx
index a6b44db89da..f5d4a67c07f 100644
--- a/packages/main/src/webComponents/Table/Table.mdx
+++ b/packages/main/src/webComponents/Table/Table.mdx
@@ -113,7 +113,7 @@ const GrowingTable = () => {
Show code
```jsx
-function TableWithRowSelection() {
+function TableWithRowSelection(props) {
const [mode, setMode] = useState(TableSelectionMode.Multiple);
return (
<>
@@ -129,7 +129,7 @@ function TableWithRowSelection() {
))}
@@ -193,6 +193,74 @@ function TableWithRowSelection() {
+## Table with virtualized rows
+
+`Table` with virtualization feature (``):
+
+
+
+
+
+ Show code
+
+```tsx
+// enrich data with `position` (if not already available)
+const dataLargeWithPosition = dataLarge.map((item, index) => ({
+ ...item,
+ position: index
+}));
+
+function VirtualizedTable(props) {
+ const [data, setData] = useState(dataLargeWithPosition.slice(0, 9));
+
+ const handleRangeChange: TableVirtualizerPropTypes['onRangeChange'] = (e) => {
+ const { first, last } = e.detail;
+
+ // render two rows before and after the visible area of the table body container
+ const overscanCountStart = Math.max(first - 2, 0);
+ const overscanCountEnd = Math.min(last + 2, dataLargeWithPosition.length);
+ setData(dataLargeWithPosition.slice(overscanCountStart, overscanCountEnd));
+ };
+ return (
+
+ Name
+ Age
+ Friend Name
+ Friend Age
+
+ }
+ features={}
+ >
+ {data.map((row) => (
+
+
+ {row.name}
+
+
+ {row.age}
+
+
+ {row.friend.name}
+
+
+ {row.friend.age}
+
+
+ ))}
+
+ );
+}
+```
+
+
+
{SubcomponentsSection}
## TableHeaderRow
diff --git a/packages/main/src/webComponents/Table/Table.stories.tsx b/packages/main/src/webComponents/Table/Table.stories.tsx
index 1053c58871f..5703e5aa020 100644
--- a/packages/main/src/webComponents/Table/Table.stories.tsx
+++ b/packages/main/src/webComponents/Table/Table.stories.tsx
@@ -1,14 +1,17 @@
+import dataLarge from '@sb/mockData/Friends500.json';
import type { Meta, StoryObj } from '@storybook/react';
import TableGrowingMode from '@ui5/webcomponents/dist/types/TableGrowingMode.js';
import TableSelectionMode from '@ui5/webcomponents/dist/types/TableSelectionMode.js';
+import type { TableVirtualizerPropTypes } from '@ui5/webcomponents-react';
import { SegmentedButton, SegmentedButtonItem } from '@ui5/webcomponents-react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { TableCell } from '../TableCell/index.js';
import { TableGrowing } from '../TableGrowing/index.js';
import { TableHeaderCell } from '../TableHeaderCell/index.js';
import { TableHeaderRow } from '../TableHeaderRow/index.js';
import { TableRow } from '../TableRow/index.js';
import { TableSelection } from '../TableSelection/index.js';
+import { TableVirtualizer } from '../TableVirtualizer/index.js';
import { Table } from './index.js';
const meta = {
@@ -182,3 +185,72 @@ export const WithSelection: Story = {
);
}
};
+
+const dataLargeWithPosition = dataLarge.map((item, index) => ({ ...item, position: index }));
+
+export const VirtualizedTableRows: Story = {
+ args: { className: 'tableHeightContentDensity' },
+ render(args) {
+ const [data, setData] = useState(dataLargeWithPosition.slice(0, 9));
+ const [isCozy, setIsCozy] = useState(true);
+
+ const handleRangeChange: TableVirtualizerPropTypes['onRangeChange'] = (e) => {
+ const { first, last } = e.detail;
+
+ // overscanCount = 2
+ const overscanCountStart = Math.max(first - 2, 0);
+ const overscanCountEnd = Math.min(last + 2, dataLargeWithPosition.length);
+ setData(dataLargeWithPosition.slice(overscanCountStart, overscanCountEnd));
+ };
+
+ // adjust row height according to content-density mode (only for demo purposes)
+ useEffect(() => {
+ const body = document.body;
+ if (!body) return;
+
+ const observer = new MutationObserver((mutationsList) => {
+ mutationsList.forEach((mutation) => {
+ if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
+ setIsCozy(!body.classList.contains('ui5-content-density-compact'));
+ }
+ });
+ });
+ observer.observe(body, { attributes: true, attributeFilter: ['class'] });
+ return () => {
+ observer.disconnect();
+ };
+ }, []);
+
+ return (
+
+ Name
+ Age
+ Friend Name
+ Friend Age
+
+ }
+ features={}
+ >
+ {data.map((row) => (
+
+
+ {row.name}
+
+
+ {row.age}
+
+
+ {row.friend.name}
+
+
+ {row.friend.age}
+
+
+ ))}
+
+ );
+ }
+};