A11y : Fix option panes not accessible when collapsed (#46405)

* Turned div into text button to make it accessible

* Only icon as button to avoid embedded buttons with overrides delete button

* use icon in Button directly

* Removed unused import

* moving id for labelledBy to h6 elem instead of icon

* Tweaking style, bigger caret

* Fixed aria-expanded

* Modifying and using CollapseToggle from unified alerting

* Added restOfProps to CollapseToggle
This commit is contained in:
Yaelle Chaudy 2022-03-14 15:37:13 +01:00 committed by GitHub
parent 2409405c34
commit 0b0d612372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 32 deletions

View File

@ -1,43 +1,45 @@
import React, { FC, HTMLAttributes } from 'react'; import React, { FC, HTMLAttributes } from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { IconSize, useStyles, Icon } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data';
import { IconSize, useStyles2, Button } from '@grafana/ui';
interface Props extends HTMLAttributes<HTMLButtonElement> { interface Props extends HTMLAttributes<HTMLButtonElement> {
isCollapsed: boolean; isCollapsed: boolean;
onToggle: (isCollapsed: boolean) => void; onToggle: (isCollapsed: boolean) => void;
// Todo: this should be made compulsory for a11y purposes
idControlled?: string;
size?: IconSize; size?: IconSize;
className?: string; className?: string;
text?: string; text?: string;
} }
export const CollapseToggle: FC<Props> = ({ isCollapsed, onToggle, className, text, size = 'xl', ...restOfProps }) => { export const CollapseToggle: FC<Props> = ({
const styles = useStyles(getStyles); isCollapsed,
onToggle,
idControlled,
className,
text,
size = 'xl',
...restOfProps
}) => {
const styles = useStyles2(getStyles);
return ( return (
<button <Button
aria-label={`${isCollapsed ? 'Expand' : 'Collapse'} alert group`} fill="text"
aria-expanded={!isCollapsed}
aria-controls={idControlled}
className={cx(styles.expandButton, className)} className={cx(styles.expandButton, className)}
icon={isCollapsed ? 'angle-right' : 'angle-down'}
onClick={() => onToggle(!isCollapsed)} onClick={() => onToggle(!isCollapsed)}
{...restOfProps} {...restOfProps}
> />
<Icon size={size} name={isCollapsed ? 'angle-right' : 'angle-down'} />
{text}
</button>
); );
}; };
export const getStyles = () => ({ export const getStyles = (theme: GrafanaTheme2) => ({
expandButton: css` expandButton: css`
background: none; color: ${theme.colors.text.secondary};
border: none; margin-right: ${theme.spacing(1)};
outline: none !important;
display: inline-flex;
align-items: center;
svg {
margin-bottom: 0;
}
`, `,
}); });

View File

@ -1,11 +1,12 @@
import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react'; import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Counter, Icon, useStyles2 } from '@grafana/ui'; import { Counter, useStyles2 } from '@grafana/ui';
import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers';
import { useLocalStorage } from 'react-use'; import { useLocalStorage } from 'react-use';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { CollapseToggle } from 'app/features/alerting/unified/components/CollapseToggle';
export interface OptionsPaneCategoryProps { export interface OptionsPaneCategoryProps {
id: string; id: string;
@ -101,12 +102,16 @@ export const OptionsPaneCategory: FC<OptionsPaneCategoryProps> = React.memo(
ref={ref} ref={ref}
> >
<div className={headerStyles} onClick={onToggle} aria-label={selectors.components.OptionsGroup.toggle(id)}> <div className={headerStyles} onClick={onToggle} aria-label={selectors.components.OptionsGroup.toggle(id)}>
<div className={cx(styles.toggle, 'editor-options-group-toggle')}> <CollapseToggle isCollapsed={!isExpanded} idControlled={id} onToggle={onToggle} />
<Icon name={isExpanded ? 'angle-down' : 'angle-right'} /> <h6 id={`button-${id}`} className={styles.title}>
</div> {renderTitle(isExpanded)}
<h6 className={styles.title}>{renderTitle(isExpanded)}</h6> </h6>
</div> </div>
{isExpanded && <div className={bodyStyles}>{children}</div>} {isExpanded && (
<div className={bodyStyles} id={id} aria-labelledby={`button-${id}`}>
{children}
</div>
)}
</div> </div>
); );
} }
@ -122,10 +127,6 @@ const getStyles = (theme: GrafanaTheme2) => {
boxNestedExpanded: css` boxNestedExpanded: css`
margin-bottom: ${theme.spacing(2)}; margin-bottom: ${theme.spacing(2)};
`, `,
toggle: css`
color: ${theme.colors.text.secondary};
margin-right: ${theme.spacing(1)};
`,
title: css` title: css`
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
@ -138,7 +139,7 @@ const getStyles = (theme: GrafanaTheme2) => {
display: flex; display: flex;
cursor: pointer; cursor: pointer;
align-items: baseline; align-items: baseline;
padding: ${theme.spacing(1)}; padding: ${theme.spacing(0.5)};
color: ${theme.colors.text.primary}; color: ${theme.colors.text.primary};
font-weight: ${theme.typography.fontWeightMedium}; font-weight: ${theme.typography.fontWeightMedium};