Dashboards: Improve a11y for dashboard title/description inputs (#106294)
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user