TableNG: Use same hover behavior for one and multiple AutoCell links (#108194)
This commit is contained in:
@@ -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 (
|
||||
<a
|
||||
href={link.href}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { formattedValueToString } from '@grafana/data';
|
||||
|
||||
import { renderSingleLink } from '../../DataLinksActionsTooltip';
|
||||
import { useSingleLink } from '../hooks';
|
||||
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
|
||||
import { AutoCellProps } from '../types';
|
||||
|
||||
export default function AutoCell({ value, field, rowIdx }: AutoCellProps) {
|
||||
const displayValue = field.display!(value);
|
||||
const formattedValue = formattedValueToString(displayValue);
|
||||
const link = useSingleLink(field, rowIdx);
|
||||
|
||||
return link != null ? renderSingleLink(link, formattedValue) : formattedValue;
|
||||
return (
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
{formattedValue}
|
||||
</MaybeWrapWithLink>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 = (
|
||||
<BarGauge
|
||||
width={width}
|
||||
height={height - heightOffset}
|
||||
field={config}
|
||||
display={field.display}
|
||||
text={{ valueSize: 14 }}
|
||||
value={displayValue}
|
||||
orientation={VizOrientation.Horizontal}
|
||||
theme={theme}
|
||||
alignmentFactors={alignmentFactors}
|
||||
itemSpacing={1}
|
||||
lcdCellWidth={8}
|
||||
displayMode={barGaugeMode}
|
||||
valueDisplayMode={valueDisplayMode}
|
||||
/>
|
||||
return (
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
<BarGauge
|
||||
width={width}
|
||||
height={height - heightOffset}
|
||||
field={config}
|
||||
display={field.display}
|
||||
text={{ valueSize: 14 }}
|
||||
value={displayValue}
|
||||
orientation={VizOrientation.Horizontal}
|
||||
theme={theme}
|
||||
alignmentFactors={alignmentFactors}
|
||||
itemSpacing={1}
|
||||
lcdCellWidth={8}
|
||||
displayMode={barGaugeMode}
|
||||
valueDisplayMode={valueDisplayMode}
|
||||
/>
|
||||
</MaybeWrapWithLink>
|
||||
);
|
||||
|
||||
const link = useSingleLink(field, rowIdx);
|
||||
|
||||
return link == null ? barGaugeComponent : renderSingleLink(link, barGaugeComponent);
|
||||
};
|
||||
|
||||
@@ -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 = <img alt={alt} src={text} className={styles.image} title={title} />;
|
||||
const link = useSingleLink(field, rowIdx);
|
||||
|
||||
return <div className={styles.imageContainer}>{link == null ? img : renderSingleLink(link, img)}</div>;
|
||||
return (
|
||||
<div className={styles.imageContainer}>
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
<img alt={alt} src={text} className={styles.image} title={title} />
|
||||
</MaybeWrapWithLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({
|
||||
|
||||
@@ -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 <div className={styles.jsonText}>{link == null ? displayValue : renderSingleLink(link, displayValue)}</div>;
|
||||
return (
|
||||
<div className={styles.jsonText}>
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
{displayValue}
|
||||
</MaybeWrapWithLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) => ({
|
||||
|
||||
@@ -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 <a aria-haspopup="menu">{children}</a>;
|
||||
}
|
||||
|
||||
// raw value
|
||||
return children;
|
||||
});
|
||||
@@ -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
|
||||
? {
|
||||
|
||||
@@ -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<DataGridHandle>, height: number, renderedRows: TableRow[]) {
|
||||
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user