From 5e333f2a32c64dfc92d1b60f74807c86f9c982d4 Mon Sep 17 00:00:00 2001 From: MarcusNotheis Date: Wed, 1 Apr 2020 10:51:46 +0200 Subject: [PATCH 1/4] refactor(AnalyticalTable): Simplify implemention by reusing react-table properties BREAKING CHANGE: column option `groupable` replaced by `disableGroupBy` BREAKING CHANGE: column option `sortable` replaced by `disableSortBy` BREAKING CHANGE: column option `filterable` replaced by `disableFilters` BREAKING CHANGE: Enabling grouping, sorting or filtering by e.g `disableGroupBy: false` will not overwrite the table overall setting in case e.g. `groupable={false}` --- .../ColumnHeader/ColumnHeaderModal.tsx | 20 +++---- .../AnalyticalTable/ColumnHeader/index.tsx | 31 ++++------- .../AnalyticalTable/demo/demo.stories.tsx | 4 +- .../AnalyticalTable/hooks/useRowHighlight.tsx | 6 +-- .../hooks/useRowSelectionColumn.tsx | 12 ++--- .../hooks/useTableCellStyling.ts | 5 +- .../hooks/useTableHeaderStyling.ts | 3 +- .../src/components/AnalyticalTable/index.tsx | 53 ++++++++----------- .../AnalyticalTableColumnDefinition.ts | 46 ++++++++++++++++ 9 files changed, 100 insertions(+), 80 deletions(-) create mode 100644 packages/main/src/interfaces/AnalyticalTableColumnDefinition.ts diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx index b482280ba03..f3c7becd6be 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx @@ -15,19 +15,19 @@ import { ColumnType } from '../types/ColumnType'; export interface ColumnHeaderModalProperties { openBy: ReactNode; - showSort?: boolean; - showFilter?: boolean; - showGroup?: boolean; column: ColumnType; style: CSSProperties; - onSort?: (e: CustomEvent<{column: unknown; sortDirection: string}>) => void; - onGroupBy?: (e: CustomEvent<{column: unknown; isGrouped: boolean}>) => void; + onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void; + onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void; } const staticStyle = { fontWeight: 'normal' }; -export const ColumnHeaderModal: FC = (props) => { - const { showGroup, showSort, showFilter, column, style, openBy, onSort, onGroupBy } = props; +export const ColumnHeaderModal: FC = (props: ColumnHeaderModalProperties) => { + const { column, style, openBy, onSort, onGroupBy } = props; + const showFilter = column.canFilter; + const showGroup = column.canGroupBy; + const showSort = column.canSort; const { Filter } = column; @@ -118,9 +118,3 @@ export const ColumnHeaderModal: FC = (props) => { ); }; - -ColumnHeaderModal.defaultProps = { - showSort: true, - showFilter: false, - showGroup: false -}; diff --git a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx index 8d9a9b58b72..15e0bcb381b 100644 --- a/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx +++ b/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx @@ -3,8 +3,8 @@ import '@ui5/webcomponents-icons/dist/icons/group-2'; import '@ui5/webcomponents-icons/dist/icons/sort-ascending'; import '@ui5/webcomponents-icons/dist/icons/sort-descending'; import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles'; -import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters'; import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper'; +import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters'; import { Icon } from '@ui5/webcomponents-react/lib/Icon'; import React, { CSSProperties, DragEventHandler, FC, ReactNode, ReactNodeArray, useMemo } from 'react'; import { ColumnType } from '../types/ColumnType'; @@ -18,12 +18,9 @@ export interface ColumnHeaderProps { className: string; column: ColumnType; style: CSSProperties; - groupable: boolean; - sortable: boolean; - filterable: boolean; isLastColumn?: boolean; - onSort?: (e: CustomEvent<{column: unknown; sortDirection: string}>) => void; - onGroupBy?: (e: CustomEvent<{column: unknown; isGrouped: boolean}>) => void; + onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void; + onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void; onDragStart: DragEventHandler; onDragOver: DragEventHandler; onDrop: DragEventHandler; @@ -32,6 +29,7 @@ export interface ColumnHeaderProps { dragOver: boolean; isResizing: boolean; isDraggable: boolean; + role: string; } const styles = { @@ -91,9 +89,6 @@ export const ColumnHeader: FC = (props: ColumnHeaderProps) => column, className, style, - groupable, - sortable, - filterable, isLastColumn, onSort, onGroupBy, @@ -103,7 +98,8 @@ export const ColumnHeader: FC = (props: ColumnHeaderProps) => onDrop, onDragEnd, isDraggable, - dragOver + dragOver, + role } = props; const openBy = useMemo(() => { @@ -179,18 +175,9 @@ export const ColumnHeader: FC = (props: ColumnHeaderProps) => if (!column) return null; return ( -
- {groupable || sortable || filterable ? ( - +
+ {column.canGroupBy || column.canSort || column.canFilter ? ( + ) : (
{openBy}
)} diff --git a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx index d5b6aff9f5b..0e288577b5d 100644 --- a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx +++ b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx @@ -18,7 +18,9 @@ const columns = [ Header: 'Age', accessor: 'age', hAlign: TextAlign.End, - disableGrouping: true, + disableGroupBy: true, + disableSortBy: false, + disableFilters: false, className: 'superCustomClass', isVisible: true }, diff --git a/packages/main/src/components/AnalyticalTable/hooks/useRowHighlight.tsx b/packages/main/src/components/AnalyticalTable/hooks/useRowHighlight.tsx index f3cac874d74..30a7e277b68 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useRowHighlight.tsx +++ b/packages/main/src/components/AnalyticalTable/hooks/useRowHighlight.tsx @@ -26,9 +26,9 @@ export const useRowHighlight = (hooks) => { { id: '__ui5wcr__internal_highlight_column', accessor: highlightField, - sortable: false, - groupable: false, - filterable: false, + disableFilters: true, + disableSortBy: true, + disableGroupBy: true, disableResizing: true, canReorder: false, width: 6, diff --git a/packages/main/src/components/AnalyticalTable/hooks/useRowSelectionColumn.tsx b/packages/main/src/components/AnalyticalTable/hooks/useRowSelectionColumn.tsx index 46edea78dae..1757699236b 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useRowSelectionColumn.tsx +++ b/packages/main/src/components/AnalyticalTable/hooks/useRowSelectionColumn.tsx @@ -35,9 +35,9 @@ export const useRowSelectionColumn: PluginHook<{}> = (hooks) => { { id: '__ui5wcr__internal_selection_column', accessor: '__ui5wcr__internal_selection_column', - sortable: false, - groupable: false, - filterable: false, + disableFilters: true, + disableSortBy: true, + disableGroupBy: true, disableResizing: true, canReorder: false, width: 36, @@ -77,11 +77,7 @@ export const useRowSelectionColumn: PluginHook<{}> = (hooks) => { }); hooks.columnsDeps.push((deps, { instance: { state, webComponentsReactProperties } }) => { - return [ - ...deps, - webComponentsReactProperties.selectionMode, - webComponentsReactProperties.noSelectionColumn - ]; + return [...deps, webComponentsReactProperties.selectionMode, webComponentsReactProperties.noSelectionColumn]; }); hooks.visibleColumnsDeps.push((deps, { instance }) => [ diff --git a/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts b/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts index cac20ddb600..9ae945af6ef 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts @@ -7,6 +7,7 @@ import { PluginHook } from 'react-table'; export const useTableCellStyling: PluginHook<{}> = (hooks) => { hooks.getCellProps.push((cellProps, { cell: { column }, instance }) => { const lastColumnId = instance.columns[instance.columns.length - 1]?.id; + const columnIndex = instance.columns.findIndex(({ id }) => id === column.id); const { classes } = instance.webComponentsReactProperties; const style: CSSProperties = {}; @@ -64,7 +65,9 @@ export const useTableCellStyling: PluginHook<{}> = (hooks) => { style: { ...cellProps.style, ...style - } + }, + tabIndex: 0, + 'aria-colindex': columnIndex + 1 // aria index is 1 based, not 0 }; }); }; diff --git a/packages/main/src/components/AnalyticalTable/hooks/useTableHeaderStyling.ts b/packages/main/src/components/AnalyticalTable/hooks/useTableHeaderStyling.ts index ce6aca0d8d5..f27b587819e 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useTableHeaderStyling.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useTableHeaderStyling.ts @@ -10,7 +10,8 @@ export const useTableHeaderStyling: PluginHook<{}> = (hooks) => { style: { ...columnProps.style, position: 'absolute' // TODO should be removed at some point in time - } + }, + id: column.id }; }); }; diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index ded40d22f7b..31c100e817b 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -1,10 +1,8 @@ import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles'; -import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils'; import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper'; import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps'; +import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils'; import { TableSelectionMode } from '@ui5/webcomponents-react/lib/TableSelectionMode'; -import { TextAlign } from '@ui5/webcomponents-react/lib/TextAlign'; -import { VerticalAlign } from '@ui5/webcomponents-react/lib/VerticalAlign'; import React, { ComponentType, FC, @@ -19,7 +17,6 @@ import React, { useRef } from 'react'; import { - Column, PluginHook, useAbsoluteLayout, useColumnOrder, @@ -32,6 +29,7 @@ import { useTable } from 'react-table'; import { TableScaleWidthMode } from '../../enums/TableScaleWidthMode'; +import { AnalyticalTableColumnDefinition } from '../../interfaces/AnalyticalTableColumnDefinition'; import { CommonProps } from '../../interfaces/CommonProps'; import styles from './AnayticalTable.jss'; import { ColumnHeader } from './ColumnHeader'; @@ -42,7 +40,7 @@ import { DefaultNoDataComponent } from './defaults/NoDataComponent'; import { useColumnsDependencies } from './hooks/useColumnsDependencies'; import { useDragAndDrop } from './hooks/useDragAndDrop'; import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths'; -import { useRowHighlight } from "./hooks/useRowHighlight"; +import { useRowHighlight } from './hooks/useRowHighlight'; import { useRowSelectionColumn } from './hooks/useRowSelectionColumn'; import { useTableCellStyling } from './hooks/useTableCellStyling'; import { useTableHeaderGroupStyling } from './hooks/useTableHeaderGroupStyling'; @@ -56,25 +54,8 @@ import { TitleBar } from './TitleBar'; import { orderByFn } from './util'; import { VirtualTableBody } from './virtualization/VirtualTableBody'; -export interface ColumnConfiguration extends Column { - accessor?: string; - width?: number; - hAlign?: TextAlign; - vAlign?: VerticalAlign; - canResize?: boolean; - minWidth?: number; - - [key: string]: any; -} - export interface TableProps extends CommonProps { - /** - * In addition to the standard 'react-table' column config you can pass the properties 'hAlign' and 'vAlign'. - * These will align the text inside the column accordingly. - * values for hAlign: Begin | End | Left | Right | Center | Initial (default) - * values for vAlign: Bottom | Middle | Top | Inherit (default) - */ - columns: ColumnConfiguration[]; + columns: AnalyticalTableColumnDefinition[]; data: object[]; /** @@ -148,6 +129,7 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< groupBy, selectionMode, onRowSelected, + onSort, reactTableOptions, tableHooks, busyIndicatorEnabled, @@ -167,7 +149,10 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< scaleWidthMode, noSelectionColumn, withRowHighlight, - highlightField = 'status' + highlightField = 'status', + groupable, + sortable, + filterable } = props; const classes = useStyles({ rowHeight: props.rowHeight }); @@ -206,6 +191,9 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< orderByFn, getSubRows, stateReducer, + disableFilters: !filterable, + disableSortBy: !sortable, + disableGroupBy: !groupable, webComponentsReactProperties: { selectionMode, classes, @@ -334,23 +322,27 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< {typeof renderExtension === 'function' &&
{renderExtension()}
}
{ -
+
{headerGroups.map((headerGroup) => { let headerProps = {}; if (headerGroup.getHeaderGroupProps) { headerProps = headerGroup.getHeaderGroupProps(); } return ( + // eslint-disable-next-line react/jsx-key
{headerGroup.headers.map((column, index) => ( + // eslint-disable-next-line react/jsx-key = forwardRef((props: TableProps, ref: Ref< onDragEnter={handleDragEnter} onDragEnd={handleOnDragEnd} dragOver={column.id === dragOver} - column={column} isDraggable={!isTreeTable && column.canReorder} > {column.render('Header')} diff --git a/packages/main/src/interfaces/AnalyticalTableColumnDefinition.ts b/packages/main/src/interfaces/AnalyticalTableColumnDefinition.ts new file mode 100644 index 00000000000..5c886bdbd79 --- /dev/null +++ b/packages/main/src/interfaces/AnalyticalTableColumnDefinition.ts @@ -0,0 +1,46 @@ +import { TextAlign } from '@ui5/webcomponents-react/lib/TextAlign'; +import { VerticalAlign } from '@ui5/webcomponents-react/lib/VerticalAlign'; +import { ComponentType } from 'react'; + +export interface AnalyticalTableColumnDefinition { + // base properties + accessor: string | ((row: any, rowIndex: number) => any); + /** + * Required if accessor is a function + */ + id?: string; + + Header?: string | ComponentType; + Cell?: string | ComponentType; + width?: number; + minWidth?: number; + maxWidth?: number; + + // useFilters + Filter?: string | ComponentType; + disableFilters?: boolean; + defaultCanFilter?: boolean; + filter?: string | Function; + + // useGroupBy + Aggregated?: string | ComponentType; + aggregate?: string | ((leafValues, aggregatedValues) => any); + aggregateValue?: string | ((values, row, column) => any); + disableGroupBy?: boolean; + + // useSortBy + defaultCanSort?: boolean; + disableSortBy?: boolean; + sortDescFirst?: boolean; + sortInverted?: boolean; + sortType?: string | ((rowA, rowB, columnId: string, descending: boolean) => any); + + // useResizeColumns + disableResizing?: boolean; + + // ui5 web components react properties + hAlign?: TextAlign; + vAlign?: VerticalAlign; + + [key: string]: any; +} From a892b82fe186c7ef8aae299f6ff0ef00e00e63d0 Mon Sep 17 00:00:00 2001 From: MarcusNotheis Date: Wed, 1 Apr 2020 12:25:49 +0200 Subject: [PATCH 2/4] feat(AnalyticalTable): Basic Keyboard Navigation --- .../AnalyticalTable/AnayticalTable.jss.ts | 6 +- .../AnalyticalTable.test.tsx.snap | 590 ++++++++++++++++-- .../hooks/useTableCellStyling.ts | 3 +- .../src/components/AnalyticalTable/index.tsx | 75 ++- 4 files changed, 609 insertions(+), 65 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/AnayticalTable.jss.ts b/packages/main/src/components/AnalyticalTable/AnayticalTable.jss.ts index 55147413b3a..5f9dfdc91fb 100644 --- a/packages/main/src/components/AnalyticalTable/AnayticalTable.jss.ts +++ b/packages/main/src/components/AnalyticalTable/AnayticalTable.jss.ts @@ -111,7 +111,11 @@ const styles = { position: 'relative', textOverflow: 'ellipsis', whiteSpace: 'nowrap', - alignItems: 'center' + alignItems: 'center', + '&:focus': { + outlineOffset: '-2px', + outline: `1px dotted ${ThemingParameters.sapContent_FocusColor}` + } }, noDataContainer: { display: 'flex', diff --git a/packages/main/src/components/AnalyticalTable/__snapshots__/AnalyticalTable.test.tsx.snap b/packages/main/src/components/AnalyticalTable/__snapshots__/AnalyticalTable.test.tsx.snap index 3253e7d6164..f59af5f3164 100644 --- a/packages/main/src/components/AnalyticalTable/__snapshots__/AnalyticalTable.test.tsx.snap +++ b/packages/main/src/components/AnalyticalTable/__snapshots__/AnalyticalTable.test.tsx.snap @@ -15,9 +15,12 @@ exports[`AnalyticalTable Alternate Row Color 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -510,9 +553,12 @@ exports[`AnalyticalTable Loading - Loader 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -1005,9 +1091,12 @@ exports[`AnalyticalTable Loading - Placeholder 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -2176,9 +2318,12 @@ exports[`AnalyticalTable custom row height 1`] = ` class="AnalyticalTable-tableContainer-0 AnalyticalTable-modifiedRowHeight-0 AnalyticalTable-modifiedRowHeight-d5-0" >
@@ -2671,9 +2856,12 @@ exports[`AnalyticalTable render without data 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -3459,9 +3690,12 @@ exports[`AnalyticalTable test Asc desc 2`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -3958,9 +4232,12 @@ exports[`AnalyticalTable test Asc desc 3`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -4457,9 +4774,12 @@ exports[`AnalyticalTable test drag and drop of a draggable column 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -4952,9 +5312,12 @@ exports[`AnalyticalTable with highlight row 1`] = ` class="AnalyticalTable-tableContainer-0" >
@@ -5561,9 +5984,12 @@ exports[`AnalyticalTable without selection Column 1`] = ` class="AnalyticalTable-tableContainer-0" >
diff --git a/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts b/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts index 9ae945af6ef..6c77605cbed 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useTableCellStyling.ts @@ -57,6 +57,7 @@ export const useTableCellStyling: PluginHook<{}> = (hooks) => { if (column.id === lastColumnId) { style.paddingRight = `calc(${ThemingParameters.sapScrollBar_Dimension} + 0.5rem)`; style.boxSizing = 'border-box'; + style.width = `calc(${cellProps.style.width} - ${ThemingParameters.sapScrollBar_Dimension})`; } return { @@ -66,7 +67,7 @@ export const useTableCellStyling: PluginHook<{}> = (hooks) => { ...cellProps.style, ...style }, - tabIndex: 0, + tabIndex: -1, 'aria-colindex': columnIndex + 1 // aria index is 1 based, not 0 }; }); diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index 31c100e817b..112c636c768 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -107,7 +107,6 @@ export interface TableProps extends CommonProps { overscanCount?: number; // default components - NoDataComponent?: ComponentType; LoadingComponent?: ComponentType; } @@ -316,6 +315,77 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< 'onColumnsReordered' ]); + const currentlyFocusedCell = useRef(null); + const onTableFocus = useCallback( + (e) => { + if (e.target.getAttribute('role') === 'grid') { + const firstCell: HTMLDivElement = e.target.querySelector( + 'div[role="row"]:first-child div[role="cell"]:first-child' + ); + firstCell.tabIndex = 0; + firstCell.focus(); + currentlyFocusedCell.current = firstCell; + } + }, + [currentlyFocusedCell] + ); + + const onKeyboardNavigation = useCallback( + (e) => { + if (currentlyFocusedCell.current) { + switch (e.key) { + case 'ArrowRight': { + const newElement = currentlyFocusedCell.current.nextElementSibling as HTMLDivElement; + if (newElement) { + currentlyFocusedCell.current.tabIndex = -1; + newElement.tabIndex = 0; + newElement.focus(); + currentlyFocusedCell.current = newElement; + } + break; + } + case 'ArrowLeft': { + const newElement = currentlyFocusedCell.current.previousElementSibling as HTMLDivElement; + if (newElement) { + currentlyFocusedCell.current.tabIndex = -1; + newElement.tabIndex = 0; + newElement.focus(); + currentlyFocusedCell.current = newElement; + } + break; + } + case 'ArrowDown': { + const nextRow = currentlyFocusedCell.current.parentElement.nextElementSibling as HTMLDivElement; + if (nextRow) { + currentlyFocusedCell.current.tabIndex = -1; + const currentColumnIndex = currentlyFocusedCell.current.getAttribute('aria-colindex'); + const newElement: HTMLDivElement = nextRow.querySelector(`div[aria-colindex="${currentColumnIndex}"]`); + newElement.tabIndex = 0; + newElement.focus(); + currentlyFocusedCell.current = newElement; + } + break; + } + case 'ArrowUp': { + const previousRow = currentlyFocusedCell.current.parentElement.previousElementSibling as HTMLDivElement; + if (previousRow) { + currentlyFocusedCell.current.tabIndex = -1; + const currentColumnIndex = currentlyFocusedCell.current.getAttribute('aria-colindex'); + const newElement: HTMLDivElement = previousRow.querySelector( + `div[aria-colindex="${currentColumnIndex}"]` + ); + newElement.tabIndex = 0; + newElement.focus(); + currentlyFocusedCell.current = newElement; + } + break; + } + } + } + }, + [currentlyFocusedCell] + ); + return (
{title && {title}} @@ -328,6 +398,9 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< aria-rowcount={rows.length} aria-colcount={tableInternalColumns.length} data-per-page={visibleRows} + tabIndex={0} + onFocus={onTableFocus} + onKeyDown={onKeyboardNavigation} > {headerGroups.map((headerGroup) => { let headerProps = {}; From c4d915545e36e378f645bd8bf2ebcac0d70a1a39 Mon Sep 17 00:00:00 2001 From: MarcusNotheis Date: Wed, 1 Apr 2020 21:15:42 +0200 Subject: [PATCH 3/4] feat(AnalyticalTable): Add infiniteScroll loading options --- .../AnalyticalTable/demo/demo.stories.tsx | 3 ++ .../src/components/AnalyticalTable/index.tsx | 14 ++++++-- .../virtualization/VirtualTableBody.tsx | 36 +++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx index 0e288577b5d..12fba659518 100644 --- a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx +++ b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx @@ -92,6 +92,9 @@ export const defaultTable = () => { noSelectionColumn={boolean('noSelectionColumn', false)} withRowHighlight={boolean('withRowHighlight', true)} highlightField={text('highlightField', 'status')} + infiniteScroll={boolean('infiniteScroll', true)} + infiniteScrollThreshold={number('infiniteScrollThreshold', 20)} + onLoadMore={action('onLoadMore')} />
); diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index 112c636c768..933fc1f43ff 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -88,6 +88,8 @@ export interface TableProps extends CommonProps { selectionMode?: TableSelectionMode; scaleWidthMode?: TableScaleWidthMode; columnOrder?: object[]; + infiniteScroll?: boolean; + infiniteScrollThreshold?: number; // events @@ -96,6 +98,7 @@ export interface TableProps extends CommonProps { onRowSelected?: (e?: CustomEvent<{ allRowsSelected?: boolean; row?: unknown; isSelected?: boolean }>) => any; onRowExpandChange?: (e?: CustomEvent<{ row: unknown; column: unknown }>) => any; onColumnsReordered?: (e?: CustomEvent<{ columnsNewOrder: string[]; column: unknown }>) => void; + onLoadMore?: (e?: { detail: { rowCount: number } }) => void; /** * additional options which will be passed to [react-table“s useTable hook](https://github.com/tannerlinsley/react-table/blob/master/docs/api/useTable.md#table-options) */ @@ -151,7 +154,10 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< highlightField = 'status', groupable, sortable, - filterable + filterable, + infiniteScroll, + infiniteScrollThreshold = 20, + onLoadMore } = props; const classes = useStyles({ rowHeight: props.rowHeight }); @@ -312,7 +318,8 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< 'onGroup', 'onRowSelected', 'onRowExpandChange', - 'onColumnsReordered' + 'onColumnsReordered', + 'onLoadMore' ]); const currentlyFocusedCell = useRef(null); @@ -463,6 +470,9 @@ const AnalyticalTable: FC = forwardRef((props: TableProps, ref: Ref< overscanCount={overscanCount} totalColumnsWidth={totalColumnsWidth} selectedFlatRows={selectedFlatRows} + infiniteScroll={infiniteScroll} + infiniteScrollThreshold={infiniteScrollThreshold} + onLoadMore={onLoadMore} /> )}
diff --git a/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx b/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx index 796f939df49..019be877b02 100644 --- a/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx +++ b/packages/main/src/components/AnalyticalTable/virtualization/VirtualTableBody.tsx @@ -6,7 +6,15 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { FixedSizeList } from 'react-window'; import { VirtualTableRow } from './VirtualTableRow'; -export const VirtualTableBody = (props) => { +interface VirtualTableBodyProps { + infiniteScroll: boolean; + infiniteScrollThreshold: number; + onLoadMore?: (e?: { detail: { rowCount: number } }) => void; + + [key: string]: any; +} + +export const VirtualTableBody = (props: VirtualTableBodyProps) => { const { classes, prepareRow, @@ -22,10 +30,14 @@ export const VirtualTableBody = (props) => { alternateRowColor, overscanCount, totalColumnsWidth, - selectedFlatRows + selectedFlatRows, + infiniteScroll, + infiniteScrollThreshold, + onLoadMore } = props; const innerDivRef = useRef(); + const firedInfiniteLoadEvents = useRef(new Set()); const itemCount = Math.max(minRows, rows.length); const overscan = overscanCount ? overscanCount : Math.floor(visibleRows / 2); @@ -63,6 +75,25 @@ export const VirtualTableBody = (props) => { classNames.put(classes.selectable); } + const onScroll = useCallback( + ({ scrollDirection, scrollOffset }) => { + if (scrollDirection === 'forward' && infiniteScroll) { + const currentTopRow = Math.floor(scrollOffset / internalRowHeight); + if (rows.length - currentTopRow < infiniteScrollThreshold) { + if (!firedInfiniteLoadEvents.current.has(rows.length)) { + onLoadMore({ + detail: { + rowCount: rows.length + } + }); + } + firedInfiniteLoadEvents.current.add(rows.length); + } + } + }, + [infiniteScroll, infiniteScrollThreshold, onLoadMore, rows.length, internalRowHeight, firedInfiniteLoadEvents] + ); + return ( { itemKey={getItemKey} innerRef={innerDivRef} overscanCount={overscan} + onScroll={onScroll} > {VirtualTableRow} From b19683938f7c4fbf563644d4a85470af9bbb304f Mon Sep 17 00:00:00 2001 From: MarcusNotheis Date: Thu, 2 Apr 2020 08:17:25 +0200 Subject: [PATCH 4/4] Remove unused option --- .../main/src/components/AnalyticalTable/demo/demo.stories.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx index 12fba659518..f603f3214a6 100644 --- a/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx +++ b/packages/main/src/components/AnalyticalTable/demo/demo.stories.tsx @@ -21,8 +21,7 @@ const columns = [ disableGroupBy: true, disableSortBy: false, disableFilters: false, - className: 'superCustomClass', - isVisible: true + className: 'superCustomClass' }, { Header: 'Friend Name',