From 96baa0a4f510958c21c13d85ddf1bd22b55c94b0 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Fri, 15 Aug 2025 08:27:46 +0200 Subject: [PATCH 1/9] wip --- .../AnalyticalTable.stories.tsx | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx index bc31b059f53..3939c16c572 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx @@ -1,3 +1,4 @@ +import type { ReactTableHooks, TableInstance } from '@/components/AnalyticalTable/types/index.js'; import dataLarge from '@sb/mockData/Friends500.json'; import dataTree from '@sb/mockData/FriendsTree.json'; import type { Meta, StoryObj } from '@storybook/react-vite'; @@ -5,7 +6,16 @@ import '@ui5/webcomponents-icons/dist/delete.js'; import '@ui5/webcomponents-icons/dist/edit.js'; import '@ui5/webcomponents-icons/dist/settings.js'; import NoDataIllustration from '@ui5/webcomponents-fiori/dist/illustrations/NoData.js'; -import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { + FocusEventHandler, + KeyboardEventHandler, + useCallback, + useEffect, + useMemo, + useReducer, + useRef, + useState, +} from 'react'; import { AnalyticalTablePopinDisplay, AnalyticalTableScaleWidthMode, @@ -29,6 +39,7 @@ import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/ind import { Select } from '../../webComponents/Select/index.js'; import { Tag } from '../../webComponents/Tag/index.js'; import { Text } from '../../webComponents/Text/index.js'; +import { Input } from '../../webComponents/input/index.js'; import { FlexBox } from '../FlexBox/index.js'; import type { AnalyticalTableColumnDefinition } from './index.js'; import { AnalyticalTable } from './index.js'; @@ -625,3 +636,162 @@ export const KitchenSink: Story = { return context.viewMode === 'story' ? : ; }, }; + +const inputCols = [ + { + Header: 'Input', + id: 'input', + Cell: (props) => { + return ( + + ); + }, + interactiveElementName: 'Input', + }, + { + Header: 'Button', + id: 'btn', + Cell: (props) => ( + + ), + interactiveElementName: () => 'Button', + }, +]; + +const useGetTableProps = (props, { instance }) => { + const handleFocus: FocusEventHandler = (e) => { + const isCell = e.target.hasAttribute('gridcell') || e.target.hasAttribute('columnheader'); + if (isCell) { + } + if (typeof props.onFocus === 'function') { + props.onFocus(e); + } + }; + + const handleKeyDown: KeyboardEventHandler = (e) => {}; + + return [props, { onFocus: handleFocus, onKeyDown: handleKeyDown }]; +}; + +function findFirstFocusableInside(element) { + if (!element) return null; + + function recursiveFindInteractiveElement(el) { + for (const child of el.children) { + const style = getComputedStyle(child); + if (child.disabled || style.display === 'none' || style.visibility === 'hidden') { + continue; // skip non-interactive + } + + const focusableSelectors = [ + 'a[href]', + 'button', + 'input', + 'textarea', + 'select', + '[tabindex]:not([tabindex="-1"])', + ]; + + if (child.matches(focusableSelectors.join(','))) { + return child; + } + + if (child.shadowRoot) { + const shadowFocusable = recursiveFindInteractiveElement(child.shadowRoot); + if (shadowFocusable) return shadowFocusable; + } + + const nestedFocusable = recursiveFindInteractiveElement(child); + if (nestedFocusable) return nestedFocusable; + } + return null; + } + + return recursiveFindInteractiveElement(element); +} + +const useCellTabIndex = (cols, { instance: { state } }) => { + console.log(state.cellTabIndex); + return cols.map((col) => { + const origCell = col.Cell; + + // only wrap function renderers, non-function renderers don't receive props anyway. + if (typeof origCell !== 'function') return col; + + return { + ...col, + Cell: (props: any) => { + return origCell({ ...props, tabIndex: state.cellTabIndex ?? -1 }); + }, + }; + }); +}; + +const useGetCellProps = (props, { cell, instance, userProps }) => { + const { interactiveElementName, Cell } = cell.column; + + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === 'F2') { + if (e.currentTarget === e.target && interactiveElementName) { + let name: string; + const interactiveElement = findFirstFocusableInside(e.target); + if (interactiveElement) { + e.currentTarget.tabIndex = -1; + interactiveElement.focus(); + instance.dispatch({ type: 'CELL_TAB_INDEX', payload: 0 }); + if (typeof interactiveElementName === 'function') { + name = interactiveElementName(cell); + } else { + name = interactiveElementName; + } + } + } + if (e.currentTarget !== e.target) { + e.currentTarget.tabIndex = 0; + e.currentTarget.focus(); + instance.dispatch({ type: 'CELL_TAB_INDEX', payload: -1 }); + } + } + }; + + return [props, { onKeyDown: handleKeyDown }]; +}; + +const stateReducer = (state, action, _prevState, instance: TableInstance) => { + const { payload, type } = action; + + if (type === 'CELL_TAB_INDEX') { + return { ...state, cellTabIndex: payload }; + } + return state; +}; + +const useF2Navigation = (hooks: ReactTableHooks) => { + // const prevFocusedCell = useRef(null); + //todo: param names claim functions are hooks, but they aren't + hooks.visibleColumns.push(useCellTabIndex); + hooks.getCellProps.push(useGetCellProps); + hooks.stateReducers.push(stateReducer); + // hooks.getTableProps.push(useGetTableProps); + // hooks.getHeaderProps.push(setHeaderProps); +}; + +const tableHooks = [useF2Navigation]; + +export const Test: Story = { + render(args) { + return ( + <> + + + + + ); + }, +}; From 8e1c00110b7a6544262c25dca7d0633585fbe129 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 18 Aug 2025 16:35:27 +0200 Subject: [PATCH 2/9] feat(AnalyticalTable): introduce `useF2CellEdit` plugin hook --- .../hooks/useKeyboardNavigation.ts | 40 ++++-- .../pluginHooks/AnalyticalTableHooks.ts | 2 + .../pluginHooks/useF2CellEdit.ts | 131 ++++++++++++++++++ .../components/AnalyticalTable/types/index.ts | 3 +- .../main/src/i18n/messagebundle.properties | 4 +- 5 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 packages/main/src/components/AnalyticalTable/pluginHooks/useF2CellEdit.ts diff --git a/packages/main/src/components/AnalyticalTable/hooks/useKeyboardNavigation.ts b/packages/main/src/components/AnalyticalTable/hooks/useKeyboardNavigation.ts index 412d3dc40ed..a8060aad58c 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useKeyboardNavigation.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useKeyboardNavigation.ts @@ -1,9 +1,20 @@ +import type { KeyboardEventHandler } from 'react'; import { useCallback, useEffect, useRef } from 'react'; import { actions } from 'react-table'; import type { ColumnType, ReactTableHooks, TableInstance } from '../types/index.js'; import { getLeafHeaders } from '../util/index.js'; const CELL_DATA_ATTRIBUTES = ['visibleColumnIndex', 'columnIndex', 'rowIndex', 'visibleRowIndex']; +const NAVIGATION_KEYS = new Set([ + 'End', + 'Home', + 'PageDown', + 'PageUp', + 'ArrowRight', + 'ArrowLeft', + 'ArrowDown', + 'ArrowUp', +]); const getFirstVisibleCell = (target, currentlyFocusedCell, noData) => { if ( @@ -175,9 +186,13 @@ const useGetTableProps = ( currentlyFocusedCell.current.dataset.rowIndex ?? currentlyFocusedCell.current.dataset.subcomponentRowIndex, 10, ); + + if (NAVIGATION_KEYS.has(e.key)) { + e.preventDefault(); + } + switch (e.key) { case 'End': { - e.preventDefault(); const visibleColumns = tableRef.current.querySelector( `div[data-component-name="AnalyticalTableHeaderRow"]`, ).children; @@ -200,7 +215,6 @@ const useGetTableProps = ( break; } case 'Home': { - e.preventDefault(); const newElement = tableRef.current.querySelector( `div[data-visible-column-index="0"][data-row-index="${rowIndex}"]`, ); @@ -208,7 +222,6 @@ const useGetTableProps = ( break; } case 'PageDown': { - e.preventDefault(); if (currentlyFocusedCell.current.dataset.rowIndex === '0') { const newElement = tableRef.current.querySelector( `div[data-column-index="${columnIndex}"][data-row-index="${rowIndex + 1}"]`, @@ -225,7 +238,6 @@ const useGetTableProps = ( break; } case 'PageUp': { - e.preventDefault(); if (currentlyFocusedCell.current.dataset.rowIndex <= '1') { const newElement = tableRef.current.querySelector( `div[data-column-index="${columnIndex}"][data-row-index="0"]`, @@ -240,7 +252,6 @@ const useGetTableProps = ( break; } case 'ArrowRight': { - e.preventDefault(); if (isActiveItemInSubComponent) { navigateFromActiveSubCompItem(currentlyFocusedCell, e); return; @@ -256,7 +267,6 @@ const useGetTableProps = ( break; } case 'ArrowLeft': { - e.preventDefault(); if (isActiveItemInSubComponent) { navigateFromActiveSubCompItem(currentlyFocusedCell, e); return; @@ -272,7 +282,6 @@ const useGetTableProps = ( break; } case 'ArrowDown': { - e.preventDefault(); if (isActiveItemInSubComponent) { navigateFromActiveSubCompItem(currentlyFocusedCell, e); return; @@ -296,7 +305,6 @@ const useGetTableProps = ( break; } case 'ArrowUp': { - e.preventDefault(); if (isActiveItemInSubComponent) { navigateFromActiveSubCompItem(currentlyFocusedCell, e); return; @@ -332,11 +340,25 @@ const useGetTableProps = ( if (showOverlay) { return tableProps; } + + // keyboard nav is only enabled if the table is not in edit mode + const handleEditModeKeyDown: KeyboardEventHandler = (e) => { + if (typeof tableProps.onKeyDown === 'function') { + tableProps.onKeyDown(e); + } + if (NAVIGATION_KEYS.has(e.key)) { + e.preventDefault(); + } + }; + return [ tableProps, { onFocus: onTableFocus, - onKeyDown: onKeyboardNavigation, + onKeyDown: + state.cellContentTabIndex === -1 || state.cellContentTabIndex == null + ? onKeyboardNavigation + : handleEditModeKeyDown, onBlur: onTableBlur, }, ]; diff --git a/packages/main/src/components/AnalyticalTable/pluginHooks/AnalyticalTableHooks.ts b/packages/main/src/components/AnalyticalTable/pluginHooks/AnalyticalTableHooks.ts index 6d7ab88c65a..14dfe7d5d79 100644 --- a/packages/main/src/components/AnalyticalTable/pluginHooks/AnalyticalTableHooks.ts +++ b/packages/main/src/components/AnalyticalTable/pluginHooks/AnalyticalTableHooks.ts @@ -1,4 +1,5 @@ import { useAnnounceEmptyCells } from './useAnnounceEmptyCells.js'; +import { useF2CellEdit } from './useF2CellEdit.js'; import { useIndeterminateRowSelection } from './useIndeterminateRowSelection.js'; import { useManualRowSelect } from './useManualRowSelect.js'; import { useOnColumnResize } from './useOnColumnResize.js'; @@ -7,6 +8,7 @@ import { useRowDisableSelection } from './useRowDisableSelection.js'; export { useAnnounceEmptyCells, + useF2CellEdit, useIndeterminateRowSelection, useManualRowSelect, useOnColumnResize, diff --git a/packages/main/src/components/AnalyticalTable/pluginHooks/useF2CellEdit.ts b/packages/main/src/components/AnalyticalTable/pluginHooks/useF2CellEdit.ts new file mode 100644 index 00000000000..a5b714ea8fa --- /dev/null +++ b/packages/main/src/components/AnalyticalTable/pluginHooks/useF2CellEdit.ts @@ -0,0 +1,131 @@ +import type { Ui5DomRef } from '@ui5/webcomponents-react-base'; +import { useI18nBundle } from '@ui5/webcomponents-react-base'; +import type { FocusEventHandler, KeyboardEventHandler } from 'react'; +import { useCallback } from 'react'; +import { INCLUDES_X } from '../../../i18n/i18n-defaults.js'; +import type { ReactTableHooks, TableInstance } from '../types/index.js'; + +//todo: how to export +export const useEditModeCallbackRef = (props) => { + const cellContentTabIndex = + props.state.cellContentTabIndex === -1 || props.state.cellContentTabIndex === undefined ? '-1' : '0'; + return useCallback( + (node: HTMLElement) => { + if (node) { + if (node.tagName.startsWith('UI5')) { + void (node as Ui5DomRef) + .getFocusDomRefAsync() + .then((focusNode) => focusNode.setAttribute('tabindex', cellContentTabIndex)) + .catch(() => { + // fail silently + }); + } else { + node.setAttribute('tabindex', cellContentTabIndex); + } + } + }, + [cellContentTabIndex], + ); +}; + +//todo: memoize everything - getCellProps is called often +export const useF2CellEdit = (hooks: ReactTableHooks) => { + const i18nBundle = useI18nBundle('@ui5/webcomponents-react'); + + const setCellProps = useCallback( + (props, { cell, instance }: { cell: Record; instance: TableInstance }) => { + const { dispatch } = instance; + const { interactiveElementName } = cell.column; + const inputName = + typeof interactiveElementName === 'function' ? interactiveElementName(cell) : interactiveElementName; + const ariaLabel = + (interactiveElementName ? i18nBundle.getText(INCLUDES_X, inputName) : '') + ' ' + props['aria-label']; + + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === 'F2') { + e.preventDefault(); + if (e.currentTarget === e.target && interactiveElementName) { + const interactiveElement = findFirstFocusableInside(e.target as HTMLElement); + if (interactiveElement) { + dispatch({ type: 'CELL_CONTENT_TAB_INDEX', payload: 0 }); + e.currentTarget.tabIndex = -1; + requestAnimationFrame(() => { + interactiveElement.focus(); + }); + } + } + if (e.currentTarget !== e.target) { + dispatch({ type: 'CELL_CONTENT_TAB_INDEX', payload: -1 }); + e.currentTarget.tabIndex = 0; + e.currentTarget.focus(); + } + } + }; + + const handleFocus: FocusEventHandler = (e) => { + if (typeof props.onFocus === 'function') { + props.onFocus(e); + } + + if (e.currentTarget !== e.target) { + dispatch({ type: 'CELL_CONTENT_TAB_INDEX', payload: 0 }); + } else { + dispatch({ type: 'CELL_CONTENT_TAB_INDEX', payload: -1 }); + } + }; + + return [props, { onKeyDown: handleKeyDown, onFocus: handleFocus, 'aria-label': ariaLabel }]; + }, + [i18nBundle], + ); + + hooks.getCellProps.push(setCellProps); + hooks.stateReducers.push(stateReducer); +}; +useF2CellEdit.pluginName = 'useF2CellEdit'; + +const stateReducer: TableInstance['stateReducer'] = (state, action, _prevState) => { + const { payload, type } = action; + + if (type === 'CELL_CONTENT_TAB_INDEX') { + return { ...state, cellContentTabIndex: payload }; + } + return state; +}; + +function findFirstFocusableInside(element: HTMLElement) { + if (!element) return null; + + function recursiveFindInteractiveElement(el) { + for (const child of el.children) { + const style = getComputedStyle(child); + if (child.disabled || style.display === 'none' || style.visibility === 'hidden') { + continue; + } + + const focusableSelectors = [ + 'a[href]', + 'button', + 'input', + 'textarea', + 'select', + '[tabindex]:not([tabindex="-1"])', + ]; + + if (child.matches(focusableSelectors.join(','))) { + return child; + } + + if (child.shadowRoot) { + const shadowFocusable = recursiveFindInteractiveElement(child.shadowRoot); + if (shadowFocusable) return shadowFocusable; + } + + const nestedFocusable = recursiveFindInteractiveElement(child); + if (nestedFocusable) return nestedFocusable; + } + return null; + } + + return recursiveFindInteractiveElement(element); +} diff --git a/packages/main/src/components/AnalyticalTable/types/index.ts b/packages/main/src/components/AnalyticalTable/types/index.ts index 28ba4e3b3c6..1ad3a7591b2 100644 --- a/packages/main/src/components/AnalyticalTable/types/index.ts +++ b/packages/main/src/components/AnalyticalTable/types/index.ts @@ -103,7 +103,7 @@ export interface TableInstance { disableSortBy?: boolean; dispatch?: (action: { type: string; - payload?: Record | AnalyticalTableState['popInColumns'] | boolean | string; + payload?: Record | AnalyticalTableState['popInColumns'] | boolean | string | number; clientX?: number; }) => void; expandedDepth?: number; @@ -319,6 +319,7 @@ export interface AnalyticalTableState { interactiveRowsHavePopIn?: boolean; tableColResized?: true; triggerScroll?: TriggerScrollState; + cellContentTabIndex?: number; } interface Filter { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 979a4abb827..55f2c9d37ce 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -378,7 +378,9 @@ HAS_DETAILS=Has Details #XACT: Message Item counter label COUNTER=Counter -#ACT: Screen reader announcement text for selection in the SelectDialog component when multi-selection mode is active. The placeholder represents a number. +#XACT: Screen reader announcement text for selection in the SelectDialog component when multi-selection mode is active. The placeholder represents a number. SELECTED_ITEMS=Selected Items {0} +#XACT: Screen reader announcement for table cell that includes interactive element/s. The placeholder is the name of the element (e.g. "Input"). +INCLUDES_X=Includes {0} From 3e698ff9420220c860aad3ab337a5d3eeaa53a2a Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Tue, 19 Aug 2025 17:36:39 +0200 Subject: [PATCH 3/9] add story, export types, add types --- .../AnalyticalTableHooks.stories.tsx | 85 ++++++++++- .../AnalyticalTable/PluginF2CellEdit.mdx | 114 ++++++++++++++ .../AnalyticalTable/defaults/Column/Cell.tsx | 10 +- .../hooks/useDynamicColumnWidths.ts | 6 +- .../hooks/useKeyboardNavigation.ts | 63 +++----- .../src/components/AnalyticalTable/index.tsx | 3 + .../pluginHooks/useF2CellEdit.ts | 140 +++++++++++++----- .../components/AnalyticalTable/types/index.ts | 25 +++- .../components/AnalyticalTable/util/index.ts | 92 +++++++----- 9 files changed, 419 insertions(+), 119 deletions(-) create mode 100644 packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx index 9f1c4202f96..cc04178fb0e 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx @@ -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, @@ -293,3 +304,75 @@ export const PluginOrderedMultiSort = { ); }, }; + +const inputCols: AnalyticalTableColumnDefinition[] = [ + { + Header: 'Input', + id: 'input', + Cell: (props: AnalyticalTableCellInstance) => { + const callbackRef = useF2CellEdit.useCallbackRef(props); + return ; + }, + interactiveElementName: 'Input', + }, + { + Header: 'Input & Button', + id: 'input_btn', + Cell: (props: AnalyticalTableCellInstance) => { + const callbackRef = useF2CellEdit.useCallbackRef(props); + return ( + <> + + ; + }, + interactiveElementName: () => 'Button', + }, + { + Header: 'Non-interactive custom content', + accessor: 'friend.name', + Cell: (props: AnalyticalTableCellInstance) => { + return {props.value}; + }, + }, + { + Header: 'Switch or CheckBox', + id: 'switch_checkbox', + Cell: (props: AnalyticalTableCellInstance) => { + if (props.row.index % 2) { + return ; + } + return ; + }, + interactiveElementName: (props: AnalyticalTableCellInstance) => { + if (props.row.index % 2) { + return 'CheckBox'; + } + return 'Switch'; + }, + }, +]; + +const tableHooks = [useF2CellEdit]; + +export const F2CellEdit: Story = { + render(args) { + return ( + + ); + }, +}; diff --git a/packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx b/packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx new file mode 100644 index 00000000000..8475c904d3c --- /dev/null +++ b/packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx @@ -0,0 +1,114 @@ +import { ImportStatement } from '@sb/components/Import'; +import { Canvas, Meta } from '@storybook/addon-docs/blocks'; +import { Footer } from '@sb/components'; +import * as ComponentStories from './AnalyticalTableHooks.stories'; + + + +# AnalyticalTable Plugin: useF2CellEdit + + + +**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 + + + +### 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(props); + return ; + }, + interactiveElementName: 'Input', + }, + { + Header: 'Input & Button', + id: 'input_btn', + Cell: (props: AnalyticalTableCellInstance) => { + const callbackRef = useF2CellEdit.useCallbackRef(props); + return ( + <> + + ; + }, + interactiveElementName: () => 'Button', + }, + { + Header: 'Non-interactive custom content', + accessor: 'friend.name', + Cell: (props: AnalyticalTableCellInstance) => { + return {props.value}; + }, + }, + { + Header: 'Switch or CheckBox', + id: 'switch_checkbox', + Cell: (props: AnalyticalTableCellInstance) => { + if (props.row.index % 2) { + return ; + } + return ; + }, + interactiveElementName: (props: AnalyticalTableCellInstance) => { + if (props.row.index % 2) { + return 'CheckBox'; + } + return 'Switch'; + }, + }, +]; + +const tableHooks: AnalyticalTablePropTypes['tableHooks'] = [useF2CellEdit]; + +function TableWithInputs({ data }) { + return ; +} +``` + +