Skip to content

Commit 93dbf39

Browse files
committed
[ENHANCEMENT] dashboards make panel desc link accessable by tab key
Signed-off-by: Seyed Mahmoud Shahrokni <39291137+shahrokni@users.noreply.github.com> Signed-off-by: Seyed Mahmoud Shahrokni <39291137+shahrokni@users.noreply.github.com>
1 parent 4df86d8 commit 93dbf39

1 file changed

Lines changed: 77 additions & 39 deletions

File tree

‎dashboards/src/components/Panel/PanelDescriptionTooltip.tsx‎

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
import type { TooltipProps } from '@mui/material';
15-
import { Box, styled, Tooltip, tooltipClasses } from '@mui/material';
14+
import { Box, Popover, styled } from '@mui/material';
1615
import type { DOMPurify as DOMPurifyInstance } from 'dompurify';
1716
import DOMPurify from 'dompurify';
1817
import { Marked } from 'marked';
19-
import type { ReactElement, ReactNode } from 'react';
20-
import { useMemo } from 'react';
18+
import type { MouseEventHandler, ReactElement } from 'react';
19+
import { cloneElement, useId, useMemo, useState } from 'react';
2120

2221
// Dedicated Marked instance, so that global `marked.use()` / `marked.setOptions()` calls elsewhere in the host
2322
// application do not change how descriptions are rendered.
@@ -92,32 +91,89 @@ export function panelDescriptionToHTML(description: string): string {
9291
});
9392
}
9493

94+
type TriggerProps = {
95+
onClick?: MouseEventHandler<HTMLElement>;
96+
'aria-expanded'?: boolean;
97+
'aria-controls'?: string;
98+
'aria-haspopup'?: 'dialog';
99+
};
100+
95101
export interface PanelDescriptionTooltipProps {
96102
id?: string;
97-
/**
98-
* Panel description, written in Markdown.
99-
*/
103+
/** Panel description, written in Markdown. */
100104
description: string;
101-
children: ReactNode;
105+
children: ReactElement<TriggerProps>;
106+
/** Kept for compatibility; no delay in click-to-open mode. */
102107
enterDelay?: number;
103108
}
104109

105110
/**
106-
* Tooltip displaying a panel description rendered as Markdown.
107-
* The tooltip is interactive, so that links can be clicked and long descriptions can be scrolled.
111+
* Displays a Markdown panel description in an accessible popover.
112+
* Links are keyboard-accessible.
108113
*/
109-
export function PanelDescriptionTooltip({
110-
id,
111-
description,
112-
children,
113-
enterDelay,
114-
}: PanelDescriptionTooltipProps): ReactElement {
115-
const title = useMemo(() => <PanelDescriptionContent description={description} />, [description]);
114+
export function PanelDescriptionTooltip({ id, description, children }: PanelDescriptionTooltipProps): ReactElement {
115+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
116+
117+
const generatedId = useId();
118+
const popoverId = id ?? generatedId;
119+
const open = Boolean(anchorEl);
120+
121+
const handleClose = (): void => {
122+
setAnchorEl(null);
123+
};
124+
125+
const trigger = cloneElement(children, {
126+
onClick: (event) => {
127+
children.props.onClick?.(event);
128+
129+
if (event.defaultPrevented) {
130+
return;
131+
}
132+
133+
event.stopPropagation();
134+
135+
setAnchorEl(event.currentTarget);
136+
},
137+
'aria-expanded': open,
138+
'aria-controls': open ? popoverId : undefined,
139+
'aria-haspopup': 'dialog',
140+
});
141+
116142
return (
117-
<StyledTooltip arrow id={id} placement="top" title={title} enterDelay={enterDelay ?? 500} enterNextDelay={500}>
118-
{/* Wrap in a span so that the tooltip always has a child that can hold a ref. */}
119-
<span>{children}</span>
120-
</StyledTooltip>
143+
<>
144+
{trigger}
145+
146+
<Popover
147+
open={open}
148+
anchorEl={anchorEl}
149+
onClose={handleClose}
150+
onClick={(event) => event.stopPropagation()}
151+
anchorOrigin={{
152+
vertical: 'top',
153+
horizontal: 'center',
154+
}}
155+
transformOrigin={{
156+
vertical: 'bottom',
157+
horizontal: 'center',
158+
}}
159+
slotProps={{
160+
paper: {
161+
id: popoverId,
162+
role: 'dialog',
163+
'aria-label': 'Panel description',
164+
sx: (theme) => ({
165+
backgroundColor: theme.palette.background.tooltip,
166+
color: theme.palette.text.primary,
167+
maxWidth: '500px',
168+
padding: theme.spacing(1),
169+
boxShadow: theme.shadows[1],
170+
}),
171+
},
172+
}}
173+
>
174+
<PanelDescriptionContent description={description} />
175+
</Popover>
176+
</>
121177
);
122178
}
123179

@@ -171,21 +227,3 @@ const MarkdownContent = styled(Box)(({ theme }) => ({
171227
},
172228
'& img': { maxWidth: '100%' },
173229
}));
174-
175-
function PopperClassTooltip({ className, ...props }: TooltipProps): ReactElement {
176-
const classes = useMemo(() => ({ popper: className }), [className]);
177-
return <Tooltip {...props} classes={classes} />;
178-
}
179-
180-
const StyledTooltip = styled(PopperClassTooltip)(({ theme }) => ({
181-
[`& .${tooltipClasses.tooltip}`]: {
182-
backgroundColor: theme.palette.background.tooltip,
183-
color: theme.palette.text.primary,
184-
maxWidth: '500px',
185-
padding: theme.spacing(1),
186-
boxShadow: theme.shadows[1],
187-
},
188-
[`& .${tooltipClasses.arrow}`]: {
189-
color: theme.palette.background.tooltip,
190-
},
191-
}));

0 commit comments

Comments
 (0)