Dashboards: Improve a11y for dashboard title/description inputs (#106294)

This commit is contained in:
kay delaney
2025-06-04 10:58:11 +01:00
committed by GitHub
parent 1b9d062583
commit edf5f9fc7c
2 changed files with 25 additions and 12 deletions
@@ -32,17 +32,21 @@ export class DashboardEditableElement implements EditableDashboardElement {
const { body } = dashboard.useState();
const dashboardOptions = useMemo(() => {
const dashboardTitleInputId = 'dashboard-title-input';
const dashboardDescriptionInputId = 'dashboard-description-input';
const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'dashboard-options' })
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.title-option', 'Title'),
render: () => <DashboardTitleInput dashboard={dashboard} />,
id: dashboardTitleInputId,
render: () => <DashboardTitleInput id={dashboardTitleInputId} dashboard={dashboard} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.description', 'Description'),
render: () => <DashboardDescriptionInput dashboard={dashboard} />,
id: dashboardDescriptionInputId,
render: () => <DashboardDescriptionInput id={dashboardDescriptionInputId} dashboard={dashboard} />,
})
);
@@ -76,14 +80,20 @@ export class DashboardEditableElement implements EditableDashboardElement {
}
}
export function DashboardTitleInput({ dashboard }: { dashboard: DashboardScene }) {
export function DashboardTitleInput({ dashboard, id }: { dashboard: DashboardScene; id?: string }) {
const { title } = dashboard.useState();
return <Input value={title} onChange={(e) => dashboard.setState({ title: e.currentTarget.value })} />;
return <Input id={id} value={title} onChange={(e) => dashboard.setState({ title: e.currentTarget.value })} />;
}
export function DashboardDescriptionInput({ dashboard }: { dashboard: DashboardScene }) {
export function DashboardDescriptionInput({ dashboard, id }: { dashboard: DashboardScene; id?: string }) {
const { description } = dashboard.useState();
return <TextArea value={description} onChange={(e) => dashboard.setState({ description: e.currentTarget.value })} />;
return (
<TextArea
id={id}
value={description}
onChange={(e) => dashboard.setState({ description: e.currentTarget.value })}
/>
);
}
@@ -23,6 +23,7 @@ export interface OptionsPaneItemInfo {
useShowIf?: () => boolean;
overrides?: OptionPaneItemOverrideInfo[];
addon?: ReactNode;
id?: string;
}
/**
@@ -56,7 +57,7 @@ interface OptionsPaneItemProps {
}
function OptionsPaneItem({ itemDescriptor, searchQuery }: OptionsPaneItemProps) {
const { title, description, render, skipField } = itemDescriptor.props;
const { title, description, id, render, skipField } = itemDescriptor.props;
const key = `${itemDescriptor.parent.props.id} ${title}`;
const showIf = itemDescriptor.useShowIf();
@@ -74,6 +75,7 @@ function OptionsPaneItem({ itemDescriptor, searchQuery }: OptionsPaneItemProps)
description={description}
key={key}
aria-label={selectors.components.PanelEditor.OptionsPane.fieldLabel(key)}
htmlFor={id}
>
{render()}
</Field>
@@ -81,7 +83,7 @@ function OptionsPaneItem({ itemDescriptor, searchQuery }: OptionsPaneItemProps)
}
function renderOptionLabel(itemDescriptor: OptionsPaneItemDescriptor, searchQuery?: string): ReactNode {
const { title, description, overrides, addon } = itemDescriptor.props;
const { title, description, overrides, id, addon } = itemDescriptor.props;
if (!title) {
return null;
@@ -93,7 +95,7 @@ function renderOptionLabel(itemDescriptor: OptionsPaneItemDescriptor, searchQuer
return null;
}
return <OptionPaneLabel title={title} description={description} overrides={overrides} addon={addon} />;
return <OptionPaneLabel title={title} description={description} overrides={overrides} addon={addon} htmlFor={id} />;
}
const categories: React.ReactNode[] = [];
@@ -107,7 +109,7 @@ function renderOptionLabel(itemDescriptor: OptionsPaneItemDescriptor, searchQuer
}
return (
<Label description={description && highlightWord(description, searchQuery)} category={categories}>
<Label description={description && highlightWord(description, searchQuery)} category={categories} htmlFor={id}>
{highlightWord(title, searchQuery)}
{overrides && overrides.length > 0 && <OptionsPaneItemOverrides overrides={overrides} />}
</Label>
@@ -123,13 +125,14 @@ interface OptionPanelLabelProps {
description?: string;
overrides?: OptionPaneItemOverrideInfo[];
addon: ReactNode;
htmlFor?: string;
}
function OptionPaneLabel({ title, description, overrides, addon }: OptionPanelLabelProps) {
function OptionPaneLabel({ title, description, overrides, addon, htmlFor }: OptionPanelLabelProps) {
const styles = useStyles2(getLabelStyles);
return (
<div className={styles.container}>
<Label description={description}>
<Label description={description} htmlFor={htmlFor}>
{title}
{overrides && overrides.length > 0 && <OptionsPaneItemOverrides overrides={overrides} />}
</Label>