mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 02:12:29 +08:00
PanelChrome: Improve accessibility landmark markup (#85863)
* Add section and use Text component to get h2 for panel * Remove heading when collapsible * Make button text truncate * Update test * Remove labelledby as it is not needed anymore * Use testid selectors in test * Remove async
This commit is contained in:
@ -155,6 +155,7 @@ export const Components = {
|
||||
Panels: {
|
||||
Panel: {
|
||||
title: (title: string) => `data-testid Panel header ${title}`,
|
||||
content: 'data-testid panel content',
|
||||
headerItems: (item: string) => `data-testid Panel header item ${item}`,
|
||||
menuItems: (item: string) => `data-testid Panel menu item ${item}`,
|
||||
menu: (title: string) => `data-testid Panel menu ${title}`,
|
||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { useToggle } from 'react-use';
|
||||
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { PanelChrome, PanelChromeProps } from './PanelChrome';
|
||||
|
||||
@ -152,16 +153,17 @@ it('collapses the controlled panel when user clicks on the chevron or the title'
|
||||
|
||||
expect(screen.getByText("Panel's Content")).toBeInTheDocument();
|
||||
|
||||
const button = screen.getByText('Default title');
|
||||
const button = screen.getByRole('button', { name: 'Default title' });
|
||||
const content = screen.getByTestId(selectors.components.Panels.Panel.content);
|
||||
// collapse button should have same aria-controls as the panel's content
|
||||
expect(button.getAttribute('aria-controls')).toBe(button.parentElement?.parentElement?.nextElementSibling?.id);
|
||||
expect(button.getAttribute('aria-controls')).toBe(content.id);
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument();
|
||||
// aria-controls should be removed when panel is collapsed
|
||||
expect(button).not.toHaveAttribute('aria-controlls');
|
||||
expect(button.parentElement?.parentElement?.nextElementSibling?.id).toBe(undefined);
|
||||
expect(screen.queryByTestId(selectors.components.Panels.Panel.content)?.id).toBe(undefined);
|
||||
});
|
||||
|
||||
it('collapses the uncontrolled panel when user clicks on the chevron or the title', () => {
|
||||
@ -169,14 +171,15 @@ it('collapses the uncontrolled panel when user clicks on the chevron or the titl
|
||||
|
||||
expect(screen.getByText("Panel's Content")).toBeInTheDocument();
|
||||
|
||||
const button = screen.getByText('Default title');
|
||||
const button = screen.getByRole('button', { name: 'Default title' });
|
||||
const content = screen.getByTestId(selectors.components.Panels.Panel.content);
|
||||
|
||||
// collapse button should have same aria-controls as the panel's content
|
||||
expect(button.getAttribute('aria-controls')).toBe(button.parentElement?.parentElement?.nextElementSibling?.id);
|
||||
expect(button.getAttribute('aria-controls')).toBe(content.id);
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument();
|
||||
// aria-controls should be removed when panel is collapsed
|
||||
expect(button).not.toHaveAttribute('aria-controlls');
|
||||
expect(button.parentElement?.parentElement?.nextElementSibling?.id).toBe(undefined);
|
||||
expect(screen.queryByTestId(selectors.components.Panels.Panel.content)?.id).toBe(undefined);
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import { getFocusStyles } from '../../themes/mixins';
|
||||
import { DelayRender } from '../../utils/DelayRender';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { LoadingBar } from '../LoadingBar/LoadingBar';
|
||||
import { Text } from '../Text/Text';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
|
||||
import { HoverWidget } from './HoverWidget';
|
||||
@ -167,34 +168,40 @@ export function PanelChrome({
|
||||
<>
|
||||
{/* Non collapsible title */}
|
||||
{!collapsible && title && (
|
||||
<h6 title={typeof title === 'string' ? title : undefined} className={styles.title}>
|
||||
{title}
|
||||
</h6>
|
||||
<div className={styles.title}>
|
||||
<Text element="h2" variant="h6" truncate title={typeof title === 'string' ? title : undefined}>
|
||||
{title}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Collapsible title */}
|
||||
{collapsible && (
|
||||
<h6 className={styles.title}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.clearButtonStyles}
|
||||
onClick={() => {
|
||||
toggleOpen();
|
||||
if (onToggleCollapse) {
|
||||
onToggleCollapse(!collapsed);
|
||||
}
|
||||
}}
|
||||
aria-expanded={!collapsed}
|
||||
aria-controls={!collapsed ? panelContentId : undefined}
|
||||
>
|
||||
<Icon
|
||||
name={!collapsed ? 'angle-down' : 'angle-right'}
|
||||
aria-hidden={!!title}
|
||||
aria-label={!title ? 'toggle collapse panel' : undefined}
|
||||
/>
|
||||
{title}
|
||||
</button>
|
||||
</h6>
|
||||
<div className={styles.title}>
|
||||
<Text element="h2" variant="h6">
|
||||
<button
|
||||
type="button"
|
||||
className={styles.clearButtonStyles}
|
||||
onClick={() => {
|
||||
toggleOpen();
|
||||
if (onToggleCollapse) {
|
||||
onToggleCollapse(!collapsed);
|
||||
}
|
||||
}}
|
||||
aria-expanded={!collapsed}
|
||||
aria-controls={!collapsed ? panelContentId : undefined}
|
||||
>
|
||||
<Icon
|
||||
name={!collapsed ? 'angle-down' : 'angle-right'}
|
||||
aria-hidden={!!title}
|
||||
aria-label={!title ? 'toggle collapse panel' : undefined}
|
||||
/>
|
||||
<Text variant="h6" truncate>
|
||||
{title}
|
||||
</Text>
|
||||
</button>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={cx(styles.titleItems, dragClassCancel)} data-testid="title-items-container">
|
||||
@ -229,7 +236,7 @@ export function PanelChrome({
|
||||
|
||||
return (
|
||||
// tabIndex={0} is needed for keyboard accessibility in the plot area
|
||||
<div
|
||||
<section
|
||||
className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })}
|
||||
style={containerStyles}
|
||||
data-testid={testid}
|
||||
@ -287,13 +294,14 @@ export function PanelChrome({
|
||||
{!collapsed && (
|
||||
<div
|
||||
id={panelContentId}
|
||||
data-testid={selectors.components.Panels.Panel.content}
|
||||
className={cx(styles.content, height === undefined && styles.containNone)}
|
||||
style={contentStyle}
|
||||
>
|
||||
{typeof children === 'function' ? children(innerWidth, innerHeight) : children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -424,13 +432,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
title: css({
|
||||
label: 'panel-title',
|
||||
display: 'flex',
|
||||
marginBottom: 0, // override default h6 margin-bottom
|
||||
padding: theme.spacing(0, padding),
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: theme.typography.h6.fontSize,
|
||||
fontWeight: theme.typography.h6.fontWeight,
|
||||
minWidth: 0,
|
||||
'& > h2': {
|
||||
minWidth: 0,
|
||||
},
|
||||
}),
|
||||
items: css({
|
||||
display: 'flex',
|
||||
@ -478,14 +484,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
display: 'flex',
|
||||
gap: theme.spacing(0.5),
|
||||
background: 'transparent',
|
||||
color: theme.colors.text.primary,
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: theme.typography.h6.fontSize,
|
||||
fontWeight: theme.typography.h6.fontWeight,
|
||||
maxWidth: '100%',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user