mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: remove react-popper from AnnotationEditor and AnnotationMarker (#82090)
update AnnotationEditor and AnnotationMarker to use floating-ui/react instead of react-popper
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null);
|
||||
const [editorPopover, setEditorPopover] = useState<HTMLDivElement | null>(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 = ({
|
||||
>
|
||||
<div // Annotation marker
|
||||
className={cx(
|
||||
css`
|
||||
position: absolute;
|
||||
top: ${selection.bbox.top}px;
|
||||
left: ${selection.bbox.left}px;
|
||||
width: ${selection.bbox.width}px;
|
||||
height: ${selection.bbox.height}px;
|
||||
`,
|
||||
css({
|
||||
position: 'absolute',
|
||||
top: selection.bbox.top,
|
||||
left: selection.bbox.left,
|
||||
width: selection.bbox.width,
|
||||
height: selection.bbox.height,
|
||||
}),
|
||||
isRegionAnnotation ? styles.overlayRange(annotation) : styles.overlay(annotation)
|
||||
)}
|
||||
>
|
||||
<div
|
||||
ref={setPopperTrigger}
|
||||
ref={refs.setReference}
|
||||
className={
|
||||
isRegionAnnotation
|
||||
? cx(commonStyles(annotation).markerBar, styles.markerBar)
|
||||
: cx(commonStyles(annotation).markerTriangle, styles.markerTriangle)
|
||||
}
|
||||
{...getReferenceProps()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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()}
|
||||
/>
|
||||
</>
|
||||
</Portal>
|
||||
@@ -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',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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<HTMLDivElement> {
|
||||
|
||||
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<HTMLDivElement | null>(null);
|
||||
const [tooltipRef, setTooltipRef] = useState<HTMLDivElement | null>(null);
|
||||
const [editorRef, setEditorRef] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const popoverRenderTimeout = useRef<NodeJS.Timeout>();
|
||||
// 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 (
|
||||
<>
|
||||
<div
|
||||
ref={setMarkerRef}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
ref={refs.setReference}
|
||||
className={!isRegionAnnotation ? styles.markerWrapper : undefined}
|
||||
data-testid={selectors.pages.Dashboard.Annotations.marker}
|
||||
{...getReferenceProps()}
|
||||
>
|
||||
{marker}
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<Portal>
|
||||
<div
|
||||
ref={setTooltipRef}
|
||||
style={popper.styles.popper}
|
||||
{...popper.attributes.popper}
|
||||
className={styles.tooltip}
|
||||
onMouseEnter={onPopoverMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<div className={styles.tooltip} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
|
||||
{renderTooltip()}
|
||||
</div>
|
||||
</Portal>
|
||||
@@ -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()}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user