add ariaLabel

This commit is contained in:
Ashley Harrison
2025-10-29 15:40:03 +00:00
parent 02543bd5ce
commit a903a0da5d
11 changed files with 55 additions and 24 deletions
@@ -19,6 +19,7 @@ export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {
return (
<div className={styles.wrapper}>
<Collapse
ariaLabel={title}
className={styles.collapse}
isOpen={isOpen}
onToggle={toggleOpen}
@@ -70,7 +70,7 @@ export const WithCustomLabel: StoryFn<typeof Collapse> = (args) => {
}}
label={
<Stack flex={1} alignItems="center" justifyContent="space-between">
Collapse panel
My title
<Stack alignItems="center">
<IconButton
onClick={(event) => {
@@ -83,6 +83,7 @@ export const WithCustomLabel: StoryFn<typeof Collapse> = (args) => {
</Stack>
</Stack>
}
ariaLabel="My title"
>
<p>{args.children}</p>
</Collapse>
@@ -1,6 +1,5 @@
import { css, cx } from '@emotion/css';
import { useId, useState } from 'react';
import * as React from 'react';
import { type PropsWithChildren, useId, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
@@ -92,11 +91,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
}),
});
export interface Props {
interface BaseProps {
/** Expand or collapse te content */
isOpen?: boolean;
/** Element or text for the Collapse header */
label: React.ReactNode;
/** Indicates loading state of the content */
loading?: boolean;
/** Callback for the toggle functionality */
@@ -107,7 +104,20 @@ export interface Props {
collapsible?: boolean;
}
export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.PropsWithChildren<Props>) => {
interface PropsWithStringLabel extends BaseProps {
label: string;
ariaLabel?: never;
}
interface PropsWithCustomLabel extends BaseProps {
label: JSX.Element;
/** aria-label for the Collapse header. Required if passing a custom element as the label */
ariaLabel: string;
}
export type Props = PropsWithStringLabel | PropsWithCustomLabel;
export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: PropsWithChildren<Props>) => {
const [open, setOpen] = useState(isOpen);
return (
<Collapse
@@ -123,9 +133,19 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr
);
};
export const Collapse = ({ isOpen, label, loading, onToggle, className, children }: React.PropsWithChildren<Props>) => {
export const Collapse = ({
ariaLabel,
isOpen,
label,
loading,
onToggle,
className,
children,
}: PropsWithChildren<Props>) => {
const style = useStyles2(getStyles);
const labelId = useId();
const contentId = useId();
const buttonLabel =
ariaLabel ?? (typeof label === 'string' ? label : t('grafana-ui.collapse.aria-label-default', 'Toggle collapse'));
const onClickToggle = () => {
if (onToggle) {
@@ -141,21 +161,16 @@ export const Collapse = ({ isOpen, label, loading, onToggle, className, children
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div className={style.header} onClick={onClickToggle}>
<IconButton
aria-describedby={labelId}
aria-expanded={isOpen}
aria-controls={contentId}
className={style.button}
aria-label={
isOpen
? t('grafana-ui.collapse.aria-label-collapse', 'Collapse panel')
: t('grafana-ui.collapse.aria-label-expand', 'Expand panel')
}
aria-label={buttonLabel}
name={isOpen ? 'angle-down' : 'angle-right'}
/>
<div id={labelId} className={style.headerLabel}>
{label}
</div>
<div className={style.headerLabel}>{label}</div>
</div>
{isOpen && (
<div className={style.collapseBody}>
<div className={style.collapseBody} id={contentId}>
<div className={loaderClass} />
<div className={style.bodyContentWrapper}>{children}</div>
</div>
@@ -171,6 +171,7 @@ export const CorrelationHelper = ({ exploreId, correlations }: Props) => {
})}
</pre>
<Collapse
ariaLabel={t('explore.correlation-helper.label-description-header', 'Label / Description')}
isOpen={isLabelDescOpen}
onToggle={() => {
setIsLabelDescOpen(!isLabelDescOpen);
@@ -200,6 +201,7 @@ export const CorrelationHelper = ({ exploreId, correlations }: Props) => {
</Field>
</Collapse>
<Collapse
ariaLabel={t('explore.correlation-helper.transformations', 'Transformations')}
isOpen={isTransformOpen}
onToggle={() => {
setIsTransformOpen(!isTransformOpen);
@@ -308,7 +308,7 @@ class LogsContainer extends PureComponent<LogsContainerProps, LogsContainerState
return (
<>
<LogsCrossFadeTransition visible={!isLive}>
<LogsCrossFadeTransition visible={isLive}>
<PanelChrome title={t('explore.logs-container.label-logs', 'Logs')}>
<LiveTailControls exploreId={exploreId}>
{(controls) => (
@@ -326,7 +326,7 @@ class LogsContainer extends PureComponent<LogsContainerProps, LogsContainerState
</LiveTailControls>
</PanelChrome>
</LogsCrossFadeTransition>
<LogsCrossFadeTransition visible={isLive}>
<LogsCrossFadeTransition visible={!isLive}>
<Logs
exploreId={exploreId}
datasourceType={this.props.datasourceInstance?.type}
@@ -145,6 +145,7 @@ export function LogsSamplePanel(props: Props) {
return queryResponse?.state !== LoadingState.NotStarted ? (
<Collapse
ariaLabel={t('explore.logs-sample-panel.label', 'Logs sample')}
label={
<div>
<Trans i18nKey="explore.logs-sample-panel.label">Logs sample</Trans>
@@ -151,7 +151,12 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
return (
<div className={styles.container}>
<Collapse label={collapseLabel} isOpen={showSpanFilters} onToggle={setShowSpanFilters}>
<Collapse
ariaLabel={t('explore.span-filters.label-collapse', 'Span Filters')}
label={collapseLabel}
isOpen={showSpanFilters}
onToggle={setShowSpanFilters}
>
<InlineFieldRow className={styles.flexContainer}>
<InlineField label={t('explore.span-filters.label-service-name', 'Service name')} labelWidth={16}>
<Stack gap={0.5}>
@@ -2,9 +2,9 @@ import { css } from '@emotion/css';
import { isString } from 'lodash';
import { FeatureLike } from 'ol/Feature';
import { useState } from 'react';
import * as React from 'react';
import { DataFrame, FieldType, getFieldDisplayName, GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Collapse, TabContent, useStyles2 } from '@grafana/ui';
import { GeomapLayerHover } from 'app/plugins/panel/geomap/event';
import { renderValue } from 'app/plugins/panel/geomap/utils/uiUtils';
@@ -37,6 +37,9 @@ export const DataHoverRows = ({ layers, activeTabIndex }: Props) => {
return shouldDisplayCollapse ? (
<Collapse
ariaLabel={t('visualization.data-hover-rows.aria-label-collapse', 'Match: {{number}}', {
number: idx + 1,
})}
key={key}
label={generateLabel(feature, idx)}
isOpen={rowMap.get(key)}
@@ -59,7 +62,7 @@ export const DataHoverRows = ({ layers, activeTabIndex }: Props) => {
);
};
export const generateLabel = (feature: FeatureLike, idx: number): string | React.ReactNode => {
export const generateLabel = (feature: FeatureLike, idx: number) => {
const names = ['Name', 'name', 'Title', 'ID', 'id'];
let props = feature.getProperties();
let first = '';
@@ -18,6 +18,7 @@ export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {
return (
<div className={styles.wrapper}>
<Collapse
ariaLabel={title}
className={styles.collapse}
isOpen={isOpen}
onToggle={toggleOpen}
@@ -296,6 +296,7 @@ export function LokiContextUi(props: LokiContextUiProps) {
</div>
<Collapse
ariaLabel={queryExpr}
isOpen={isOpen}
onToggle={() => {
window.localStorage.setItem(IS_LOKI_LOG_CONTEXT_UI_OPEN, (!isOpen).toString());
@@ -24,6 +24,7 @@ export function QueryOptionGroup({ title, children, collapsedInfo, queryStats, o
return (
<div className={styles.wrapper}>
<Collapse
ariaLabel={title}
className={styles.collapse}
isOpen={propsIsOpen ?? isOpen}
onToggle={onToggle ?? toggleOpen}