Tooltips: Make tooltips non interactive by default (#45053)

* Tooltips: Make tooltips non interactive by default

* More updates and cleanup

* Update

* Make time range picker tooltip interactive

* Fix issue

* Trying to make Receivers test faster

* Make JSONCell tooltip interactive
This commit is contained in:
Torkel Ödegaard
2022-02-08 22:58:20 +01:00
committed by GitHub
parent 16e001e762
commit 7c0b453e19
31 changed files with 293 additions and 311 deletions
@@ -69,7 +69,7 @@ export function createComponents(colors: ThemeColors, shadows: ThemeShadows): Th
background: input.background,
},
tooltip: {
background: colors.mode === 'light' ? '#555' : colors.background.secondary,
background: colors.mode === 'light' ? '#555' : '#35383e',
text: colors.mode === 'light' ? '#FFF' : colors.text.primary,
},
dashboard: {
+1
View File
@@ -77,6 +77,7 @@
"react-hook-form": "7.5.3",
"react-inlinesvg": "2.3.0",
"react-popper": "2.2.5",
"react-popper-tooltip": "^4.3.1",
"react-router-dom": "^5.2.0",
"react-select": "5.2.2",
"react-select-event": "^5.1.0",
@@ -1,6 +1,6 @@
import React from 'react';
import { NamedColorsPalette } from './NamedColorsPalette';
import { PopoverContentProps } from '../Tooltip/Tooltip';
import { PopoverContentProps } from '../Tooltip';
import SpectrumPalette from './SpectrumPalette';
import { Themeable2 } from '../../types/theme';
import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation';
@@ -1,7 +1,7 @@
import React, { FunctionComponent } from 'react';
import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover';
import { PopoverContentProps } from '../Tooltip/Tooltip';
import { PopoverContentProps } from '../Tooltip';
import { Switch } from '../Forms/Legacy/Switch/Switch';
import { css } from '@emotion/css';
import { withTheme2, useStyles } from '../../themes';
@@ -105,7 +105,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme
/>
)}
<Tooltip content={<TimePickerTooltip timeRange={value} timeZone={timeZone} />} placement="bottom">
<Tooltip content={<TimePickerTooltip timeRange={value} timeZone={timeZone} />} placement="bottom" interactive>
<ToolbarButton
data-testid={selectors.components.TimePicker.openButton}
aria-label={`Time range picker with current time range ${formattedRange(value, timeZone)} selected`}
@@ -1,5 +1,5 @@
import React, { HTMLAttributes } from 'react';
import { PopoverContent } from '../Tooltip/Tooltip';
import { PopoverContent } from '../Tooltip';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { ToolbarButtonVariant, ToolbarButton, ButtonGroup } from '../Button';
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
@@ -1,7 +1,7 @@
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
import { css, cx } from '@emotion/css';
import { InlineFormLabel } from '../FormLabel/FormLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
import { PopoverContent } from '../Tooltip';
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
label: string;
@@ -1,6 +1,6 @@
import React, { FunctionComponent, ReactNode } from 'react';
import classNames from 'classnames';
import { Tooltip, PopoverContent } from '../Tooltip/Tooltip';
import { Tooltip, PopoverContent } from '../Tooltip';
import { Icon } from '../Icon/Icon';
interface Props {
@@ -3,7 +3,7 @@ import { cx, css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useTheme2 } from '../../themes';
import { InlineLabel } from './InlineLabel';
import { PopoverContent } from '../Tooltip/Tooltip';
import { PopoverContent } from '../Tooltip';
import { FieldProps } from './Field';
import { getChildId } from '../../utils/reactUtils';
import { FieldValidationMessage } from './FieldValidationMessage';
@@ -1,7 +1,7 @@
import React, { FunctionComponent } from 'react';
import { GrafanaTheme } from '@grafana/data';
import { css, cx } from '@emotion/css';
import { Tooltip, PopoverContent } from '../Tooltip/Tooltip';
import { Tooltip, PopoverContent } from '../Tooltip';
import { Icon } from '../Icon/Icon';
import { useTheme } from '../../themes';
import { LabelProps } from './Label';
@@ -14,7 +14,7 @@ import IndicatorsContainer from './IndicatorsContainer';
import NoOptionsMessage from './NoOptionsMessage';
import resetSelectStyles from '../../../Select/resetSelectStyles';
import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar';
import { PopoverContent, Tooltip } from '../../../Tooltip/Tooltip';
import { Tooltip, PopoverContent } from '../../../Tooltip';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { ThemeContext } from '../../../../themes';
@@ -5,8 +5,7 @@ import { stylesFactory } from '../../themes/stylesFactory';
import { css, cx } from '@emotion/css';
import { useTheme2 } from '../../themes/ThemeContext';
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { PopoverContent, Tooltip } from '../Tooltip/Tooltip';
import { TooltipPlacement } from '../Tooltip/PopoverController';
import { TooltipPlacement, PopoverContent, Tooltip } from '../Tooltip';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
export type IconButtonVariant = 'primary' | 'secondary' | 'destructive';
@@ -1,5 +1,5 @@
import React from 'react';
import { PopoverContent, TooltipProps } from '../Tooltip/Tooltip';
import { TooltipProps, PopoverContent } from '../Tooltip';
import { IconButton } from '../IconButton/IconButton';
interface InfoTooltipProps extends Omit<TooltipProps, 'children' | 'content'> {
@@ -3,7 +3,7 @@ import React, { InputHTMLAttributes, FunctionComponent } from 'react';
import { FormField } from '../FormField/FormField';
import { Button } from '../Button/Button';
import { css, cx } from '@emotion/css';
import { PopoverContent } from '../Tooltip/Tooltip';
import { PopoverContent } from '../Tooltip';
export interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onReset'> {
// Function to use when reset is clicked. Means you have to reset the input value yourself as this is uncontrolled
@@ -29,7 +29,7 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
const content = <JSONTooltip value={value} />;
return (
<Tooltip placement="auto-start" content={content} theme="info-alt">
<Tooltip placement="auto-start" content={content} theme="info" interactive>
<div {...cellProps} className={tableStyles.cellContainer}>
<div className={cx(tableStyles.cellText, txt)}>{displayValue}</div>
</div>
@@ -3,7 +3,7 @@ import { Placement, VirtualElement } from '@popperjs/core';
import { Manager, Popper as ReactPopper, PopperArrowProps } from 'react-popper';
import { Portal } from '../Portal/Portal';
import Transition from 'react-transition-group/Transition';
import { PopoverContent } from './Tooltip';
import { PopoverContent } from './types';
const defaultTransitionStyles = {
transitionProperty: 'opacity',
@@ -1,33 +1,6 @@
import React from 'react';
import { Placement } from '@popperjs/core';
import { PopoverContent } from './Tooltip';
// This API allows popovers to update Popper's position when e.g. popover content changes
// updatePopperPosition is delivered to content by react-popper
export interface UsingPopperProps {
show?: boolean;
placement?: TooltipPlacement;
content: PopoverContent;
children: JSX.Element;
}
export type TooltipPlacement =
| 'auto-start'
| 'auto'
| 'auto-end'
| 'top-start'
| 'top'
| 'top-end'
| 'right-start'
| 'right'
| 'right-end'
| 'bottom-end'
| 'bottom'
| 'bottom-start'
| 'left-end'
| 'left'
| 'left-start';
import { PopoverContent } from './types';
type PopperControllerRenderProp = (
showPopper: () => void,
@@ -61,9 +61,10 @@ export const Basic: Story = ({ content, ...args }) => {
</Tooltip>
);
};
Basic.args = {
content: 'This is a tooltip',
theme: 'info',
show: true,
show: undefined,
placement: 'auto',
};
@@ -1,61 +1,204 @@
import React, { createRef, FC } from 'react';
import { VirtualElement } from '@popperjs/core';
import { Popover } from './Popover';
import { PopoverController, UsingPopperProps } from './PopoverController';
import { closePopover } from '../../utils/closePopover';
import React from 'react';
import { usePopperTooltip } from 'react-popper-tooltip';
import { colorManipulator, GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
import { useStyles2 } from '../../themes/ThemeContext';
import { PopoverContent, TooltipPlacement } from './types';
import { Portal } from '../Portal/Portal';
export interface TooltipProps extends UsingPopperProps {
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 interface PopoverContentProps {
updatePopperPosition?: () => void;
}
export const Tooltip = React.memo(({ children, theme, interactive, show, placement, content }: TooltipProps) => {
const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible, update } = usePopperTooltip({
visible: show,
placement: placement,
interactive: interactive,
delayHide: interactive ? 100 : 0,
delayShow: 150,
offset: [0, 8],
trigger: ['hover', 'focus'],
});
export type PopoverContent = string | React.ReactElement<any> | ((props: PopoverContentProps) => JSX.Element);
export const Tooltip: FC<TooltipProps> = React.memo(({ children, theme, ...controllerProps }: TooltipProps) => {
const tooltipTriggerRef = createRef<HTMLElement | VirtualElement>();
const popperBackgroundClassName = 'popper__background' + (theme ? ' popper__background--' + theme : '');
const styles = useStyles2(getStyles);
const containerStyle = styles[theme ?? 'info'];
return (
<PopoverController {...controllerProps}>
{(showPopper, hidePopper, popperProps) => {
{
/* Override internal 'show' state if passed in as prop */
}
const payloadProps = {
...popperProps,
show: controllerProps.show !== undefined ? controllerProps.show : popperProps.show,
};
return (
<>
{tooltipTriggerRef.current && controllerProps.content && (
<Popover
{...payloadProps}
onMouseEnter={showPopper}
onMouseLeave={hidePopper}
referenceElement={tooltipTriggerRef.current}
wrapperClassName="popper"
className={popperBackgroundClassName}
renderArrow={({ arrowProps, placement }) => (
<div className="popper__arrow" data-placement={placement} {...arrowProps} />
)}
/>
)}
{React.cloneElement(children, {
ref: tooltipTriggerRef,
onMouseEnter: showPopper,
onMouseLeave: hidePopper,
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => closePopover(event, hidePopper),
onFocus: showPopper,
onBlur: hidePopper,
})}
</>
);
}}
</PopoverController>
<>
{React.cloneElement(children, {
ref: setTriggerRef,
})}
{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' &&
content({
updatePopperPosition: update as any,
})}
</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;
&[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.3)};
color: ${tooltipText};
}
strong,
em {
color: ${colorManipulator.emphasize(tooltipBg)};
}
a {
color: ${theme.colors.text.link};
text-decoration: underline;
}
`;
}
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,
};
}
@@ -1,156 +0,0 @@
$popper-margin-from-ref: 5px;
@mixin popper-theme($backgroundColor, $textColor) {
background: $backgroundColor;
color: $textColor;
.popper__arrow {
border-color: $backgroundColor;
}
code {
border: none;
background: darken($backgroundColor, 15%);
color: lighten($textColor, 20%);
}
strong,
em {
color: lighten($textColor, 20%);
}
a {
color: $tooltipLinkColor;
text-decoration: underline;
}
a.external-link {
color: $tooltipExternalLinkColor;
}
}
.popper {
position: absolute;
z-index: $zindex-tooltip;
color: $tooltipColor;
max-width: 400px;
}
.popper__background {
background: $tooltipBackground;
border-radius: $border-radius-sm;
box-shadow: $tooltipShadow;
padding: $space-xs $space-sm;
font-size: $font-size-sm;
color: $tooltipColor;
font-weight: $font-weight-semi-bold;
.popper__arrow {
border-color: $tooltipBackground;
}
// Themes
&.popper__background--error {
@include popper-theme($tooltipBackgroundError, $white);
}
&.popper__background--info {
@include popper-theme($popover-help-bg, $popover-help-color);
}
&.popper__background--info-alt {
@include popper-theme($popover-code-bg, $text-color);
}
}
.popper__arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 0px;
}
// Top
.popper[data-placement^='top'] {
padding-bottom: $popper-margin-from-ref;
}
.popper[data-placement^='top'] .popper__arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
bottom: -5px;
left: calc(50% - 5px);
padding-top: 5px;
}
// Bottom
.popper[data-placement^='bottom'] {
padding-top: $popper-margin-from-ref;
}
.popper[data-placement^='bottom'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0;
left: calc(50% - 5px);
}
.popper[data-placement^='bottom-start'] {
padding-top: $popper-margin-from-ref;
}
.popper[data-placement^='bottom-start'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0;
left: 5px;
}
.popper[data-placement^='bottom-end'] {
padding-top: $popper-margin-from-ref;
}
.popper[data-placement^='bottom-end'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0;
left: calc(100% - 5px);
}
// Right
.popper[data-placement^='right'] {
padding-left: $popper-margin-from-ref;
}
.popper[data-placement^='right'] .popper__arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent;
border-top-color: transparent;
border-bottom-color: transparent;
left: 0;
top: calc(50% - 5px);
}
// Left
.popper[data-placement^='left'] {
padding-right: $popper-margin-from-ref;
}
.popper[data-placement^='left'] .popper__arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
right: -5px;
top: calc(50% - 5px);
}
.popper__target,
.popper__manager {
display: inline-block;
}
.popper__manager--block {
display: block;
}
@@ -0,0 +1,3 @@
export { Tooltip, TooltipProps } from './Tooltip';
export { PopoverController } from './PopoverController';
export { TooltipPlacement, PopoverContent, PopoverContentProps } from './types';
@@ -0,0 +1,27 @@
/**
* This API allows popovers to update Popper's position when e.g. popover content changes
* updatePopperPosition is delivered to content by react-popper.
*/
export interface PopoverContentProps {
// Is this used anywhere in plugins? Can we remove it or rename it to just update?
updatePopperPosition?: () => void;
}
export type PopoverContent = string | React.ReactElement<any> | ((props: PopoverContentProps) => JSX.Element);
export type TooltipPlacement =
| 'auto-start'
| 'auto'
| 'auto-end'
| 'top-start'
| 'top'
| 'top-end'
| 'right-start'
| 'right'
| 'right-end'
| 'bottom-end'
| 'bottom'
| 'bottom-start'
| 'left-end'
| 'left'
| 'left-start';
@@ -2,6 +2,5 @@
@import 'Drawer/Drawer';
@import 'Forms/Legacy/Select/Select';
@import 'DateTimePickers/TimeOfDayPicker';
@import 'Tooltip/Tooltip';
@import 'Slider/Slider';
@import 'uPlot/Plot';
+2 -1
View File
@@ -2,7 +2,8 @@ export { Icon } from './Icon/Icon';
export { IconButton, IconButtonVariant } from './IconButton/IconButton';
export { ConfirmButton } from './ConfirmButton/ConfirmButton';
export { DeleteButton } from './ConfirmButton/DeleteButton';
export { Tooltip, PopoverContent } from './Tooltip/Tooltip';
export { Tooltip } from './Tooltip/Tooltip';
export { PopoverContent } from './Tooltip/types';
export { PopoverController } from './Tooltip/PopoverController';
export { Popover } from './Tooltip/Popover';
export { Portal } from './Portal/Portal';