mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Co-authored-by: Torkel Ödegaard <torkel@grafana.com> Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com> Co-authored-by: polinaboneva <polina.boneva@grafana.com>
234 lines
6.4 KiB
TypeScript
234 lines
6.4 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import React, { useEffect } from 'react';
|
|
import { usePopperTooltip } from 'react-popper-tooltip';
|
|
|
|
import { colorManipulator, GrafanaTheme2 } from '@grafana/data';
|
|
|
|
import { useStyles2 } from '../../themes/ThemeContext';
|
|
import { Portal } from '../Portal/Portal';
|
|
|
|
import { PopoverContent, TooltipPlacement } from './types';
|
|
|
|
export interface TooltipProps {
|
|
theme?: 'info' | 'error' | 'info-alt';
|
|
show?: boolean;
|
|
placement?: TooltipPlacement;
|
|
content: PopoverContent;
|
|
children: JSX.Element;
|
|
/**
|
|
* Set to true if you want the tooltip to stay long enough so the user can move mouse over content to select text or click a link
|
|
*/
|
|
interactive?: boolean;
|
|
}
|
|
|
|
export const Tooltip = React.memo(({ children, theme, interactive, show, placement, content }: TooltipProps) => {
|
|
const [controlledVisible, setControlledVisible] = React.useState(show);
|
|
|
|
useEffect(() => {
|
|
if (controlledVisible !== false) {
|
|
const handleKeyDown = (enterKey: KeyboardEvent) => {
|
|
if (enterKey.key === 'Escape') {
|
|
setControlledVisible(false);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
} else {
|
|
return;
|
|
}
|
|
}, [controlledVisible]);
|
|
|
|
const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible, update } = usePopperTooltip({
|
|
visible: controlledVisible,
|
|
placement: placement,
|
|
interactive: interactive,
|
|
delayHide: interactive ? 100 : 0,
|
|
delayShow: 150,
|
|
offset: [0, 8],
|
|
trigger: ['hover', 'focus'],
|
|
onVisibleChange: setControlledVisible,
|
|
});
|
|
|
|
const styles = useStyles2(getStyles);
|
|
const containerStyle = styles[theme ?? 'info'];
|
|
|
|
return (
|
|
<>
|
|
{React.cloneElement(children, {
|
|
ref: setTriggerRef,
|
|
tabIndex: 0, // tooltip should be keyboard focusable
|
|
})}
|
|
{visible && (
|
|
<Portal>
|
|
<div ref={setTooltipRef} {...getTooltipProps({ className: containerStyle })}>
|
|
<div {...getArrowProps({ className: 'tooltip-arrow' })} />
|
|
{typeof content === 'string' && content}
|
|
{React.isValidElement(content) && React.cloneElement(content)}
|
|
{typeof content === 'function' &&
|
|
update &&
|
|
content({
|
|
updatePopperPosition: update,
|
|
})}
|
|
</div>
|
|
</Portal>
|
|
)}
|
|
</>
|
|
);
|
|
});
|
|
|
|
Tooltip.displayName = 'Tooltip';
|
|
|
|
function getStyles(theme: GrafanaTheme2) {
|
|
function buildTooltipTheme(tooltipBg: string, tooltipBorder: string, tooltipText: string) {
|
|
return css`
|
|
background-color: ${tooltipBg};
|
|
border-radius: 3px;
|
|
border: 1px solid ${tooltipBorder};
|
|
box-shadow: ${theme.shadows.z2};
|
|
color: ${tooltipText};
|
|
font-size: ${theme.typography.bodySmall.fontSize};
|
|
padding: ${theme.spacing(0.5, 1)};
|
|
transition: opacity 0.3s;
|
|
z-index: ${theme.zIndex.tooltip};
|
|
max-width: 400px;
|
|
overflow-wrap: break-word;
|
|
|
|
&[data-popper-interactive='false'] {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tooltip-arrow {
|
|
height: 1rem;
|
|
position: absolute;
|
|
width: 1rem;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tooltip-arrow::before {
|
|
border-style: solid;
|
|
content: '';
|
|
display: block;
|
|
height: 0;
|
|
margin: auto;
|
|
width: 0;
|
|
}
|
|
|
|
.tooltip-arrow::after {
|
|
border-style: solid;
|
|
content: '';
|
|
display: block;
|
|
height: 0;
|
|
margin: auto;
|
|
position: absolute;
|
|
width: 0;
|
|
}
|
|
|
|
&[data-popper-placement*='bottom'] .tooltip-arrow {
|
|
left: 0;
|
|
margin-top: -10px;
|
|
top: 0;
|
|
}
|
|
|
|
&[data-popper-placement*='bottom'] .tooltip-arrow::before {
|
|
border-color: transparent transparent ${tooltipBorder} transparent;
|
|
border-width: 0 8px 7px 8px;
|
|
position: absolute;
|
|
top: -1px;
|
|
}
|
|
|
|
&[data-popper-placement*='bottom'] .tooltip-arrow::after {
|
|
border-color: transparent transparent ${tooltipBg} transparent;
|
|
border-width: 0 8px 7px 8px;
|
|
}
|
|
|
|
&[data-popper-placement*='top'] .tooltip-arrow {
|
|
bottom: 0;
|
|
left: 0;
|
|
margin-bottom: -11px;
|
|
}
|
|
|
|
&[data-popper-placement*='top'] .tooltip-arrow::before {
|
|
border-color: ${tooltipBorder} transparent transparent transparent;
|
|
border-width: 7px 8px 0 7px;
|
|
position: absolute;
|
|
top: 1px;
|
|
}
|
|
|
|
&[data-popper-placement*='top'] .tooltip-arrow::after {
|
|
border-color: ${tooltipBg} transparent transparent transparent;
|
|
border-width: 7px 8px 0 7px;
|
|
}
|
|
|
|
&[data-popper-placement*='right'] .tooltip-arrow {
|
|
left: 0;
|
|
margin-left: -11px;
|
|
}
|
|
|
|
&[data-popper-placement*='right'] .tooltip-arrow::before {
|
|
border-color: transparent ${tooltipBorder} transparent transparent;
|
|
border-width: 7px 6px 7px 0;
|
|
}
|
|
|
|
&[data-popper-placement*='right'] .tooltip-arrow::after {
|
|
border-color: transparent ${tooltipBg} transparent transparent;
|
|
border-width: 6px 7px 7px 0;
|
|
left: 2px;
|
|
top: 1px;
|
|
}
|
|
|
|
&[data-popper-placement*='left'] .tooltip-arrow {
|
|
margin-right: -10px;
|
|
right: 0;
|
|
}
|
|
|
|
&[data-popper-placement*='left'] .tooltip-arrow::before {
|
|
border-color: transparent transparent transparent ${tooltipBorder};
|
|
border-width: 7px 0px 6px 7px;
|
|
}
|
|
|
|
&[data-popper-placement*='left'] .tooltip-arrow::after {
|
|
border-color: transparent transparent transparent ${tooltipBg};
|
|
border-width: 6px 0 5px 5px;
|
|
left: 1px;
|
|
top: 1px;
|
|
}
|
|
|
|
code {
|
|
border: none;
|
|
display: inline;
|
|
background: ${colorManipulator.darken(tooltipBg, 0.1)};
|
|
color: ${tooltipText};
|
|
}
|
|
|
|
pre {
|
|
background: ${colorManipulator.darken(tooltipBg, 0.1)};
|
|
color: ${tooltipText};
|
|
}
|
|
|
|
a {
|
|
color: ${tooltipText};
|
|
text-decoration: underline;
|
|
}
|
|
|
|
a:hover {
|
|
text-decoration: none;
|
|
}
|
|
`;
|
|
}
|
|
|
|
const info = buildTooltipTheme(
|
|
theme.components.tooltip.background,
|
|
theme.components.tooltip.background,
|
|
theme.components.tooltip.text
|
|
);
|
|
const error = buildTooltipTheme(theme.colors.error.main, theme.colors.error.main, theme.colors.error.contrastText);
|
|
|
|
return {
|
|
info: info,
|
|
['info-alt']: info,
|
|
error,
|
|
};
|
|
}
|