diff --git a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx index 5b2e28c646f..6686f76d49b 100644 --- a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx @@ -1,7 +1,6 @@ import { css } from '@emotion/css'; import { flip, shift, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; -import { ReactElement, useMemo } from 'react'; -import * as React from 'react'; +import { useMemo, ReactNode } from 'react'; import { ActionModel, GrafanaTheme2, LinkModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -16,7 +15,7 @@ import { DataLinksActionsTooltipCoords } from './utils'; interface Props { links: LinkModel[]; actions?: ActionModel[]; - value?: string | ReactElement; + value?: ReactNode; coords: DataLinksActionsTooltipCoords; onTooltipClose?: () => void; } @@ -102,11 +101,7 @@ export const DataLinksActionsTooltip = ({ links, actions, value, coords, onToolt ); }; -export const renderSingleLink = ( - link: LinkModel, - children: string | React.JSX.Element, - className?: string -): React.JSX.Element => { +export const renderSingleLink = (link: LinkModel, children: ReactNode, className?: string): ReactNode => { return ( + {formattedValue} + + ); } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx index 69ebfac671c..3f762218553 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/BarGaugeCell.tsx @@ -2,8 +2,7 @@ import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMin import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@grafana/schema'; import { BarGauge } from '../../../BarGauge/BarGauge'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { BarGaugeCellProps } from '../types'; import { extractPixelValue, getCellOptions, getAlignmentFactor } from '../utils'; @@ -47,25 +46,23 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar const alignmentFactors = getAlignmentFactor(field, displayValue, rowIdx!); - const barGaugeComponent = ( - + return ( + + + ); - - const link = useSingleLink(field, rowIdx); - - return link == null ? barGaugeComponent : renderSingleLink(link, barGaugeComponent); }; diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx index e0c1050396a..9953ef0948e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx @@ -4,9 +4,8 @@ import { Property } from 'csstype'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../../../themes/ThemeContext'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; import { TableCellDisplayMode } from '../../types'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { ImageCellProps } from '../types'; const DATALINKS_HEIGHT_OFFSET = 10; @@ -19,10 +18,13 @@ export const ImageCell = ({ cellOptions, field, height, justifyContent, value, r const { alt, title } = cellOptions.type === TableCellDisplayMode.Image ? cellOptions : { alt: undefined, title: undefined }; - const img = {alt}; - const link = useSingleLink(field, rowIdx); - - return
{link == null ? img : renderSingleLink(link, img)}
; + return ( +
+ + {alt} + +
+ ); }; const getStyles = (theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({ diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx index dc195cbcd67..ed66bdef90c 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx @@ -4,8 +4,7 @@ import { Property } from 'csstype'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../../../themes/ThemeContext'; -import { renderSingleLink } from '../../DataLinksActionsTooltip'; -import { useSingleLink } from '../hooks'; +import { MaybeWrapWithLink } from '../MaybeWrapWithLink'; import { JSONCellProps } from '../types'; export const JSONCell = ({ value, justifyContent, field, rowIdx }: JSONCellProps) => { @@ -31,9 +30,13 @@ export const JSONCell = ({ value, justifyContent, field, rowIdx }: JSONCellProps } } - const link = useSingleLink(field, rowIdx); - - return
{link == null ? displayValue : renderSingleLink(link, displayValue)}
; + return ( +
+ + {displayValue} + +
+ ); }; const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) => ({ diff --git a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx new file mode 100644 index 00000000000..443053e8134 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx @@ -0,0 +1,32 @@ +import { memo, ReactNode } from 'react'; + +import { Field } from '@grafana/data'; + +import { renderSingleLink } from '../DataLinksActionsTooltip'; + +import { getCellLinks } from './utils'; + +interface MaybeWrapWithLinkProps { + field: Field; + rowIdx: number; + children: ReactNode; +} + +export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWithLinkProps): ReactNode => { + const linksCount = field.config.links?.length ?? 0; + const actionsCount = field.config.actions?.length ?? 0; + + // as real, single link + if (linksCount === 1 && actionsCount === 0) { + let link = (getCellLinks(field, rowIdx) ?? [])[0]; + return renderSingleLink(link, children); + } + // as faux link that acts as hit-area for tooltip activation + else if (linksCount + actionsCount > 0) { + // eslint-disable-next-line jsx-a11y/anchor-is-valid + return
{children}; + } + + // raw value + return children; +}); diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 012424e1e28..edcb619702e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -621,11 +621,17 @@ export function TableNG(props: TableNGProps) { rows={paginatedRows} headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: noHeader })} headerRowHeight={headerHeight} - onCellClick={({ column, row }, { clientX, clientY, preventGridDefault }) => { + onCellClick={({ column, row }, { clientX, clientY, preventGridDefault, target }) => { // Note: could be column.field; JS says yes, but TS says no! const field = columns[column.idx].field; - if (colsWithTooltip[getDisplayName(field)]) { + if ( + colsWithTooltip[getDisplayName(field)] && + target instanceof HTMLElement && + // this walks up the tree to find either a faux link wrapper or the cell root + // it then only proceeds if we matched the faux link wrapper + target.closest('a[aria-haspopup], .rdg-cell')?.matches('a') + ) { const rowIdx = row.__index; setTooltipState({ coords: { @@ -895,7 +901,6 @@ const getCellStyles = ( minHeight: '100%', backgroundClip: 'padding-box !important', // helps when cells have a bg color ...(shouldWrap && { whiteSpace: 'pre-line' }), - ...(hasTooltip && { cursor: 'pointer' }), '&:last-child': { borderInlineEnd: 'none', @@ -914,7 +919,7 @@ const getCellStyles = ( }), }, - [hasTooltip ? '&' : 'a']: { + a: { cursor: 'pointer', ...(isColorized ? { diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts index 252eb362e14..7e0229903fb 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts @@ -2,7 +2,7 @@ import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect, Ref import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid'; import { varPreLine } from 'uwrap'; -import { Field, fieldReducers, FieldType, formattedValueToString, LinkModel, reduceField } from '@grafana/data'; +import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data'; import { useTheme2 } from '../../../themes/ThemeContext'; import { TableCellDisplayMode, TableColumnResizeActionCallback } from '../types'; @@ -17,7 +17,6 @@ import { getColumnTypes, GetMaxWrapCellOptions, getMaxWrapCell, - getCellLinks, } from './utils'; // Helper function to get displayed value @@ -602,13 +601,6 @@ export function useColumnResize( return dataGridResizeHandler; } -export function useSingleLink(field: Field, rowIdx: number): LinkModel | undefined { - const linksCount = field.config.links?.length ?? 0; - const actionsCount = field.config.actions?.length ?? 0; - const shouldShowLink = linksCount === 1 && actionsCount === 0; - return useMemo(() => (shouldShowLink ? (getCellLinks(field, rowIdx) ?? []) : [])[0], [field, shouldShowLink, rowIdx]); -} - export function useScrollbarWidth(ref: RefObject, height: number, renderedRows: TableRow[]) { const [scrollbarWidth, setScrollbarWidth] = useState(0);