mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
16e001e762
commit
7c0b453e19
@ -337,6 +337,7 @@
|
||||
"react-loadable": "5.5.0",
|
||||
"react-moveable": "0.30.3",
|
||||
"react-popper": "2.2.5",
|
||||
"react-popper-tooltip": "^4.3.1",
|
||||
"react-redux": "7.2.6",
|
||||
"react-resizable": "3.0.4",
|
||||
"react-reverse-portal": "^2.0.1",
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
}
|
3
packages/grafana-ui/src/components/Tooltip/index.ts
Normal file
3
packages/grafana-ui/src/components/Tooltip/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { Tooltip, TooltipProps } from './Tooltip';
|
||||
export { PopoverController } from './PopoverController';
|
||||
export { TooltipPlacement, PopoverContent, PopoverContentProps } from './types';
|
27
packages/grafana-ui/src/components/Tooltip/types.ts
Normal file
27
packages/grafana-ui/src/components/Tooltip/types.ts
Normal file
@ -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,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';
|
||||
|
@ -174,23 +174,28 @@ describe('Receivers', () => {
|
||||
await renderReceivers();
|
||||
|
||||
// go to new contact point page
|
||||
userEvent.click(await ui.newContactPointButton.find());
|
||||
await act(async () => {
|
||||
userEvent.click(await ui.newContactPointButton.find());
|
||||
});
|
||||
|
||||
await byRole('heading', { name: /create contact point/i }).find();
|
||||
|
||||
expect(locationService.getLocation().pathname).toEqual('/alerting/notifications/receivers/new');
|
||||
|
||||
// type in a name for the new receiver
|
||||
userEvent.type(ui.inputs.name.get(), 'my new receiver');
|
||||
await act(async () => {
|
||||
// type in a name for the new receiver
|
||||
userEvent.type(ui.inputs.name.get(), 'my new receiver');
|
||||
|
||||
// enter some email
|
||||
const email = ui.inputs.email.addresses.get();
|
||||
userEvent.clear(email);
|
||||
userEvent.type(email, 'tester@grafana.com');
|
||||
// enter some email
|
||||
const email = ui.inputs.email.addresses.get();
|
||||
userEvent.clear(email);
|
||||
userEvent.type(email, 'tester@grafana.com');
|
||||
|
||||
// try to test the contact point
|
||||
userEvent.click(ui.testContactPointButton.get());
|
||||
// try to test the contact point
|
||||
userEvent.click(await ui.testContactPointButton.find());
|
||||
});
|
||||
|
||||
await waitFor(() => expect(ui.testContactPointModal.get()).toBeInTheDocument());
|
||||
await waitFor(() => expect(ui.testContactPointModal.get()).toBeInTheDocument(), { timeout: 1000 });
|
||||
userEvent.click(ui.customContactPointOption.get());
|
||||
await waitFor(() => expect(ui.contactPointAnnotationSelect(0).get()).toBeInTheDocument());
|
||||
|
||||
@ -254,7 +259,7 @@ describe('Receivers', () => {
|
||||
|
||||
// it seems react-hook-form does some async state updates after submit
|
||||
await act(async () => {
|
||||
await userEvent.click(ui.saveContactButton.get());
|
||||
userEvent.click(await ui.saveContactButton.find());
|
||||
});
|
||||
|
||||
// see that we're back to main page and proper api calls have been made
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Icon, IconName, useStyles, Tooltip } from '@grafana/ui';
|
||||
import { PopoverContent } from '@grafana/ui/src/components/Tooltip/Tooltip';
|
||||
import { TooltipPlacement } from '@grafana/ui/src/components/Tooltip/PopoverController';
|
||||
import { PopoverContent, TooltipPlacement } from '@grafana/ui/src/components/Tooltip';
|
||||
import React, { FC } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
@ -86,7 +86,7 @@ export class PanelHeaderCorner extends Component<Props> {
|
||||
const ariaLabel = selectors.components.Panels.Panel.headerCornerInfo(infoMode.toLowerCase());
|
||||
|
||||
return (
|
||||
<Tooltip content={content} placement="top-start" theme={theme}>
|
||||
<Tooltip content={content} placement="top-start" theme={theme} interactive>
|
||||
<section className={className} onClick={onClick} aria-label={ariaLabel}>
|
||||
<i aria-hidden className="fa" />
|
||||
<span className="panel-info-corner-inner" />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { PopoverController, Popover, ClickOutsideWrapper, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls';
|
||||
import { FuncInstance } from '../gfunc';
|
||||
import { css } from '@emotion/css';
|
||||
@ -19,13 +19,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
fontSize: theme.typography.bodySmall.fontSize, // to match .gf-form-label
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
paddingBottom: '2px',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const FunctionEditor: React.FC<FunctionEditorProps> = ({ onMoveLeft, onMoveRight, func, ...props }) => {
|
||||
const triggerRef = useRef<HTMLSpanElement>(null);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const renderContent = ({ updatePopperPosition }: any) => (
|
||||
@ -44,41 +42,16 @@ const FunctionEditor: React.FC<FunctionEditorProps> = ({ onMoveLeft, onMoveRight
|
||||
);
|
||||
|
||||
return (
|
||||
<PopoverController content={renderContent} placement="top" hideAfter={100}>
|
||||
{(showPopper, hidePopper, popperProps) => {
|
||||
return (
|
||||
<>
|
||||
{triggerRef.current && (
|
||||
<Popover
|
||||
{...popperProps}
|
||||
referenceElement={triggerRef.current}
|
||||
wrapperClassName="popper"
|
||||
className="popper__background"
|
||||
renderArrow={({ arrowProps, placement }) => (
|
||||
<div className="popper__arrow" data-placement={placement} {...arrowProps} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ClickOutsideWrapper
|
||||
onClick={() => {
|
||||
if (popperProps.show) {
|
||||
hidePopper();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span ref={triggerRef} onClick={popperProps.show ? hidePopper : showPopper} className={styles.label}>
|
||||
{func.def.unknown && (
|
||||
<Tooltip content={<TooltipContent />} placement="bottom">
|
||||
<Icon data-testid="warning-icon" name="exclamation-triangle" size="xs" className={styles.icon} />
|
||||
</Tooltip>
|
||||
)}
|
||||
{func.def.name}
|
||||
</span>
|
||||
</ClickOutsideWrapper>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</PopoverController>
|
||||
<>
|
||||
{func.def.unknown && (
|
||||
<Tooltip content={<TooltipContent />} placement="bottom" interactive>
|
||||
<Icon data-testid="warning-icon" name="exclamation-triangle" size="xs" className={styles.icon} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={renderContent} placement="top" interactive>
|
||||
<span className={styles.label}>{func.def.name}</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -265,9 +265,6 @@ describe('AnnoListPanel', () => {
|
||||
|
||||
getMock.mockClear();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
userEvent.hover(screen.getByRole('img'));
|
||||
|
||||
expect(screen.getByText(/result email/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
20
yarn.lock
20
yarn.lock
@ -4300,6 +4300,7 @@ __metadata:
|
||||
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
|
||||
@ -6613,7 +6614,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@popperjs/core@npm:2.11.2":
|
||||
"@popperjs/core@npm:2.11.2, @popperjs/core@npm:^2.10.2":
|
||||
version: 2.11.2
|
||||
resolution: "@popperjs/core@npm:2.11.2"
|
||||
checksum: 5695bf020eda54636e16a62dc9b5fdd92beaf7b2d19f62fcef049d57c5cff92773562d80cbf760b217c3ec928da310eb24994ab6a00fd39dffa0af9b5dfc01a6
|
||||
@ -20311,6 +20312,7 @@ __metadata:
|
||||
react-loadable: 5.5.0
|
||||
react-moveable: 0.30.3
|
||||
react-popper: 2.2.5
|
||||
react-popper-tooltip: ^4.3.1
|
||||
react-redux: 7.2.6
|
||||
react-refresh: 0.11.0
|
||||
react-resizable: 3.0.4
|
||||
@ -30408,7 +30410,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-popper@npm:2.2.5, react-popper@npm:^2.2.4":
|
||||
"react-popper-tooltip@npm:^4.3.1":
|
||||
version: 4.3.1
|
||||
resolution: "react-popper-tooltip@npm:4.3.1"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.15.4
|
||||
"@popperjs/core": ^2.10.2
|
||||
react-popper: ^2.2.5
|
||||
peerDependencies:
|
||||
react: ">=16.6.0"
|
||||
react-dom: ">=16.6.0"
|
||||
checksum: 82ae84c3b75324ddcafbc3ff4358d77780d20ff28dc5b01a3500b31b34e84caae5dbe3b569165b967b4cdbbacce7af6886e006e0921ee0da5b73638893014e0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-popper@npm:2.2.5, react-popper@npm:^2.2.4, react-popper@npm:^2.2.5":
|
||||
version: 2.2.5
|
||||
resolution: "react-popper@npm:2.2.5"
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user