Skip to content

Commit 86bb7e1

Browse files
authored
feat(AnalyticalTable): introduce useF2CellEdit plugin hook (#7666)
Closes #6161
1 parent da20bb6 commit 86bb7e1

File tree

11 files changed

+1105
-78
lines changed

11 files changed

+1105
-78
lines changed

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

Lines changed: 557 additions & 0 deletions
Large diffs are not rendered by default.

packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@ import dataManualSelect from '@sb/mockData/FriendsManualSelect25.json';
44
import dataTree from '@sb/mockData/FriendsTree.json';
55
import type { Meta, StoryObj } from '@storybook/react-vite';
66
import InputType from '@ui5/webcomponents/dist/types/InputType.js';
7+
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';
78
import { useCallback, useMemo, useReducer, useState } from 'react';
89
import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../enums';
9-
import { Button, CheckBox, Input, Label, ToggleButton, Text } from '../../webComponents';
10+
import { Button } from '../../webComponents/Button/index.js';
11+
import { CheckBox } from '../../webComponents/CheckBox/index.js';
12+
import type { InputDomRef } from '../../webComponents/Input/index.js';
13+
import { Input } from '../../webComponents/Input/index.js';
14+
import { Label } from '../../webComponents/Label/index.js';
15+
import { Switch } from '../../webComponents/Switch/index.js';
16+
import { Tag } from '../../webComponents/Tag/index.js';
17+
import { Text } from '../../webComponents/Text/index.js';
18+
import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
1019
import { FlexBox } from '../FlexBox';
1120
import meta from './AnalyticalTable.stories';
1221
import * as AnalyticalTableHooks from './pluginHooks/AnalyticalTableHooks';
22+
import { useF2CellEdit } from './pluginHooks/AnalyticalTableHooks';
1323
import { AnalyticalTable } from './index';
24+
import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from './index';
1425

1526
const pluginsMeta = {
1627
...meta,
@@ -293,3 +304,76 @@ export const PluginOrderedMultiSort = {
293304
);
294305
},
295306
};
307+
308+
const inputCols: AnalyticalTableColumnDefinition[] = [
309+
{
310+
Header: 'Input',
311+
id: 'input',
312+
Cell: (props: AnalyticalTableCellInstance) => {
313+
const callbackRef = useF2CellEdit.useCallbackRef<InputDomRef>(props);
314+
return <Input ref={callbackRef} />;
315+
},
316+
interactiveElementName: 'Input',
317+
},
318+
{
319+
Header: 'Input & Button',
320+
id: 'input_btn',
321+
Cell: (props: AnalyticalTableCellInstance) => {
322+
const callbackRef = useF2CellEdit.useCallbackRef(props);
323+
return (
324+
<>
325+
<Input ref={callbackRef} />
326+
<Button ref={callbackRef} icon={paperPlaneIcon} tooltip="Submit" accessibleName="Submit" />
327+
</>
328+
);
329+
},
330+
interactiveElementName: 'Input and Button',
331+
},
332+
{
333+
Header: 'Text',
334+
accessor: 'name',
335+
},
336+
{
337+
Header: 'Button',
338+
id: 'btn',
339+
Cell: (props: AnalyticalTableCellInstance) => {
340+
const callbackRef = useF2CellEdit.useCallbackRef(props);
341+
return <Button ref={callbackRef}>Button</Button>;
342+
},
343+
interactiveElementName: () => 'Button',
344+
},
345+
{
346+
Header: 'Non-interactive custom content',
347+
accessor: 'friend.name',
348+
Cell: (props: AnalyticalTableCellInstance) => {
349+
return <Tag>{props.value}</Tag>;
350+
},
351+
},
352+
{
353+
Header: 'Switch or CheckBox',
354+
id: 'switch_checkbox',
355+
Cell: (props: AnalyticalTableCellInstance) => {
356+
const callbackRef = useF2CellEdit.useCallbackRef(props);
357+
if (props.row.index % 2) {
358+
return <CheckBox ref={callbackRef} accessibleName="Dummy CheckBox" />;
359+
}
360+
return <Switch ref={callbackRef} accessibleName="Dummy Switch" />;
361+
},
362+
interactiveElementName: (props: AnalyticalTableCellInstance) => {
363+
if (props.row.index % 2) {
364+
return 'CheckBox';
365+
}
366+
return 'Switch';
367+
},
368+
},
369+
];
370+
371+
const tableHooks = [useF2CellEdit];
372+
373+
export const F2CellEdit: Story = {
374+
render(args) {
375+
return (
376+
<AnalyticalTable data={args.data.slice(0, 10)} columns={inputCols} tableHooks={tableHooks} visibleRows={5} />
377+
);
378+
},
379+
};
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { ImportStatement } from '@sb/components/Import';
2+
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
3+
import { DocsHeader, Footer } from '@sb/components';
4+
import * as ComponentStories from './AnalyticalTableHooks.stories';
5+
6+
<Meta title="Data Display / AnalyticalTable / Plugin Hooks / useF2CellEdit" />
7+
8+
# AnalyticalTable Plugin: useF2CellEdit
9+
10+
<ImportStatement moduleNames={['AnalyticalTableHooks']} packageName={'@ui5/webcomponents-react'} />
11+
12+
**Since: v2.14.0**
13+
14+
A plugin hook that enables F2-based cell editing for interactive elements inside a cell.
15+
16+
To **ensure the hook works correctly**, make sure that:
17+
18+
- Each column containing interactive elements has the `interactiveElementName` property set. **Note:** This property is also used to describe the cell's content for screen readers.
19+
- The callback Ref returned by `useF2CellEdit.useCallbackRef` is attached to every interactive element within the cell.
20+
21+
The hook manages focus, keyboard navigation, and `tabindex` for cells with interactive content:
22+
23+
- Pressing `F2` moves focus between the cell container and its first interactive element.
24+
- Updates the cell's `aria-label` with the interactive element's name for accessibility.
25+
- Prevents standard navigation keys from interfering when editing a cell.
26+
27+
## Example
28+
29+
<Canvas of={ComponentStories.F2CellEdit} sourceState="none" />
30+
31+
### Code
32+
33+
```tsx
34+
import type {
35+
AnalyticalTableCellInstance,
36+
AnalyticalTableColumnDefinition,
37+
InputDomRef,
38+
AnalyticalTablePropTypes,
39+
} from '@ui5/webcomponents-react';
40+
import { AnalyticalTableHooks, AnalyticalTable, Button, CheckBox, Input, Switch, Tag } from '@ui5/webcomponents-react';
41+
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';
42+
43+
const { useF2CellEdit } = AnalyticalTableHooks;
44+
45+
const columns: AnalyticalTableColumnDefinition[] = [
46+
{
47+
Header: 'Input',
48+
id: 'input',
49+
Cell: (props: AnalyticalTableCellInstance) => {
50+
const callbackRef = useF2CellEdit.useCallbackRef<InputDomRef>(props);
51+
return <Input ref={callbackRef} />;
52+
},
53+
interactiveElementName: 'Input',
54+
},
55+
{
56+
Header: 'Input & Button',
57+
id: 'input_btn',
58+
Cell: (props: AnalyticalTableCellInstance) => {
59+
const callbackRef = useF2CellEdit.useCallbackRef(props);
60+
return (
61+
<>
62+
<Input ref={callbackRef} />
63+
<Button ref={callbackRef} icon={paperPlaneIcon} tooltip="Submit" accessibleName="Submit" />
64+
</>
65+
);
66+
},
67+
interactiveElementName: 'Input and Button',
68+
},
69+
{
70+
Header: 'Text',
71+
accessor: 'name',
72+
},
73+
{
74+
Header: 'Button',
75+
id: 'btn',
76+
Cell: (props: AnalyticalTableCellInstance) => {
77+
const callbackRef = useF2CellEdit.useCallbackRef(props);
78+
return <Button ref={callbackRef}>Button</Button>;
79+
},
80+
interactiveElementName: () => 'Button',
81+
},
82+
{
83+
Header: 'Non-interactive custom content',
84+
accessor: 'friend.name',
85+
Cell: (props: AnalyticalTableCellInstance) => {
86+
return <Tag>{props.value}</Tag>;
87+
},
88+
},
89+
{
90+
Header: 'Switch or CheckBox',
91+
id: 'switch_checkbox',
92+
Cell: (props: AnalyticalTableCellInstance) => {
93+
const callbackRef = useF2CellEdit.useCallbackRef(props);
94+
if (props.row.index % 2) {
95+
return <CheckBox ref={callbackRef} accessibleName="Dummy CheckBox" />;
96+
}
97+
return <Switch ref={callbackRef} accessibleName="Dummy Switch" />;
98+
},
99+
interactiveElementName: (props: AnalyticalTableCellInstance) => {
100+
if (props.row.index % 2) {
101+
return 'CheckBox';
102+
}
103+
return 'Switch';
104+
},
105+
},
106+
];
107+
108+
const tableHooks: AnalyticalTablePropTypes['tableHooks'] = [useF2CellEdit];
109+
110+
function TableWithInputs({ data }) {
111+
return <AnalyticalTable data={data} columns={columns} tableHooks={tableHooks} visibleRows={5} />;
112+
}
113+
```
114+
115+
<Footer />

packages/main/src/components/AnalyticalTable/defaults/Column/Cell.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
export const Cell = ({ cell: { value = '', isGrouped }, column, row, webComponentsReactProperties }) => {
1+
import type { CellInstance } from '../../types/index.js';
2+
3+
export const Cell = (props: CellInstance) => {
4+
const {
5+
cell: { value = '', isGrouped },
6+
column,
7+
row,
8+
webComponentsReactProperties,
9+
} = props;
210
let cellContent = `${value ?? ''}`;
311
if (isGrouped) {
412
cellContent += ` (${row.subRows.length})`;

packages/main/src/components/AnalyticalTable/hooks/useDynamicColumnWidths.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,13 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
318318
});
319319
};
320320

321-
const columnsDeps = (
321+
const useColumnsDeps = (
322322
deps,
323323
{ instance: { state, webComponentsReactProperties, visibleColumns, data, rows, columns } },
324324
) => {
325325
const isLoadingPlaceholder = !data?.length && webComponentsReactProperties.loading;
326326
const hasRows = rows?.length > 0;
327-
// eslint-disable-next-line react-hooks/rules-of-hooks
327+
328328
const colsEqual = useMemo(() => {
329329
return visibleColumns
330330
?.filter(
@@ -497,5 +497,5 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta
497497

498498
export const useDynamicColumnWidths = (hooks: ReactTableHooks) => {
499499
hooks.columns.push(columns);
500-
hooks.columnsDeps.push(columnsDeps);
500+
hooks.columnsDeps.push(useColumnsDeps);
501501
};

0 commit comments

Comments
 (0)