Skip to content

Commit f5b276c

Browse files
authored
docs(Table): add DnD and row actions examples (#6947)
Closes #6936 Closes #6943
1 parent 90b7bb6 commit f5b276c

File tree

2 files changed

+228
-29
lines changed

2 files changed

+228
-29
lines changed

packages/main/src/webComponents/Table/Table.mdx

+88
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,94 @@ function VirtualizedTable(props) {
263263

264264
</details>
265265

266+
## Table with row actions
267+
268+
The `TableRowAction` & `TableRowActionNavigation` component lets you incorporate interactive elements into table rows, enabling users to take actions directly related to each row.
269+
The `rowActionCount` prop of the `Table` component determines the width of the row action column. A maximum value of 3 is recommended, as exceeding this limit may take up too much space on smaller screens. If the number of row actions exceeds the `rowActionCount`, an overflow button will appear, providing access to the additional actions.
270+
271+
The `invisible` prop of row actions allows you to hide specific row actions while preserving their space. This can be useful for consistent alignment of row actions across several rows.
272+
273+
<Canvas of={ComponentStories.withRowActions} />
274+
275+
## Drag and rop table rows
276+
277+
Enable drag-and-drop by using the `onMoveOver` and `onMove` event in combination with the `movable` prop on the `TableRow`.
278+
279+
<Canvas of={ComponentStories.dragAndDropRows} />
280+
281+
<details>
282+
283+
<summary>Show static code</summary>
284+
285+
```tsx
286+
function DragAndDropTableRows(props) {
287+
const [rows, setRows] = useState(dataLargeWithPosition.slice(0, 10));
288+
289+
const handleMove: TablePropTypes["onMove"] = (e) => {
290+
const { source, destination } = e.detail;
291+
292+
setRows((prevRows) => {
293+
const sourceIndex = prevRows.findIndex(
294+
(row) => `${row.position}` === source.element.dataset.id,
295+
);
296+
const destinationIndex = prevRows.findIndex(
297+
(row) => `${row.position}` === destination.element.dataset.id,
298+
);
299+
300+
if (sourceIndex === -1 || destinationIndex === -1) {
301+
return prevRows;
302+
}
303+
304+
const updatedRows = [...prevRows];
305+
const [movedRow] = updatedRows.splice(sourceIndex, 1);
306+
307+
if (destination.placement === "Before") {
308+
updatedRows.splice(destinationIndex, 0, movedRow);
309+
} else if (destination.placement === "After") {
310+
updatedRows.splice(destinationIndex + 1, 0, movedRow);
311+
}
312+
313+
return updatedRows;
314+
});
315+
};
316+
317+
const handleMoveOver: TablePropTypes["onMoveOver"] = (e) => {
318+
const { source, destination } = e.detail;
319+
320+
if (
321+
source.element.hasAttribute("ui5-table-row") &&
322+
destination.element.hasAttribute("ui5-table-row") &&
323+
destination.placement !== "On"
324+
) {
325+
e.preventDefault();
326+
}
327+
};
328+
329+
return (
330+
<Table {...props} onMove={handleMove} onMoveOver={handleMoveOver}>
331+
{rows.map((row) => (
332+
<TableRow key={row.position} movable data-id={row.position}>
333+
<TableCell>
334+
<span>{row.name}</span>
335+
</TableCell>
336+
<TableCell>
337+
<span>{row.age}</span>
338+
</TableCell>
339+
<TableCell>
340+
<span>{row.friend.name}</span>
341+
</TableCell>
342+
<TableCell>
343+
<span>{row.friend.age}</span>
344+
</TableCell>
345+
</TableRow>
346+
))}
347+
</Table>
348+
);
349+
}
350+
```
351+
352+
</details>
353+
266354
<Markdown>{SubcomponentsSection}</Markdown>
267355

268356
## TableHeaderRow

packages/main/src/webComponents/Table/Table.stories.tsx

+140-29
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,51 @@ import dataLarge from '@sb/mockData/Friends500.json';
22
import type { Meta, StoryObj } from '@storybook/react';
33
import TableGrowingMode from '@ui5/webcomponents/dist/types/TableGrowingMode.js';
44
import TableSelectionMode from '@ui5/webcomponents/dist/types/TableSelectionMode.js';
5-
import type { TableVirtualizerPropTypes } from '@ui5/webcomponents-react';
5+
import editIcon from '@ui5/webcomponents-icons/dist/edit.js';
6+
import saveIcon from '@ui5/webcomponents-icons/dist/save.js';
7+
import type { TablePropTypes, TableVirtualizerPropTypes } from '@ui5/webcomponents-react';
68
import { SegmentedButton, SegmentedButtonItem } from '@ui5/webcomponents-react';
79
import { useEffect, useState } from 'react';
810
import { TableCell } from '../TableCell/index.js';
911
import { TableGrowing } from '../TableGrowing/index.js';
1012
import { TableHeaderCell } from '../TableHeaderCell/index.js';
1113
import { TableHeaderRow } from '../TableHeaderRow/index.js';
1214
import { TableRow } from '../TableRow/index.js';
15+
import { TableRowAction } from '../TableRowAction/index.js';
16+
import { TableRowActionNavigation } from '../TableRowActionNavigation/index.js';
1317
import { TableSelection } from '../TableSelection/index.js';
1418
import { TableVirtualizer } from '../TableVirtualizer/index.js';
1519
import { Table } from './index.js';
1620

21+
const popInColumns = (
22+
<TableHeaderRow sticky>
23+
<TableHeaderCell width={'200px'} minWidth={'200px'}>
24+
<span>Product</span>
25+
</TableHeaderCell>
26+
<TableHeaderCell minWidth={'200px'}>
27+
<span>Supplier</span>
28+
</TableHeaderCell>
29+
<TableHeaderCell minWidth={'200px'}>
30+
<span>Dimensions</span>
31+
</TableHeaderCell>
32+
<TableHeaderCell minWidth={'100px'} maxWidth="200px">
33+
<span>Weight</span>
34+
</TableHeaderCell>
35+
<TableHeaderCell minWidth="200px">
36+
<span>Price</span>
37+
</TableHeaderCell>
38+
</TableHeaderRow>
39+
);
40+
41+
const columns = (
42+
<TableHeaderRow sticky>
43+
<TableHeaderCell>Name</TableHeaderCell>
44+
<TableHeaderCell>Age</TableHeaderCell>
45+
<TableHeaderCell>Friend Name</TableHeaderCell>
46+
<TableHeaderCell>Friend Age</TableHeaderCell>
47+
</TableHeaderRow>
48+
);
49+
1750
const meta = {
1851
title: 'Data Display / Table',
1952
component: Table,
@@ -23,25 +56,7 @@ const meta = {
2356
children: { control: { disable: true } }
2457
},
2558
args: {
26-
headerRow: (
27-
<TableHeaderRow sticky>
28-
<TableHeaderCell width={'200px'} minWidth={'200px'}>
29-
<span>Product</span>
30-
</TableHeaderCell>
31-
<TableHeaderCell minWidth={'200px'}>
32-
<span>Supplier</span>
33-
</TableHeaderCell>
34-
<TableHeaderCell minWidth={'200px'}>
35-
<span>Dimensions</span>
36-
</TableHeaderCell>
37-
<TableHeaderCell minWidth={'100px'} maxWidth="200px">
38-
<span>Weight</span>
39-
</TableHeaderCell>
40-
<TableHeaderCell minWidth="200px">
41-
<span>Price</span>
42-
</TableHeaderCell>
43-
</TableHeaderRow>
44-
)
59+
headerRow: popInColumns
4560
},
4661
tags: ['package:@ui5/webcomponents']
4762
} satisfies Meta<typeof Table>;
@@ -189,7 +204,7 @@ export const WithSelection: Story = {
189204
const dataLargeWithPosition = dataLarge.map((item, index) => ({ ...item, position: index }));
190205

191206
export const VirtualizedTableRows: Story = {
192-
args: { className: 'tableHeightContentDensity' },
207+
args: { className: 'tableHeightContentDensity', headerRow: columns },
193208
render(args) {
194209
const [data, setData] = useState(dataLargeWithPosition.slice(0, 9));
195210
const [isCozy, setIsCozy] = useState(true);
@@ -224,14 +239,6 @@ export const VirtualizedTableRows: Story = {
224239
return (
225240
<Table
226241
{...args}
227-
headerRow={
228-
<TableHeaderRow sticky>
229-
<TableHeaderCell>Name</TableHeaderCell>
230-
<TableHeaderCell>Age</TableHeaderCell>
231-
<TableHeaderCell>Friend Name</TableHeaderCell>
232-
<TableHeaderCell>Friend Age</TableHeaderCell>
233-
</TableHeaderRow>
234-
}
235242
features={<TableVirtualizer rowCount={500} rowHeight={isCozy ? 44 : 32} onRangeChange={handleRangeChange} />}
236243
>
237244
{data.map((row) => (
@@ -254,3 +261,107 @@ export const VirtualizedTableRows: Story = {
254261
);
255262
}
256263
};
264+
265+
export const withRowActions: Story = {
266+
args: { headerRow: columns, rowActionCount: 3 },
267+
render(args) {
268+
return (
269+
<Table {...args}>
270+
{dataLarge.slice(0, 10).map((row, index) => (
271+
<TableRow
272+
key={`${row.name}-${row.age}`}
273+
actions={
274+
<>
275+
<TableRowAction icon={editIcon}>Edit</TableRowAction>
276+
<TableRowAction icon={saveIcon} disabled>
277+
Save
278+
</TableRowAction>
279+
<TableRowActionNavigation interactive={!!(index % 2)} />
280+
</>
281+
}
282+
>
283+
<TableCell>
284+
<span>{row.name}</span>
285+
</TableCell>
286+
<TableCell>
287+
<span>{row.age}</span>
288+
</TableCell>
289+
<TableCell>
290+
<span>{row.friend.name}</span>
291+
</TableCell>
292+
<TableCell>
293+
<span>{row.friend.age}</span>
294+
</TableCell>
295+
</TableRow>
296+
))}
297+
</Table>
298+
);
299+
}
300+
};
301+
302+
export const dragAndDropRows: Story = {
303+
args: { headerRow: columns },
304+
render(args) {
305+
const [rows, setRows] = useState(dataLargeWithPosition.slice(0, 10));
306+
307+
const handleMove: TablePropTypes['onMove'] = (e) => {
308+
const { source, destination } = e.detail;
309+
// enabling this causes the Storybook to freeze due to the number of logs
310+
// args.onMove(e);
311+
312+
setRows((prevRows) => {
313+
const sourceIndex = prevRows.findIndex((row) => `${row.position}` === source.element.dataset.id);
314+
const destinationIndex = prevRows.findIndex((row) => `${row.position}` === destination.element.dataset.id);
315+
316+
if (sourceIndex === -1 || destinationIndex === -1) {
317+
return prevRows;
318+
}
319+
320+
const updatedRows = [...prevRows];
321+
const [movedRow] = updatedRows.splice(sourceIndex, 1);
322+
323+
if (destination.placement === 'Before') {
324+
updatedRows.splice(destinationIndex, 0, movedRow);
325+
} else if (destination.placement === 'After') {
326+
updatedRows.splice(destinationIndex + 1, 0, movedRow);
327+
}
328+
329+
return updatedRows;
330+
});
331+
};
332+
333+
const handleMoveOver: TablePropTypes['onMoveOver'] = (e) => {
334+
const { source, destination } = e.detail;
335+
// args.onMoveOver(e);
336+
337+
if (
338+
source.element.hasAttribute('ui5-table-row') &&
339+
destination.element.hasAttribute('ui5-table-row') &&
340+
destination.placement !== 'On'
341+
) {
342+
e.preventDefault();
343+
}
344+
};
345+
346+
return (
347+
<Table {...args} onMove={handleMove} onMoveOver={handleMoveOver}>
348+
{rows.map((row) => (
349+
<TableRow key={row.position} movable data-id={row.position}>
350+
<TableCell>
351+
<span>{row.name}</span>
352+
</TableCell>
353+
<TableCell>
354+
<span>{row.age}</span>
355+
</TableCell>
356+
<TableCell>
357+
<span>{row.friend.name}</span>
358+
</TableCell>
359+
<TableCell>
360+
<span>{row.friend.age}</span>
361+
</TableCell>
362+
</TableRow>
363+
))}
364+
</Table>
365+
);
366+
}
367+
};

0 commit comments

Comments
 (0)