diff --git a/.betterer.results b/.betterer.results index 416142425ce..39b77490211 100644 --- a/.betterer.results +++ b/.betterer.results @@ -6270,12 +6270,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"] ], "public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] + [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -6286,11 +6281,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "5"], [0, 0, 0, "Styles should be written using objects.", "6"] ], - "public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], "public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], diff --git a/packages/grafana-ui/src/components/Toggletip/types.ts b/packages/grafana-ui/src/components/Toggletip/types.ts index 4f3332b0810..62d0335a868 100644 --- a/packages/grafana-ui/src/components/Toggletip/types.ts +++ b/packages/grafana-ui/src/components/Toggletip/types.ts @@ -1,8 +1,9 @@ -/** - * This API allows popovers to update Popper's position when e.g. popover content changes - * update is delivered to content by react-popper. - */ export interface ToggletipContentProps { + /** + * @deprecated + * This prop is deprecated and no longer has any effect as popper position updates automatically. + * It will be removed in a future release. + */ update?: () => void; } diff --git a/packages/grafana-ui/src/components/Tooltip/types.ts b/packages/grafana-ui/src/components/Tooltip/types.ts index 6ccaf00069c..bd4d9ba8529 100644 --- a/packages/grafana-ui/src/components/Tooltip/types.ts +++ b/packages/grafana-ui/src/components/Tooltip/types.ts @@ -1,10 +1,11 @@ import { Placement } from '@floating-ui/react'; -/** - * 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? + /** + * @deprecated + * This prop is deprecated and no longer has any effect as popper position updates automatically. + * It will be removed in a future release. + */ updatePopperPosition?: () => void; } diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx index 686c076fc86..1702de0ce4a 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx @@ -1,6 +1,6 @@ import { css, cx } from '@emotion/css'; -import React, { HTMLAttributes, useState } from 'react'; -import { usePopper } from 'react-popper'; +import { autoUpdate, flip, shift, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; +import React, { HTMLAttributes } from 'react'; import { colorManipulator, DataFrame, getDisplayProcessor, GrafanaTheme2, TimeZone } from '@grafana/data'; import { PlotSelection, useStyles2, useTheme2, Portal, DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; @@ -31,22 +31,35 @@ export const AnnotationEditor = ({ const theme = useTheme2(); const styles = useStyles2(getStyles); const commonStyles = useStyles2(getCommonAnnotationStyles); - const [popperTrigger, setPopperTrigger] = useState(null); - const [editorPopover, setEditorPopover] = useState(null); - const popper = usePopper(popperTrigger, editorPopover, { - modifiers: [ - { name: 'arrow', enabled: false }, - { - name: 'preventOverflow', - enabled: true, - options: { - rootBoundary: 'viewport', - }, - }, - ], + // the order of middleware is important! + const middleware = [ + flip({ + fallbackAxisSideDirection: 'end', + // see https://floating-ui.com/docs/flip#combining-with-shift + crossAxis: false, + boundary: document.body, + }), + shift(), + ]; + + const { context, refs, floatingStyles } = useFloating({ + open: true, + placement: 'bottom', + onOpenChange: (open) => { + if (!open) { + onDismiss(); + } + }, + middleware, + whileElementsMounted: autoUpdate, + strategy: 'fixed', }); + const dismiss = useDismiss(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]); + let xField = data.fields[0]; if (!xField) { return null; @@ -62,23 +75,24 @@ export const AnnotationEditor = ({ >
@@ -88,9 +102,9 @@ export const AnnotationEditor = ({ timeFormatter={(v) => xFieldFmt(v).text} onSave={onSave} onDismiss={onDismiss} - ref={setEditorPopover} - style={popper.styles.popper} - {...popper.attributes.popper} + ref={refs.setFloating} + style={floatingStyles} + {...getFloatingProps()} /> @@ -101,27 +115,27 @@ const getStyles = (theme: GrafanaTheme2) => { return { overlay: (annotation?: AnnotationsDataFrameViewDTO) => { const color = theme.visualization.getColorByName(annotation?.color || DEFAULT_ANNOTATION_COLOR); - return css` - border-left: 1px dashed ${color}; - `; + return css({ + borderLeft: `1px dashed ${color}`, + }); }, overlayRange: (annotation?: AnnotationsDataFrameViewDTO) => { const color = theme.visualization.getColorByName(annotation?.color || DEFAULT_ANNOTATION_COLOR); - return css` - background: ${colorManipulator.alpha(color, 0.1)}; - border-left: 1px dashed ${color}; - border-right: 1px dashed ${color}; - `; + return css({ + background: colorManipulator.alpha(color, 0.1), + borderLeft: `1px dashed ${color}`, + borderRight: `1px dashed ${color}`, + }); }, - markerTriangle: css` - top: calc(100% + 2px); - left: -4px; - position: absolute; - `, - markerBar: css` - top: 100%; - left: 0; - position: absolute; - `, + markerTriangle: css({ + top: `calc(100% + 2px)`, + left: '-4px', + position: 'absolute', + }), + markerBar: css({ + top: '100%', + left: 0, + position: 'absolute', + }), }; }; diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx index 6a936dbbfeb..2853fbc70d7 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx @@ -1,6 +1,15 @@ import { css } from '@emotion/css'; -import React, { HTMLAttributes, useCallback, useRef, useState } from 'react'; -import { usePopper } from 'react-popper'; +import { + autoUpdate, + flip, + safePolygon, + shift, + useDismiss, + useFloating, + useHover, + useInteractions, +} from '@floating-ui/react'; +import React, { HTMLAttributes, useCallback, useState } from 'react'; import { GrafanaTheme2, dateTimeFormat, systemDateFormats, TimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -21,19 +30,6 @@ interface Props extends HTMLAttributes { const MIN_REGION_ANNOTATION_WIDTH = 6; -const POPPER_CONFIG = { - modifiers: [ - { name: 'arrow', enabled: false }, - { - name: 'preventOverflow', - enabled: true, - options: { - rootBoundary: 'viewport', - }, - }, - ], -}; - export function AnnotationMarker({ annotation, timeZone, width }: Props) { const { canEditAnnotations, canDeleteAnnotations, ...panelCtx } = usePanelContext(); const commonStyles = useStyles2(getCommonAnnotationStyles); @@ -41,14 +37,33 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) { const [isOpen, setIsOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); - const [markerRef, setMarkerRef] = useState(null); - const [tooltipRef, setTooltipRef] = useState(null); - const [editorRef, setEditorRef] = useState(null); - const popoverRenderTimeout = useRef(); + // the order of middleware is important! + const middleware = [ + flip({ + fallbackAxisSideDirection: 'end', + // see https://floating-ui.com/docs/flip#combining-with-shift + crossAxis: false, + boundary: document.body, + }), + shift(), + ]; - const popper = usePopper(markerRef, tooltipRef, POPPER_CONFIG); - const editorPopper = usePopper(markerRef, editorRef, POPPER_CONFIG); + const { context, refs, floatingStyles } = useFloating({ + open: isOpen, + placement: 'bottom', + onOpenChange: setIsOpen, + middleware, + whileElementsMounted: autoUpdate, + strategy: 'fixed', + }); + + const hover = useHover(context, { + handleClose: safePolygon(), + }); + const dismiss = useDismiss(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, hover]); const onAnnotationEdit = useCallback(() => { setIsEditing(true); @@ -61,25 +76,6 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) { } }, [annotation, panelCtx]); - const onMouseEnter = useCallback(() => { - if (popoverRenderTimeout.current) { - clearTimeout(popoverRenderTimeout.current); - } - setIsOpen(true); - }, [setIsOpen]); - - const onPopoverMouseEnter = useCallback(() => { - if (popoverRenderTimeout.current) { - clearTimeout(popoverRenderTimeout.current); - } - }, []); - - const onMouseLeave = useCallback(() => { - popoverRenderTimeout.current = setTimeout(() => { - setIsOpen(false); - }, 100); - }, [setIsOpen]); - const timeFormatter = useCallback( (value: number) => { return dateTimeFormat(value, { @@ -124,25 +120,17 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) { return ( <>
{marker}
{isOpen && ( -
+
{renderTooltip()}
@@ -155,9 +143,9 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) { onSave={() => setIsEditing(false)} timeFormatter={timeFormatter} annotation={annotation} - ref={setEditorRef} - style={editorPopper.styles.popper} - {...editorPopper.attributes.popper} + ref={refs.setFloating} + style={floatingStyles} + {...getFloatingProps()} /> )} @@ -167,16 +155,13 @@ export function AnnotationMarker({ annotation, timeZone, width }: Props) { const getStyles = (theme: GrafanaTheme2) => { return { - markerWrapper: css` - label: markerWrapper; - padding: 0 4px 4px 4px; - `, - wrapper: css` - max-width: 400px; - `, - tooltip: css` - ${getTooltipContainerStyles(theme)}; - padding: 0; - `, + markerWrapper: css({ + label: 'markerWrapper', + padding: theme.spacing(0, 0.5, 0.5, 0.5), + }), + tooltip: css({ + ...getTooltipContainerStyles(theme), + padding: 0, + }), }; };