diff --git a/eslint-suppressions.json b/eslint-suppressions.json index e3e822abdc4..32b47888768 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -843,9 +843,6 @@ } }, "packages/grafana-ui/src/components/Table/TableRT/Table.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - }, "@typescript-eslint/no-explicit-any": { "count": 2 } diff --git a/packages/grafana-ui/src/components/Table/TableRT/RowsList.tsx b/packages/grafana-ui/src/components/Table/TableRT/RowsList.tsx index 251cfddaefd..38f10b74133 100644 --- a/packages/grafana-ui/src/components/Table/TableRT/RowsList.tsx +++ b/packages/grafana-ui/src/components/Table/TableRT/RowsList.tsx @@ -1,8 +1,8 @@ import { css, cx } from '@emotion/css'; -import { CSSProperties, UIEventHandler, useCallback, useEffect, useMemo, useState } from 'react'; +import { CSSProperties, useCallback, useEffect, useMemo, useState } from 'react'; import * as React from 'react'; import { Cell, Row, TableState, HeaderGroup } from 'react-table'; -import { VariableSizeList } from 'react-window'; +import { List, useListRef } from 'react-window'; import { Subscription, debounceTime } from 'rxjs'; import { @@ -18,7 +18,6 @@ import { import { TableCellDisplayMode, TableCellHeight } from '@grafana/schema'; import { useTheme2 } from '../../../themes/ThemeContext'; -import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar'; import { usePanelContext } from '../../PanelChrome'; import { TableCell } from '../Cells/TableCell'; import { @@ -42,14 +41,10 @@ interface RowsListProps { data: DataFrame; rows: Row[]; enableSharedCrosshair: boolean; - headerHeight: number; - rowHeight: number; itemCount: number; - pageIndex: number; listHeight: number; width: number; cellHeight?: TableCellHeight; - listRef: React.RefObject; tableState: TableState; tableStyles: TableStyles; nestedDataField?: Field; @@ -70,11 +65,8 @@ export const RowsList = (props: RowsListProps) => { const { data, rows, - headerHeight, footerPaginationEnabled, - rowHeight, itemCount, - pageIndex, tableState, prepareRow, onCellFilterAdded, @@ -84,7 +76,6 @@ export const RowsList = (props: RowsListProps) => { tableStyles, nestedDataField, listHeight, - listRef, enableSharedCrosshair = false, initialRowIndex = undefined, headerGroups, @@ -95,11 +86,25 @@ export const RowsList = (props: RowsListProps) => { setInspectCell, } = props; + const listRef = useListRef(null); const [rowHighlightIndex, setRowHighlightIndex] = useState(initialRowIndex); if (initialRowIndex === undefined && rowHighlightIndex !== undefined) { setRowHighlightIndex(undefined); } + useEffect(() => { + if (rowHighlightIndex !== undefined) { + // TODO can we do this without a setTimeout? + setTimeout(() => { + listRef.current?.scrollToRow({ + index: rowHighlightIndex, + align: 'center', + behavior: 'instant', + }); + }); + } + }, [rowHighlightIndex, listRef]); + const theme = useTheme2(); const panelContext = usePanelContext(); @@ -234,15 +239,6 @@ export const RowsList = (props: RowsListProps) => { }; }, [data, enableSharedCrosshair, footerPaginationEnabled, onDataHoverEvent, panelContext]); - let scrollTop: number | undefined = undefined; - if (rowHighlightIndex !== undefined) { - const firstMatchedRowIndex = rows.findIndex((row) => row.index === rowHighlightIndex); - - if (firstMatchedRowIndex !== -1) { - scrollTop = headerHeight + (firstMatchedRowIndex - 1) * rowHeight; - } - } - const rowIndexForPagination = useCallback( (index: number) => { return tableState.pageIndex * tableState.pageSize + index; @@ -316,6 +312,17 @@ export const RowsList = (props: RowsListProps) => { ); style.height = bbox.height; } + + // some disgusting code to mutate the style object to convert transform to top + // this is all so that hover behaviour is maintained + // using transform creates new stacking contexts which means hover states don't overlay correctly + const yPos = style.transform?.match(/translateY\((.*)\)/)?.[1]; + + style = { + ...style, + top: yPos, + transform: undefined, + }; const { key, ...rowProps } = row.getRowProps({ style, ...additionalProps }); return ( @@ -414,36 +421,17 @@ export const RowsList = (props: RowsListProps) => { return tableStyles.rowHeight; }; - const handleScroll: UIEventHandler = (event) => { - const { scrollTop } = event.currentTarget; - - if (listRef.current !== null) { - listRef.current.scrollTo(scrollTop); - } - }; - - // It's a hack for text wrapping. - // VariableSizeList component didn't know that we manually set row height. - // So we need to reset the list when the rows high changes. - useEffect(() => { - if (listRef.current) { - listRef.current.resetAfterIndex(0); - } - }, [rows, listRef]); - return ( - - - {({ index, style }) => RenderRow({ index, style, rowHighlightIndex })} - - + RenderRow({ index, style, rowHighlightIndex })} + /> ); }; diff --git a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx index ea909d4642f..ce808a2624b 100644 --- a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx +++ b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx @@ -8,7 +8,6 @@ import { useSortBy, useTable, } from 'react-table'; -import { VariableSizeList } from 'react-window'; import { FieldType, ReducerID, getRowUniqueId, getFieldMatcher } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -16,12 +15,10 @@ import { Trans } from '@grafana/i18n'; import { TableCellHeight } from '@grafana/schema'; import { useTheme2 } from '../../../themes/ThemeContext'; -import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar'; import { Pagination } from '../../Pagination/Pagination'; import { TableCellInspector } from '../TableCellInspector'; -import { useFixScrollbarContainer, useResetVariableListSizeCache } from '../hooks'; import { getInitialState, useTableStateReducer } from '../reducer'; -import { FooterItem, GrafanaTableState, InspectCell, TableRTProps as Props } from '../types'; +import { FooterItem, InspectCell, TableRTProps as Props } from '../types'; import { getColumns, sortCaseInsensitive, @@ -70,7 +67,6 @@ export const Table = memo((props: Props) => { replaceVariables, } = props; - const listRef = useRef(null); const tableDivRef = useRef(null); const variableSizeListScrollbarRef = useRef(null); const theme = useTheme2(); @@ -200,7 +196,6 @@ export const Table = memo((props: Props) => { toggleAllRowsExpanded, } = useTable(options, useFilters, useSortBy, useAbsoluteLayout, useResizeColumns, useExpanded, usePagination); - const extendedState = state as GrafanaTableState; toggleAllRowsExpandedRef.current = toggleAllRowsExpanded; /* @@ -270,9 +265,6 @@ export const Table = memo((props: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); - useResetVariableListSizeCache(extendedState, listRef, data, hasUniqueId); - useFixScrollbarContainer(variableSizeListScrollbarRef, tableDivRef); - const onNavigate = useCallback( (toPage: number) => { gotoPage(toPage - 1); @@ -340,60 +332,51 @@ export const Table = memo((props: Props) => { ref={tableDivRef} style={{ width, height }} > - -
- {!noHeader && ( - - )} - {itemCount > 0 ? ( -
- -
- ) : ( -
- {noValuesDisplayText} -
- )} - {footerItems && ( - + {!noHeader && ( + + )} + {itemCount > 0 ? ( +
+ - )} -
- +
+ ) : ( +
+ {noValuesDisplayText} +
+ )} + {footerItems && ( + + )} + {paginationEl} diff --git a/packages/grafana-ui/src/components/Table/TableRT/styles.ts b/packages/grafana-ui/src/components/Table/TableRT/styles.ts index 3157e2934f2..e7af4b0e100 100644 --- a/packages/grafana-ui/src/components/Table/TableRT/styles.ts +++ b/packages/grafana-ui/src/components/Table/TableRT/styles.ts @@ -111,6 +111,7 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell width: '100%', overflow: 'auto', display: 'flex', + position: 'relative', flexDirection: 'column', }), thead: css({ diff --git a/packages/grafana-ui/src/components/Table/hooks.ts b/packages/grafana-ui/src/components/Table/hooks.ts deleted file mode 100644 index bb9b6b13d0c..00000000000 --- a/packages/grafana-ui/src/components/Table/hooks.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { useEffect } from 'react'; -import * as React from 'react'; -import { VariableSizeList } from 'react-window'; - -import { DataFrame } from '@grafana/data'; - -import { GrafanaTableState } from './types'; - -/** - To have the custom vertical scrollbar always visible (https://github.com/grafana/grafana/issues/52136), - we need to bring the element from the VariableSizeList scope to the outer Table container scope, - because the VariableSizeList scope has overflow. By moving scrollbar to container scope we will have - it always visible since the entire width is in view. - Select the scrollbar element from the VariableSizeList scope - */ -export function useFixScrollbarContainer( - variableSizeListScrollbarRef: React.RefObject, - tableDivRef: React.RefObject -) { - useEffect(() => { - if (variableSizeListScrollbarRef.current && tableDivRef.current) { - const listVerticalScrollbarHTML = variableSizeListScrollbarRef.current.querySelector('.track-vertical'); - - // Select Table custom scrollbars - const tableScrollbarView = tableDivRef.current.firstChild; - - //If they exist, move the scrollbar element to the Table container scope - if (tableScrollbarView && listVerticalScrollbarHTML) { - listVerticalScrollbarHTML.remove(); - if (tableScrollbarView instanceof HTMLElement) { - tableScrollbarView.querySelector(':scope > .track-vertical')?.remove(); - tableScrollbarView.append(listVerticalScrollbarHTML); - } - } - } - }); -} - -/** - react-table caches the height of cells, so we need to reset them when expanding/collapsing rows. - We use `lastExpandedOrCollapsedIndex` since collapsed rows disappear from `expandedIndexes` but still keep their expanded - height. - */ -export function useResetVariableListSizeCache( - extendedState: GrafanaTableState, - listRef: React.RefObject, - data: DataFrame, - hasUniqueId: boolean -) { - // Make sure we trigger the reset when keys change in any way - const expandedRowsRepr = JSON.stringify(Object.keys(extendedState.expanded)); - - useEffect(() => { - // By default, reset all rows - let resetIndex = 0; - - // If we have unique field, extendedState.expanded keys are not row indexes but IDs so instead of trying to search - // for correct index we just reset the whole table. - if (!hasUniqueId) { - // If we don't have we reset from the last changed index. - if (Number.isFinite(extendedState.lastExpandedOrCollapsedIndex)) { - resetIndex = extendedState.lastExpandedOrCollapsedIndex!; - } - - // Account for paging. - resetIndex = - extendedState.pageIndex === 0 - ? resetIndex - 1 - : resetIndex - extendedState.pageIndex - extendedState.pageIndex * extendedState.pageSize; - } - - listRef.current?.resetAfterIndex(Math.max(resetIndex, 0)); - return; - }, [ - extendedState.lastExpandedOrCollapsedIndex, - extendedState.pageSize, - extendedState.pageIndex, - listRef, - data, - expandedRowsRepr, - hasUniqueId, - ]); -}