[Dashboard] Add tabindex to panel header tooltip, make it sibling to h2 (#208391)

## Summary
This PR makes tooltip on dashboard panel header keyboard focusable and
moves the tooltip to be a sibling of the `<h2>`.
Closes: #117233
This commit is contained in:
Krzysztof Kowalczyk 2025-01-31 18:01:17 +01:00 committed by GitHub
parent 0227642b4d
commit 9112044369
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 29 deletions

View file

@ -7,27 +7,13 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EuiScreenReaderOnly } from '@elastic/eui';
import { ViewMode } from '@kbn/presentation-publishing';
import { i18n } from '@kbn/i18n';
import classNames from 'classnames';
import React, { useCallback } from 'react';
import { placeholderTitle } from './presentation_panel_title';
import { DefaultPresentationPanelApi, PresentationPanelInternalProps } from '../types';
import { PresentationPanelTitle } from './presentation_panel_title';
import { usePresentationPanelHeaderActions } from './use_presentation_panel_header_actions';
const getAriaLabelForTitle = (title?: string) => {
return title
? i18n.translate('presentationPanel.enhancedAriaLabel', {
defaultMessage: 'Panel: {title}',
values: { title: title || placeholderTitle },
})
: i18n.translate('presentationPanel.ariaLabel', {
defaultMessage: 'Panel',
});
};
export type PresentationPanelHeaderProps<ApiType extends DefaultPresentationPanelApi> = {
api: ApiType;
headerId: string;
@ -72,13 +58,6 @@ export const PresentationPanelHeader = <
if (!showPanelBar) return null;
const ariaLabel = getAriaLabelForTitle(showPanelBar ? panelTitle : undefined);
const ariaLabelElement = (
<EuiScreenReaderOnly>
<span id={headerId}>{ariaLabel}</span>
</EuiScreenReaderOnly>
);
const headerClasses = classNames('embPanel__header', {
'embPanel--dragHandle': viewMode === 'edit',
'embPanel__header--floater': !showPanelBar,
@ -93,17 +72,21 @@ export const PresentationPanelHeader = <
className={headerClasses}
data-test-subj={`embeddablePanelHeading-${(panelTitle || '').replace(/\s/g, '')}`}
>
<h2 ref={memoizedSetDragHandle} data-test-subj="dashboardPanelTitle" className={titleClasses}>
{ariaLabelElement}
<div
ref={memoizedSetDragHandle}
data-test-subj="dashboardPanelTitle"
className={titleClasses}
>
<PresentationPanelTitle
api={api}
headerId={headerId}
viewMode={viewMode}
hideTitle={hideTitle}
panelTitle={panelTitle}
panelDescription={panelDescription}
/>
{showBadges && badgeElements}
</h2>
</div>
{showNotifications && notificationElements}
</figcaption>
);

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui';
import { EuiIcon, EuiLink, EuiScreenReaderOnly, EuiToolTip } from '@elastic/eui';
import classNames from 'classnames';
import { once } from 'lodash';
import React, { useMemo, useEffect, useRef, useState } from 'react';
@ -36,6 +36,17 @@ export const placeholderTitle = i18n.translate('presentationPanel.placeholderTit
defaultMessage: '[No Title]',
});
const getAriaLabelForTitle = (title?: string) => {
return title
? i18n.translate('presentationPanel.enhancedAriaLabel', {
defaultMessage: 'Panel: {title}',
values: { title: title || placeholderTitle },
})
: i18n.translate('presentationPanel.ariaLabel', {
defaultMessage: 'Panel',
});
};
const createDocumentMouseMoveListener = once(() => fromEvent<MouseEvent>(document, 'mousemove'));
const createDocumentMouseUpListener = once(() => fromEvent<MouseEvent>(document, 'mouseup'));
@ -83,18 +94,21 @@ export const usePresentationPanelTitleClickHandler = (titleElmRef: HTMLElement |
export const PresentationPanelTitle = ({
api,
headerId,
viewMode,
hideTitle,
panelTitle,
panelDescription,
}: {
api: unknown;
headerId: string;
hideTitle?: boolean;
panelTitle?: string;
panelDescription?: string;
viewMode?: ViewMode;
}) => {
const [panelTitleElmRef, setPanelTitleElmRef] = useState<HTMLElement | null>(null);
const panelTitleElement = useMemo(() => {
if (hideTitle) return null;
const titleClassNames = classNames('embPanel__titleText', {
@ -146,6 +160,14 @@ export const PresentationPanelTitle = ({
</span>
);
}
const ariaLabel = getAriaLabelForTitle(panelTitle);
const ariaLabelElement = (
<EuiScreenReaderOnly>
<span id={headerId}>{ariaLabel}</span>
</EuiScreenReaderOnly>
);
return (
<EuiToolTip
title={!hideTitle ? panelTitle || undefined : undefined}
@ -155,17 +177,23 @@ export const PresentationPanelTitle = ({
anchorClassName="embPanel__titleTooltipAnchor"
anchorProps={{ 'data-test-subj': 'embeddablePanelTooltipAnchor' }}
>
<span data-test-subj="embeddablePanelTitleInner" className="embPanel__titleInner">
{!hideTitle ? <>{panelTitleElement}&nbsp;</> : null}
<div data-test-subj="embeddablePanelTitleInner" className="embPanel__titleInner">
{!hideTitle ? (
<h2>
{ariaLabelElement}
{panelTitleElement}&nbsp;
</h2>
) : null}
<EuiIcon
type="iInCircle"
color="subdued"
data-test-subj="embeddablePanelTitleDescriptionIcon"
tabIndex={0}
/>
</span>
</div>
</EuiToolTip>
);
}, [hideTitle, panelDescription, panelTitle, panelTitleElement]);
}, [hideTitle, panelDescription, panelTitle, panelTitleElement, headerId]);
return describedPanelTitleElement;
};