Skip to content
557 changes: 557 additions & 0 deletions packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ import dataManualSelect from '@sb/mockData/FriendsManualSelect25.json';
import dataTree from '@sb/mockData/FriendsTree.json';
import type { Meta, StoryObj } from '@storybook/react-vite';
import InputType from '@ui5/webcomponents/dist/types/InputType.js';
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';
import { useCallback, useMemo, useReducer, useState } from 'react';
import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../enums';
import { Button, CheckBox, Input, Label, ToggleButton, Text } from '../../webComponents';
import { Button } from '../../webComponents/Button/index.js';
import { CheckBox } from '../../webComponents/CheckBox/index.js';
import type { InputDomRef } from '../../webComponents/Input/index.js';
import { Input } from '../../webComponents/Input/index.js';
import { Label } from '../../webComponents/Label/index.js';
import { Switch } from '../../webComponents/Switch/index.js';
import { Tag } from '../../webComponents/Tag/index.js';
import { Text } from '../../webComponents/Text/index.js';
import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
import { FlexBox } from '../FlexBox';
import meta from './AnalyticalTable.stories';
import * as AnalyticalTableHooks from './pluginHooks/AnalyticalTableHooks';
import { useF2CellEdit } from './pluginHooks/AnalyticalTableHooks';
import { AnalyticalTable } from './index';
import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from './index';

const pluginsMeta = {
...meta,
Expand Down Expand Up @@ -293,3 +304,76 @@ export const PluginOrderedMultiSort = {
);
},
};

const inputCols: AnalyticalTableColumnDefinition[] = [
{
Header: 'Input',
id: 'input',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef<InputDomRef>(props);
return <Input ref={callbackRef} />;
},
interactiveElementName: 'Input',
},
{
Header: 'Input & Button',
id: 'input_btn',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
return (
<>
<Input ref={callbackRef} />
<Button ref={callbackRef} icon={paperPlaneIcon} tooltip="Submit" accessibleName="Submit" />
</>
);
},
interactiveElementName: 'Input and Button',
},
{
Header: 'Text',
accessor: 'name',
},
{
Header: 'Button',
id: 'btn',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
return <Button ref={callbackRef}>Button</Button>;
},
interactiveElementName: () => 'Button',
},
{
Header: 'Non-interactive custom content',
accessor: 'friend.name',
Cell: (props: AnalyticalTableCellInstance) => {
return <Tag>{props.value}</Tag>;
},
},
{
Header: 'Switch or CheckBox',
id: 'switch_checkbox',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
if (props.row.index % 2) {
return <CheckBox ref={callbackRef} accessibleName="Dummy CheckBox" />;
}
return <Switch ref={callbackRef} accessibleName="Dummy Switch" />;
},
interactiveElementName: (props: AnalyticalTableCellInstance) => {
if (props.row.index % 2) {
return 'CheckBox';
}
return 'Switch';
},
},
];

const tableHooks = [useF2CellEdit];

export const F2CellEdit: Story = {
render(args) {
return (
<AnalyticalTable data={args.data.slice(0, 10)} columns={inputCols} tableHooks={tableHooks} visibleRows={5} />
);
},
};
115 changes: 115 additions & 0 deletions packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { ImportStatement } from '@sb/components/Import';
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { DocsHeader, Footer } from '@sb/components';
import * as ComponentStories from './AnalyticalTableHooks.stories';

<Meta title="Data Display / AnalyticalTable / Plugin Hooks / useF2CellEdit" />

# AnalyticalTable Plugin: useF2CellEdit

<ImportStatement moduleNames={['AnalyticalTableHooks']} packageName={'@ui5/webcomponents-react'} />

**Since: v2.14.0**

A plugin hook that enables F2-based cell editing for interactive elements inside a cell.

To **ensure the hook works correctly**, make sure that:

- 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.
- The callback Ref returned by `useF2CellEdit.useCallbackRef` is attached to every interactive element within the cell.

The hook manages focus, keyboard navigation, and `tabindex` for cells with interactive content:

- Pressing `F2` moves focus between the cell container and its first interactive element.
- Updates the cell's `aria-label` with the interactive element's name for accessibility.
- Prevents standard navigation keys from interfering when editing a cell.

## Example

<Canvas of={ComponentStories.F2CellEdit} sourceState="none" />

### Code

```tsx
import type {
AnalyticalTableCellInstance,
AnalyticalTableColumnDefinition,
InputDomRef,
AnalyticalTablePropTypes,
} from '@ui5/webcomponents-react';
import { AnalyticalTableHooks, AnalyticalTable, Button, CheckBox, Input, Switch, Tag } from '@ui5/webcomponents-react';
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';

const { useF2CellEdit } = AnalyticalTableHooks;

const columns: AnalyticalTableColumnDefinition[] = [
{
Header: 'Input',
id: 'input',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef<InputDomRef>(props);
return <Input ref={callbackRef} />;
},
interactiveElementName: 'Input',
},
{
Header: 'Input & Button',
id: 'input_btn',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
return (
<>
<Input ref={callbackRef} />
<Button ref={callbackRef} icon={paperPlaneIcon} tooltip="Submit" accessibleName="Submit" />
</>
);
},
interactiveElementName: 'Input and Button',
},
{
Header: 'Text',
accessor: 'name',
},
{
Header: 'Button',
id: 'btn',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
return <Button ref={callbackRef}>Button</Button>;
},
interactiveElementName: () => 'Button',
},
{
Header: 'Non-interactive custom content',
accessor: 'friend.name',
Cell: (props: AnalyticalTableCellInstance) => {
return <Tag>{props.value}</Tag>;
},
},
{
Header: 'Switch or CheckBox',
id: 'switch_checkbox',
Cell: (props: AnalyticalTableCellInstance) => {
const callbackRef = useF2CellEdit.useCallbackRef(props);
if (props.row.index % 2) {
return <CheckBox ref={callbackRef} accessibleName="Dummy CheckBox" />;
}
return <Switch ref={callbackRef} accessibleName="Dummy Switch" />;
},
interactiveElementName: (props: AnalyticalTableCellInstance) => {
if (props.row.index % 2) {
return 'CheckBox';
}
return 'Switch';
},
},
];

const tableHooks: AnalyticalTablePropTypes['tableHooks'] = [useF2CellEdit];

function TableWithInputs({ data }) {
return <AnalyticalTable data={data} columns={columns} tableHooks={tableHooks} visibleRows={5} />;
}
```

<Footer />
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export const Cell = ({ cell: { value = '', isGrouped }, column, row, webComponentsReactProperties }) => {
import type { CellInstance } from '../../types/index.js';

export const Cell = (props: CellInstance) => {
const {
cell: { value = '', isGrouped },
column,
row,
webComponentsReactProperties,
} = props;
let cellContent = `${value ?? ''}`;
if (isGrouped) {
cellContent += ` (${row.subRows.length})`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,13 @@ const calculateSmartColumns = (columns: AnalyticalTableColumnDefinition[], insta
});
};

const columnsDeps = (
const useColumnsDeps = (
deps,
{ instance: { state, webComponentsReactProperties, visibleColumns, data, rows, columns } },
) => {
const isLoadingPlaceholder = !data?.length && webComponentsReactProperties.loading;
const hasRows = rows?.length > 0;
// eslint-disable-next-line react-hooks/rules-of-hooks

const colsEqual = useMemo(() => {
return visibleColumns
?.filter(
Expand Down Expand Up @@ -497,5 +497,5 @@ const columns = (columns: TableInstance['columns'], { instance }: { instance: Ta

export const useDynamicColumnWidths = (hooks: ReactTableHooks) => {
hooks.columns.push(columns);
hooks.columnsDeps.push(columnsDeps);
hooks.columnsDeps.push(useColumnsDeps);
};
Loading
Loading