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:
Oscar Kilhed
2023-01-24 12:42:40 +01:00
committed by GitHub
parent 81c35560a8
commit 6c9d9a2db5
5 changed files with 55 additions and 15 deletions

View File

@ -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;
}
`;
};

View File

@ -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>
)} )}

View File

@ -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">

View File

@ -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>
)} )}

View File

@ -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>
)} )}