mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Accessibility: Enable jsx-a11y/mouse-events-have-key-events
(#58050)
* implement VizLegend keyboard accessibility * add onBlur/onFocus
This commit is contained in:
parent
e6b088fbf5
commit
f76ba90078
@ -75,7 +75,6 @@
|
|||||||
// we should remove the corresponding line and fix them one by one
|
// we should remove the corresponding line and fix them one by one
|
||||||
// any marked "error" contain specific overrides we'll need to keep
|
// any marked "error" contain specific overrides we'll need to keep
|
||||||
"jsx-a11y/click-events-have-key-events": "off",
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
"jsx-a11y/mouse-events-have-key-events": "off",
|
|
||||||
"jsx-a11y/no-autofocus": [
|
"jsx-a11y/no-autofocus": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
@ -28,8 +28,11 @@ export function VizLegend<T>({
|
|||||||
}: LegendProps<T>) {
|
}: LegendProps<T>) {
|
||||||
const { eventBus, onToggleSeriesVisibility, onToggleLegendSort } = usePanelContext();
|
const { eventBus, onToggleSeriesVisibility, onToggleLegendSort } = usePanelContext();
|
||||||
|
|
||||||
const onMouseEnter = useCallback(
|
const onMouseOver = useCallback(
|
||||||
(item: VizLegendItem, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
(
|
||||||
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => {
|
||||||
eventBus?.publish({
|
eventBus?.publish({
|
||||||
type: DataHoverEvent.type,
|
type: DataHoverEvent.type,
|
||||||
payload: {
|
payload: {
|
||||||
@ -44,7 +47,10 @@ export function VizLegend<T>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onMouseOut = useCallback(
|
const onMouseOut = useCallback(
|
||||||
(item: VizLegendItem, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
(
|
||||||
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => {
|
||||||
eventBus?.publish({
|
eventBus?.publish({
|
||||||
type: DataHoverClearEvent.type,
|
type: DataHoverClearEvent.type,
|
||||||
payload: {
|
payload: {
|
||||||
@ -59,7 +65,7 @@ export function VizLegend<T>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onLegendLabelClick = useCallback(
|
const onLegendLabelClick = useCallback(
|
||||||
(item: VizLegendItem, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
(item: VizLegendItem, event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
if (onLabelClick) {
|
if (onLabelClick) {
|
||||||
onLabelClick(item, event);
|
onLabelClick(item, event);
|
||||||
}
|
}
|
||||||
@ -86,7 +92,7 @@ export function VizLegend<T>({
|
|||||||
sortDesc={sortDesc}
|
sortDesc={sortDesc}
|
||||||
onLabelClick={onLegendLabelClick}
|
onLabelClick={onLegendLabelClick}
|
||||||
onToggleSort={onToggleSort || onToggleLegendSort}
|
onToggleSort={onToggleSort || onToggleLegendSort}
|
||||||
onLabelMouseEnter={onMouseEnter}
|
onLabelMouseOver={onMouseOver}
|
||||||
onLabelMouseOut={onMouseOut}
|
onLabelMouseOut={onMouseOut}
|
||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
@ -98,7 +104,7 @@ export function VizLegend<T>({
|
|||||||
className={className}
|
className={className}
|
||||||
items={items}
|
items={items}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
onLabelMouseEnter={onMouseEnter}
|
onLabelMouseOver={onMouseOver}
|
||||||
onLabelMouseOut={onMouseOut}
|
onLabelMouseOut={onMouseOut}
|
||||||
onLabelClick={onLegendLabelClick}
|
onLabelClick={onLegendLabelClick}
|
||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
|
@ -18,7 +18,7 @@ export interface Props<T> extends VizLegendBaseProps<T> {}
|
|||||||
export const VizLegendList = <T extends unknown>({
|
export const VizLegendList = <T extends unknown>({
|
||||||
items,
|
items,
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
onLabelMouseEnter,
|
onLabelMouseOver,
|
||||||
onLabelMouseOut,
|
onLabelMouseOut,
|
||||||
onLabelClick,
|
onLabelClick,
|
||||||
placement,
|
placement,
|
||||||
@ -33,7 +33,7 @@ export const VizLegendList = <T extends unknown>({
|
|||||||
<VizLegendListItem
|
<VizLegendListItem
|
||||||
item={item}
|
item={item}
|
||||||
onLabelClick={onLabelClick}
|
onLabelClick={onLabelClick}
|
||||||
onLabelMouseEnter={onLabelMouseEnter}
|
onLabelMouseOver={onLabelMouseOver}
|
||||||
onLabelMouseOut={onLabelMouseOut}
|
onLabelMouseOut={onLabelMouseOut}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
|
@ -13,9 +13,15 @@ import { VizLegendItem } from './types';
|
|||||||
export interface Props<T> {
|
export interface Props<T> {
|
||||||
item: VizLegendItem<T>;
|
item: VizLegendItem<T>;
|
||||||
className?: string;
|
className?: string;
|
||||||
onLabelClick?: (item: VizLegendItem<T>, event: React.MouseEvent<HTMLDivElement>) => void;
|
onLabelClick?: (item: VizLegendItem<T>, event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onLabelMouseEnter?: (item: VizLegendItem, event: React.MouseEvent<HTMLDivElement>) => void;
|
onLabelMouseOver?: (
|
||||||
onLabelMouseOut?: (item: VizLegendItem, event: React.MouseEvent<HTMLDivElement>) => void;
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
|
onLabelMouseOut?: (
|
||||||
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,24 +31,24 @@ export interface Props<T> {
|
|||||||
export const VizLegendListItem = <T = unknown,>({
|
export const VizLegendListItem = <T = unknown,>({
|
||||||
item,
|
item,
|
||||||
onLabelClick,
|
onLabelClick,
|
||||||
onLabelMouseEnter,
|
onLabelMouseOver,
|
||||||
onLabelMouseOut,
|
onLabelMouseOut,
|
||||||
className,
|
className,
|
||||||
readonly,
|
readonly,
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const onMouseEnter = useCallback(
|
const onMouseOver = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>) => {
|
||||||
if (onLabelMouseEnter) {
|
if (onLabelMouseOver) {
|
||||||
onLabelMouseEnter(item, event);
|
onLabelMouseOver(item, event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[item, onLabelMouseEnter]
|
[item, onLabelMouseOver]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseOut = useCallback(
|
const onMouseOut = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>) => {
|
||||||
if (onLabelMouseOut) {
|
if (onLabelMouseOut) {
|
||||||
onLabelMouseOut(item, event);
|
onLabelMouseOut(item, event);
|
||||||
}
|
}
|
||||||
@ -51,7 +57,7 @@ export const VizLegendListItem = <T = unknown,>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
if (onLabelClick) {
|
if (onLabelClick) {
|
||||||
onLabelClick(item, event);
|
onLabelClick(item, event);
|
||||||
}
|
}
|
||||||
@ -65,14 +71,18 @@ export const VizLegendListItem = <T = unknown,>({
|
|||||||
aria-label={selectors.components.VizLegend.seriesName(item.label)}
|
aria-label={selectors.components.VizLegend.seriesName(item.label)}
|
||||||
>
|
>
|
||||||
<VizLegendSeriesIcon seriesName={item.label} color={item.color} gradient={item.gradient} readonly={readonly} />
|
<VizLegendSeriesIcon seriesName={item.label} color={item.color} gradient={item.gradient} readonly={readonly} />
|
||||||
<div
|
<button
|
||||||
onMouseEnter={onMouseEnter}
|
disabled={readonly}
|
||||||
|
type="button"
|
||||||
|
onBlur={onMouseOut}
|
||||||
|
onFocus={onMouseOver}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
onMouseOut={onMouseOut}
|
onMouseOut={onMouseOut}
|
||||||
onClick={!readonly ? onClick : undefined}
|
onClick={onClick}
|
||||||
className={cx(styles.label, !readonly && styles.clickable)}
|
className={styles.label}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
{item.getDisplayValues && <VizLegendStatsList stats={item.getDisplayValues()} />}
|
{item.getDisplayValues && <VizLegendStatsList stats={item.getDisplayValues()} />}
|
||||||
</div>
|
</div>
|
||||||
@ -85,10 +95,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
label: css`
|
label: css`
|
||||||
label: LegendLabel;
|
label: LegendLabel;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`,
|
background: none;
|
||||||
clickable: css`
|
border: none;
|
||||||
label: LegendClickabel;
|
font-size: inherit;
|
||||||
cursor: pointer;
|
padding: 0;
|
||||||
`,
|
`,
|
||||||
itemDisabled: css`
|
itemDisabled: css`
|
||||||
label: LegendLabelDisabled;
|
label: LegendLabelDisabled;
|
||||||
|
@ -21,7 +21,7 @@ export const VizLegendTable = <T extends unknown>({
|
|||||||
className,
|
className,
|
||||||
onToggleSort,
|
onToggleSort,
|
||||||
onLabelClick,
|
onLabelClick,
|
||||||
onLabelMouseEnter,
|
onLabelMouseOver,
|
||||||
onLabelMouseOut,
|
onLabelMouseOut,
|
||||||
readonly,
|
readonly,
|
||||||
}: VizLegendTableProps<T>): JSX.Element => {
|
}: VizLegendTableProps<T>): JSX.Element => {
|
||||||
@ -57,7 +57,7 @@ export const VizLegendTable = <T extends unknown>({
|
|||||||
key={`${item.label}-${index}`}
|
key={`${item.label}-${index}`}
|
||||||
item={item}
|
item={item}
|
||||||
onLabelClick={onLabelClick}
|
onLabelClick={onLabelClick}
|
||||||
onLabelMouseEnter={onLabelMouseEnter}
|
onLabelMouseOver={onLabelMouseOver}
|
||||||
onLabelMouseOut={onLabelMouseOut}
|
onLabelMouseOut={onLabelMouseOut}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
|
@ -13,9 +13,15 @@ export interface Props {
|
|||||||
key?: React.Key;
|
key?: React.Key;
|
||||||
item: VizLegendItem;
|
item: VizLegendItem;
|
||||||
className?: string;
|
className?: string;
|
||||||
onLabelClick?: (item: VizLegendItem, event: React.MouseEvent<HTMLDivElement>) => void;
|
onLabelClick?: (item: VizLegendItem, event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onLabelMouseEnter?: (item: VizLegendItem, event: React.MouseEvent<HTMLDivElement>) => void;
|
onLabelMouseOver?: (
|
||||||
onLabelMouseOut?: (item: VizLegendItem, event: React.MouseEvent<HTMLDivElement>) => void;
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
|
onLabelMouseOut?: (
|
||||||
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,24 +31,24 @@ export interface Props {
|
|||||||
export const LegendTableItem: React.FunctionComponent<Props> = ({
|
export const LegendTableItem: React.FunctionComponent<Props> = ({
|
||||||
item,
|
item,
|
||||||
onLabelClick,
|
onLabelClick,
|
||||||
onLabelMouseEnter,
|
onLabelMouseOver,
|
||||||
onLabelMouseOut,
|
onLabelMouseOut,
|
||||||
className,
|
className,
|
||||||
readonly,
|
readonly,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const onMouseEnter = useCallback(
|
const onMouseOver = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>) => {
|
||||||
if (onLabelMouseEnter) {
|
if (onLabelMouseOver) {
|
||||||
onLabelMouseEnter(item, event);
|
onLabelMouseOver(item, event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[item, onLabelMouseEnter]
|
[item, onLabelMouseOver]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseOut = useCallback(
|
const onMouseOut = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.FocusEvent<HTMLButtonElement>) => {
|
||||||
if (onLabelMouseOut) {
|
if (onLabelMouseOut) {
|
||||||
onLabelMouseOut(item, event);
|
onLabelMouseOut(item, event);
|
||||||
}
|
}
|
||||||
@ -51,7 +57,7 @@ export const LegendTableItem: React.FunctionComponent<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
if (onLabelClick) {
|
if (onLabelClick) {
|
||||||
onLabelClick(item, event);
|
onLabelClick(item, event);
|
||||||
}
|
}
|
||||||
@ -64,14 +70,18 @@ export const LegendTableItem: React.FunctionComponent<Props> = ({
|
|||||||
<td>
|
<td>
|
||||||
<span className={styles.itemWrapper}>
|
<span className={styles.itemWrapper}>
|
||||||
<VizLegendSeriesIcon color={item.color} seriesName={item.label} readonly={readonly} />
|
<VizLegendSeriesIcon color={item.color} seriesName={item.label} readonly={readonly} />
|
||||||
<div
|
<button
|
||||||
onMouseEnter={onMouseEnter}
|
disabled={readonly}
|
||||||
|
type="button"
|
||||||
|
onBlur={onMouseOut}
|
||||||
|
onFocus={onMouseOver}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
onMouseOut={onMouseOut}
|
onMouseOut={onMouseOut}
|
||||||
onClick={!readonly ? onClick : undefined}
|
onClick={!readonly ? onClick : undefined}
|
||||||
className={cx(styles.label, item.disabled && styles.labelDisabled, !readonly && styles.clickable)}
|
className={cx(styles.label, item.disabled && styles.labelDisabled)}
|
||||||
>
|
>
|
||||||
{item.label} {item.yAxis === 2 && <span className={styles.yAxisLabel}>(right y-axis)</span>}
|
{item.label} {item.yAxis === 2 && <span className={styles.yAxisLabel}>(right y-axis)</span>}
|
||||||
</div>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
{item.getDisplayValues &&
|
{item.getDisplayValues &&
|
||||||
@ -108,15 +118,15 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
label: css`
|
label: css`
|
||||||
label: LegendLabel;
|
label: LegendLabel;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0;
|
||||||
`,
|
`,
|
||||||
labelDisabled: css`
|
labelDisabled: css`
|
||||||
label: LegendLabelDisabled;
|
label: LegendLabelDisabled;
|
||||||
color: ${theme.colors.text.disabled};
|
color: ${theme.colors.text.disabled};
|
||||||
`,
|
`,
|
||||||
clickable: css`
|
|
||||||
label: LegendClickable;
|
|
||||||
cursor: pointer;
|
|
||||||
`,
|
|
||||||
itemWrapper: css`
|
itemWrapper: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -13,10 +13,16 @@ export interface VizLegendBaseProps<T> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
items: Array<VizLegendItem<T>>;
|
items: Array<VizLegendItem<T>>;
|
||||||
seriesVisibilityChangeBehavior?: SeriesVisibilityChangeBehavior;
|
seriesVisibilityChangeBehavior?: SeriesVisibilityChangeBehavior;
|
||||||
onLabelClick?: (item: VizLegendItem<T>, event: React.MouseEvent<HTMLElement>) => void;
|
onLabelClick?: (item: VizLegendItem<T>, event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
itemRenderer?: (item: VizLegendItem<T>, index: number) => JSX.Element;
|
itemRenderer?: (item: VizLegendItem<T>, index: number) => JSX.Element;
|
||||||
onLabelMouseEnter?: (item: VizLegendItem, event: React.MouseEvent<HTMLElement>) => void;
|
onLabelMouseOver?: (
|
||||||
onLabelMouseOut?: (item: VizLegendItem, event: React.MouseEvent<HTMLElement>) => void;
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
|
onLabelMouseOut?: (
|
||||||
|
item: VizLegendItem,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement> | React.FocusEvent<HTMLButtonElement>
|
||||||
|
) => void;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,8 +147,10 @@ function SpanBar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(styles.wrapper, className)}
|
className={cx(styles.wrapper, className)}
|
||||||
|
onBlur={setShortLabel}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseLeave={setShortLabel}
|
onFocus={setLongLabel}
|
||||||
|
onMouseOut={setShortLabel}
|
||||||
onMouseOver={setLongLabel}
|
onMouseOver={setLongLabel}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
data-testid={selectors.components.TraceViewer.spanBar}
|
data-testid={selectors.components.TraceViewer.spanBar}
|
||||||
|
@ -37,8 +37,10 @@ export function GraphiteFunctionEditor({ func }: FunctionEditorProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(styles.container, { [styles.error]: func.def.unknown })}
|
className={cx(styles.container, { [styles.error]: func.def.unknown })}
|
||||||
|
onBlur={() => setIsMouseOver(false)}
|
||||||
|
onFocus={() => setIsMouseOver(true)}
|
||||||
onMouseOver={() => setIsMouseOver(true)}
|
onMouseOver={() => setIsMouseOver(true)}
|
||||||
onMouseLeave={() => setIsMouseOver(false)}
|
onMouseOut={() => setIsMouseOver(false)}
|
||||||
>
|
>
|
||||||
<HorizontalGroup spacing="none">
|
<HorizontalGroup spacing="none">
|
||||||
<FunctionEditor
|
<FunctionEditor
|
||||||
|
Loading…
Reference in New Issue
Block a user