mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 15:52:23 +08:00
Table panel: Use link elements instead of div elements with on click events to aid with keyboard accessibility (#59393)
* TablePanel: fix image of image cell overflowing table cell when a data link is added
This commit is contained in:
@ -311,3 +311,18 @@ export const clearButtonStyles = (theme: GrafanaTheme2) => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const clearLinkButtonStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return css`
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
color: inherit;
|
||||||
|
height: 100%;
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
@ -5,7 +5,9 @@ import tinycolor from 'tinycolor2';
|
|||||||
import { DisplayValue, formattedValueToString } from '@grafana/data';
|
import { DisplayValue, formattedValueToString } from '@grafana/data';
|
||||||
import { TableCellBackgroundDisplayMode, TableCellOptions } from '@grafana/schema';
|
import { TableCellBackgroundDisplayMode, TableCellOptions } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
import { getCellLinks, getTextColorForAlphaBackground } from '../../utils';
|
import { getCellLinks, getTextColorForAlphaBackground } from '../../utils';
|
||||||
|
import { Button, clearLinkButtonStyles } from '../Button';
|
||||||
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
||||||
|
|
||||||
import { CellActions } from './CellActions';
|
import { CellActions } from './CellActions';
|
||||||
@ -31,6 +33,7 @@ export const DefaultCell: FC<TableCellProps> = (props) => {
|
|||||||
const cellOptions = getCellOptions(field);
|
const cellOptions = getCellOptions(field);
|
||||||
const cellStyle = getCellStyle(tableStyles, cellOptions, displayValue, inspectEnabled);
|
const cellStyle = getCellStyle(tableStyles, cellOptions, displayValue, inspectEnabled);
|
||||||
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
||||||
|
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...cellProps} className={cellStyle}>
|
<div {...cellProps} className={cellStyle}>
|
||||||
@ -39,11 +42,16 @@ export const DefaultCell: FC<TableCellProps> = (props) => {
|
|||||||
{hasLinks && (
|
{hasLinks && (
|
||||||
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
|
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
|
||||||
{(api) => {
|
{(api) => {
|
||||||
return (
|
const content = <div className={getLinkStyle(tableStyles, cellOptions, api.targetClassName)}>{value}</div>;
|
||||||
<div onClick={api.openMenu} className={getLinkStyle(tableStyles, cellOptions, api.targetClassName)}>
|
if (api.openMenu) {
|
||||||
{value}
|
return (
|
||||||
</div>
|
<Button className={cx(clearButtonStyle)} onClick={api.openMenu}>
|
||||||
);
|
{content}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
</DataLinksContextMenu>
|
</DataLinksContextMenu>
|
||||||
)}
|
)}
|
||||||
|
@ -50,6 +50,8 @@ export const FilterPopup: FC<Props> = ({ column: { preFilteredRows, filterValue,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickOutsideWrapper onClick={onCancel} useCapture={true}>
|
<ClickOutsideWrapper onClick={onCancel} useCapture={true}>
|
||||||
|
{/* This is just blocking click events from bubbeling and should not have a keyboard interaction. */}
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||||
<div className={cx(styles.filterContainer)} onClick={stopPropagation}>
|
<div className={cx(styles.filterContainer)} onClick={stopPropagation}>
|
||||||
<VerticalGroup spacing="lg">
|
<VerticalGroup spacing="lg">
|
||||||
<VerticalGroup spacing="xs">
|
<VerticalGroup spacing="xs">
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { cx } from '@emotion/css';
|
import { cx } from '@emotion/css';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
import { getCellLinks } from '../../utils';
|
import { getCellLinks } from '../../utils';
|
||||||
|
import { Button, clearLinkButtonStyles } from '../Button';
|
||||||
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
||||||
|
|
||||||
import { TableCellProps } from './types';
|
import { TableCellProps } from './types';
|
||||||
@ -12,6 +14,7 @@ export const ImageCell: FC<TableCellProps> = (props) => {
|
|||||||
const displayValue = field.display!(cell.value);
|
const displayValue = field.display!(cell.value);
|
||||||
|
|
||||||
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
||||||
|
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...cellProps} className={tableStyles.cellContainer}>
|
<div {...cellProps} className={tableStyles.cellContainer}>
|
||||||
@ -19,11 +22,16 @@ export const ImageCell: FC<TableCellProps> = (props) => {
|
|||||||
{hasLinks && (
|
{hasLinks && (
|
||||||
<DataLinksContextMenu style={{ height: '100%' }} links={() => getCellLinks(field, row) || []}>
|
<DataLinksContextMenu style={{ height: '100%' }} links={() => getCellLinks(field, row) || []}>
|
||||||
{(api) => {
|
{(api) => {
|
||||||
return (
|
const img = <img src={displayValue.text} className={tableStyles.imageCell} alt="" />;
|
||||||
<div onClick={api.openMenu} className={cx(tableStyles.imageCellLink, api.targetClassName)}>
|
if (api.openMenu) {
|
||||||
<img src={displayValue.text} className={tableStyles.imageCell} alt="" />
|
return (
|
||||||
</div>
|
<Button className={cx(clearButtonStyle)} onClick={api.openMenu}>
|
||||||
);
|
{img}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return img;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
</DataLinksContextMenu>
|
</DataLinksContextMenu>
|
||||||
)}
|
)}
|
||||||
|
@ -2,7 +2,9 @@ import { css, cx } from '@emotion/css';
|
|||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
import { getCellLinks } from '../../utils';
|
import { getCellLinks } from '../../utils';
|
||||||
|
import { Button, clearLinkButtonStyles } from '../Button';
|
||||||
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
||||||
|
|
||||||
import { CellActions } from './CellActions';
|
import { CellActions } from './CellActions';
|
||||||
@ -28,6 +30,7 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
const hasLinks = Boolean(getCellLinks(field, row)?.length);
|
||||||
|
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...cellProps} className={inspectEnabled ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer}>
|
<div {...cellProps} className={inspectEnabled ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer}>
|
||||||
@ -36,11 +39,15 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
|
|||||||
{hasLinks && (
|
{hasLinks && (
|
||||||
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
|
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
|
||||||
{(api) => {
|
{(api) => {
|
||||||
return (
|
if (api.openMenu) {
|
||||||
<div onClick={api.openMenu} className={api.targetClassName}>
|
return (
|
||||||
{displayValue}
|
<Button className={cx(clearButtonStyle)} onClick={api.openMenu}>
|
||||||
</div>
|
{displayValue}
|
||||||
);
|
</Button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <>{displayValue}</>;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
</DataLinksContextMenu>
|
</DataLinksContextMenu>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user