mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Add accessible landmark markup (#36753)
* Add landmark markup * Make panel titles h2 * Descibe panel landmarks * Add nav elements and heading 1 * Reset line height and remove margin * Make focus styles visible * Change nav to section * Add desc * Fix focus styles cutoff
This commit is contained in:
parent
2172920095
commit
cfd06775c0
@ -37,9 +37,9 @@ export const Components = {
|
|||||||
},
|
},
|
||||||
Panels: {
|
Panels: {
|
||||||
Panel: {
|
Panel: {
|
||||||
title: (title: string) => `Panel header title item ${title}`,
|
title: (title: string) => `Panel header ${title}`,
|
||||||
headerItems: (item: string) => `Panel header item ${item}`,
|
headerItems: (item: string) => `Panel header item ${item}`,
|
||||||
containerByTitle: (title: string) => `Panel container title ${title}`,
|
containerByTitle: (title: string) => `${title} panel`,
|
||||||
headerCornerInfo: (mode: string) => `Panel header ${mode}`,
|
headerCornerInfo: (mode: string) => `Panel header ${mode}`,
|
||||||
},
|
},
|
||||||
Visualization: {
|
Visualization: {
|
||||||
@ -157,13 +157,13 @@ export const Components = {
|
|||||||
},
|
},
|
||||||
PageToolbar: {
|
PageToolbar: {
|
||||||
container: () => '.page-toolbar',
|
container: () => '.page-toolbar',
|
||||||
item: (tooltip: string) => `Page toolbar button ${tooltip}`,
|
item: (tooltip: string) => `${tooltip}`,
|
||||||
},
|
},
|
||||||
QueryEditorToolbarItem: {
|
QueryEditorToolbarItem: {
|
||||||
button: (title: string) => `QueryEditor toolbar item button ${title}`,
|
button: (title: string) => `QueryEditor toolbar item button ${title}`,
|
||||||
},
|
},
|
||||||
BackButton: {
|
BackButton: {
|
||||||
backArrow: 'Go Back button',
|
backArrow: 'Go Back',
|
||||||
},
|
},
|
||||||
OptionsGroup: {
|
OptionsGroup: {
|
||||||
group: (title?: string) => (title ? `Options group ${title}` : 'Options group'),
|
group: (title?: string) => (title ? `Options group ${title}` : 'Options group'),
|
||||||
|
@ -63,6 +63,7 @@ export const PageToolbar: FC<Props> = React.memo(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<nav className={styles.navElement}>
|
||||||
{parent && parentHref && (
|
{parent && parentHref && (
|
||||||
<>
|
<>
|
||||||
<Link className={cx(styles.titleText, styles.parentLink, styles.titleLink)} href={parentHref}>
|
<Link className={cx(styles.titleText, styles.parentLink, styles.titleLink)} href={parentHref}>
|
||||||
@ -76,11 +77,14 @@ export const PageToolbar: FC<Props> = React.memo(
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{titleHref && (
|
{titleHref && (
|
||||||
|
<h1 className={styles.h1Styles}>
|
||||||
<Link className={cx(styles.titleText, styles.titleLink)} href={titleHref}>
|
<Link className={cx(styles.titleText, styles.titleLink)} href={titleHref}>
|
||||||
{title}
|
{title}
|
||||||
</Link>
|
</Link>
|
||||||
|
</h1>
|
||||||
)}
|
)}
|
||||||
{!titleHref && <div className={styles.titleText}>{title}</div>}
|
{!titleHref && <h1 className={styles.titleText}>{title}</h1>}
|
||||||
|
</nav>
|
||||||
{leftItems?.map((child, index) => (
|
{leftItems?.map((child, index) => (
|
||||||
<div className={styles.leftActionItem} key={index}>
|
<div className={styles.leftActionItem} key={index}>
|
||||||
{child}
|
{child}
|
||||||
@ -147,6 +151,14 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`,
|
`,
|
||||||
|
navElement: css`
|
||||||
|
display: flex;
|
||||||
|
`,
|
||||||
|
h1Styles: css`
|
||||||
|
margin: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
display: flex;
|
||||||
|
`,
|
||||||
parentIcon: css`
|
parentIcon: css`
|
||||||
margin-left: ${theme.spacing(0.5)};
|
margin-left: ${theme.spacing(0.5)};
|
||||||
`,
|
`,
|
||||||
|
@ -97,7 +97,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
|||||||
<div className="grafana-app">
|
<div className="grafana-app">
|
||||||
<Router history={locationService.getHistory()}>
|
<Router history={locationService.getHistory()}>
|
||||||
<SideMenu />
|
<SideMenu />
|
||||||
<div className="main-view">
|
<main className="main-view">
|
||||||
<div
|
<div
|
||||||
ref={this.container}
|
ref={this.container}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
@ -110,7 +110,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
|||||||
{bodyRenderHooks.map((Hook, index) => (
|
{bodyRenderHooks.map((Hook, index) => (
|
||||||
<Hook key={index.toString()} />
|
<Hook key={index.toString()} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
<LiveConnectionWarning />
|
<LiveConnectionWarning />
|
||||||
|
@ -24,7 +24,7 @@ export const SideMenu: FC = React.memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidemenu" data-testid="sidemenu">
|
<nav className="sidemenu" data-testid="sidemenu">
|
||||||
<a href={homeUrl} className="sidemenu__logo" key="logo">
|
<a href={homeUrl} className="sidemenu__logo" key="logo">
|
||||||
<Branding.MenuLogo />
|
<Branding.MenuLogo />
|
||||||
</a>
|
</a>
|
||||||
@ -37,7 +37,7 @@ export const SideMenu: FC = React.memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
<TopSection key="topsection" />
|
<TopSection key="topsection" />
|
||||||
<BottomSection key="bottomsection" />
|
<BottomSection key="bottomsection" />
|
||||||
</div>
|
</nav>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,9 +114,10 @@ class DashNav extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (canStar) {
|
if (canStar) {
|
||||||
|
let desc = isStarred ? 'Unmark as favorite' : 'Mark as favorite';
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<DashNavButton
|
<DashNavButton
|
||||||
tooltip="Mark as favorite"
|
tooltip={desc}
|
||||||
icon={isStarred ? 'favorite' : 'star'}
|
icon={isStarred ? 'favorite' : 'star'}
|
||||||
iconType={isStarred ? 'mono' : 'default'}
|
iconType={isStarred ? 'mono' : 'default'}
|
||||||
iconSize="lg"
|
iconSize="lg"
|
||||||
@ -127,11 +128,12 @@ class DashNav extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (canShare) {
|
if (canShare) {
|
||||||
|
let desc = 'Share dashboard or panel';
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<ModalsController key="button-share">
|
<ModalsController key="button-share">
|
||||||
{({ showModal, hideModal }) => (
|
{({ showModal, hideModal }) => (
|
||||||
<DashNavButton
|
<DashNavButton
|
||||||
tooltip="Share dashboard or panel"
|
tooltip={desc}
|
||||||
icon="share-alt"
|
icon="share-alt"
|
||||||
iconSize="lg"
|
iconSize="lg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -317,7 +317,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className={containerClassNames}>
|
<div className={containerClassNames}>
|
||||||
{kioskMode !== KioskMode.Full && (
|
{kioskMode !== KioskMode.Full && (
|
||||||
<div aria-label={selectors.pages.Dashboard.DashNav.nav}>
|
<header aria-label={selectors.pages.Dashboard.DashNav.nav}>
|
||||||
<DashNav
|
<DashNav
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
title={dashboard.title}
|
title={dashboard.title}
|
||||||
@ -327,7 +327,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
kioskMode={kioskMode}
|
kioskMode={kioskMode}
|
||||||
hideTimePicker={dashboard.timepicker.hidden}
|
hideTimePicker={dashboard.timepicker.hidden}
|
||||||
/>
|
/>
|
||||||
</div>
|
</header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DashboardPrompt dashboard={dashboard} />
|
<DashboardPrompt dashboard={dashboard} />
|
||||||
@ -343,9 +343,9 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
<div className={styles.dashboardContent}>
|
<div className={styles.dashboardContent}>
|
||||||
{initError && <DashboardFailed />}
|
{initError && <DashboardFailed />}
|
||||||
{!editPanel && kioskMode === KioskMode.Off && (
|
{!editPanel && kioskMode === KioskMode.Off && (
|
||||||
<div aria-label={selectors.pages.Dashboard.SubMenu.submenu}>
|
<section aria-label={selectors.pages.Dashboard.SubMenu.submenu}>
|
||||||
<SubMenu dashboard={dashboard} annotations={dashboard.annotations.list} links={dashboard.links} />
|
<SubMenu dashboard={dashboard} annotations={dashboard.annotations.list} links={dashboard.links} />
|
||||||
</div>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DashboardGrid
|
<DashboardGrid
|
||||||
|
@ -434,7 +434,10 @@ export class PanelChrome extends Component<Props, State> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClassNames} aria-label={selectors.components.Panels.Panel.containerByTitle(panel.title)}>
|
<section
|
||||||
|
className={containerClassNames}
|
||||||
|
aria-label={selectors.components.Panels.Panel.containerByTitle(panel.title)}
|
||||||
|
>
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
panel={panel}
|
panel={panel}
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
@ -456,7 +459,7 @@ export class PanelChrome extends Component<Props, State> {
|
|||||||
return this.renderPanel(width, height);
|
return this.renderPanel(width, height);
|
||||||
}}
|
}}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { cx } from '@emotion/css';
|
import { cx, css } from '@emotion/css';
|
||||||
import { DataLink, PanelData } from '@grafana/data';
|
import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
|
||||||
import { Icon } from '@grafana/ui';
|
import { Icon, useStyles2 } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import PanelHeaderCorner from './PanelHeaderCorner';
|
import PanelHeaderCorner from './PanelHeaderCorner';
|
||||||
@ -30,6 +30,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
|
|||||||
const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
|
const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
|
||||||
const title = panel.getDisplayTitle();
|
const title = panel.getDisplayTitle();
|
||||||
const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
|
const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
|
||||||
|
const styles = useStyles2(panelStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -57,7 +58,7 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="panel-title-text">{title}</span>
|
<h2 className={styles.titleText}>{title}</h2>
|
||||||
<Icon name="angle-down" className="panel-menu-toggle" />
|
<Icon name="angle-down" className="panel-menu-toggle" />
|
||||||
<PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} show={panelMenuOpen} onClose={closeMenu} />
|
<PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} show={panelMenuOpen} onClose={closeMenu} />
|
||||||
{data.request && data.request.timeInfo && (
|
{data.request && data.request.timeInfo && (
|
||||||
@ -73,3 +74,25 @@ export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, dat
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const panelStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
titleText: css`
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: calc(100% - 38px);
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: ${theme.typography.fontWeightMedium};
|
||||||
|
font-size: ${theme.typography.body.fontSize};
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${theme.colors.text.primary};
|
||||||
|
}
|
||||||
|
.panel-has-alert & {
|
||||||
|
max-width: calc(100% - 54px);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -33,9 +33,9 @@ export const PanelHeaderMenuTrigger: FC<Props> = ({ children, ...divProps }) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...divProps} className="panel-title-container" onClick={onMenuToggle} onMouseDown={onMouseDown}>
|
<header {...divProps} className="panel-title-container" onClick={onMenuToggle} onMouseDown={onMouseDown}>
|
||||||
{children({ panelMenuOpen, closeMenu: () => setPanelMenuOpen(false) })}
|
{children({ panelMenuOpen, closeMenu: () => setPanelMenuOpen(false) })}
|
||||||
</div>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,22 +37,6 @@ $panel-header-no-title-zindex: 1;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-title-text {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: calc(100% - 38px);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: $font-weight-semi-bold;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $link-hover-color;
|
|
||||||
}
|
|
||||||
.panel-has-alert & {
|
|
||||||
max-width: calc(100% - 54px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-menu-container {
|
.panel-menu-container {
|
||||||
width: 0px;
|
width: 0px;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
|
Loading…
Reference in New Issue
Block a user