Floating UI: Update to use the `.main-view` as a boundary (#111089)

* fix(Dropdown): update the boundary to the `.main-view`

* apply floating-ui changes everywhere

* remove some unused imports

* up timeout on this test cause it's unbelievably slow

* don't spread when not necessary

* fix case in panel edit on small screens

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
Levente Balogh 2025-09-17 10:40:49 +02:00 committed by GitHub
parent 3cc99ba811
commit 1564a49373
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 114 additions and 214 deletions

View File

@ -1,21 +1,12 @@
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationInfoButton.tsx
import { css } from '@emotion/css';
import {
autoUpdate,
flip,
offset,
shift,
useClick,
useDismiss,
useFloating,
useInteractions,
} from '@floating-ui/react';
import { autoUpdate, offset, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { memo, useState } from 'react';
import { GrafanaTheme2, renderMarkdown } from '@grafana/data';
import { t } from '@grafana/i18n';
import { FlexItem } from '@grafana/plugin-ui';
import { Button, Portal, useStyles2 } from '@grafana/ui';
import { Button, floatingUtils, Portal, useStyles2 } from '@grafana/ui';
import { QueryBuilderOperation, QueryBuilderOperationDef } from './types';
@ -29,16 +20,7 @@ export const OperationInfoButton = memo<Props>(({ def, operation }) => {
const [show, setShow] = useState(false);
// the order of middleware is important!
const middleware = [
offset(16),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = [offset(16), ...floatingUtils.getPositioningMiddleware()];
const { context, refs, floatingStyles } = useFloating({
open: show,

View File

@ -1,6 +1,7 @@
import { autoUpdate, autoPlacement, size, useFloating } from '@floating-ui/react';
import { useMemo, useRef, useState } from 'react';
import { BOUNDARY_ELEMENT_ID } from '../../utils/floating';
import { measureText } from '../../utils/measureText';
import {
@ -36,7 +37,7 @@ export const useComboboxFloat = (items: Array<ComboboxOption<string | number>>,
autoPlacement({
// see https://floating-ui.com/docs/autoplacement
allowedPlacements: ['bottom-start', 'bottom-end', 'top-start', 'top-end'],
boundary: document.body,
boundary: document.getElementById(BOUNDARY_ELEMENT_ID) ?? undefined,
crossAxis: true,
}),
size({

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/react';
import { autoUpdate, offset, useFloating } from '@floating-ui/react';
import Prism, { Grammar, LanguageMap } from 'prismjs';
import { memo, useEffect, useRef, useState } from 'react';
import * as React from 'react';
@ -12,6 +12,7 @@ import { DataLinkBuiltInVars, GrafanaTheme2, VariableOrigin, VariableSuggestion
import { SlatePrism } from '../../slate-plugins/slate-prism';
import { useStyles2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { SCHEMA, makeValue } from '../../utils/slate';
import { getInputStyles } from '../Input/Input';
import { Portal } from '../Portal/Portal';
@ -97,13 +98,7 @@ export const DataLinkInput = memo(
offset(({ rects }) => ({
alignmentAxis: rects.reference.width,
})),
flip({
fallbackAxisSideDirection: 'start',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
...getPositioningMiddleware(),
];
const { refs, floatingStyles } = useFloating({

View File

@ -1,10 +1,11 @@
import { css } from '@emotion/css';
import { autoUpdate, flip, shift, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { autoUpdate, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react';
import { GrafanaTheme2, dateTime } from '@grafana/data';
import { useStyles2 } from '../../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../../utils/floating';
import { Props as InputProps, Input } from '../../Input/Input';
import { DatePicker } from '../DatePicker/DatePicker';
@ -31,21 +32,15 @@ export const DatePickerWithInput = forwardRef<HTMLInputElement, DatePickerWithIn
({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => {
const [open, setOpen] = useState(false);
const styles = useStyles2(getStyles);
const placement = 'bottom-start';
// the order of middleware is important!
// see https://floating-ui.com/docs/arrow#order
const middleware = [
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = getPositioningMiddleware(placement);
const { context, refs, floatingStyles } = useFloating<HTMLInputElement>({
open,
placement: 'bottom-start',
placement,
onOpenChange: setOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { autoUpdate, flip, shift, useFloating } from '@floating-ui/react';
import { autoUpdate, useFloating } from '@floating-ui/react';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays';
@ -22,6 +22,7 @@ import { Components } from '@grafana/e2e-selectors';
import { t, Trans } from '@grafana/i18n';
import { useStyles2, useTheme2 } from '../../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../../utils/floating';
import { Button } from '../../Button/Button';
import { InlineField } from '../../Forms/InlineField';
import { Icon } from '../../Icon/Icon';
@ -92,22 +93,16 @@ export const DateTimePicker = ({
const theme = useTheme2();
const { modalBackdrop } = useStyles2(getModalStyles);
const isFullscreen = useMedia(`(min-width: ${theme.breakpoints.values.lg}px)`);
const placement = 'bottom-start';
const styles = useStyles2(getStyles);
// the order of middleware is important!
// see https://floating-ui.com/docs/arrow#order
const middleware = [
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = getPositioningMiddleware(placement);
const { refs, floatingStyles } = useFloating({
open: isOpen,
placement: 'bottom-start',
placement,
onOpenChange: setOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { autoUpdate, flip, shift, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { autoUpdate, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays';
@ -9,6 +9,7 @@ import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { useStyles2 } from '../../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../../utils/floating';
import { Button } from '../../Button/Button';
import { Field } from '../../Forms/Field';
import { Icon } from '../../Icon/Icon';
@ -56,21 +57,15 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
);
const { dialogProps } = useDialog({}, ref);
const validOptions = getQuickOptions().filter((o) => isRelativeFormat(o.from));
const placement = 'bottom-start';
// the order of middleware is important!
// see https://floating-ui.com/docs/arrow#order
const middleware = [
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = getPositioningMiddleware(placement);
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement: 'bottom-start',
placement,
onOpenChange: setIsOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,20 +1,12 @@
import { css } from '@emotion/css';
import {
autoUpdate,
flip,
offset,
shift,
useClick,
useDismiss,
useFloating,
useInteractions,
} from '@floating-ui/react';
import { autoUpdate, offset, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { FocusScope } from '@react-aria/focus';
import { memo, HTMLAttributes, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/MenuItem';
import { ToolbarButton, ToolbarButtonVariant } from '../ToolbarButton/ToolbarButton';
@ -39,22 +31,14 @@ const ButtonSelectComponent = <T,>(props: Props<T>) => {
const { className, options, value, onChange, narrow, variant, ...restProps } = props;
const styles = useStyles2(getStyles);
const [isOpen, setIsOpen] = useState(false);
const placement = 'bottom-end';
// the order of middleware is important!
const middleware = [
offset(0),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = [offset(0), ...getPositioningMiddleware(placement)];
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement: 'bottom-end',
placement,
onOpenChange: setIsOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -2,9 +2,7 @@ import { css } from '@emotion/css';
import {
FloatingFocusManager,
autoUpdate,
flip,
offset as floatingUIOffset,
shift,
useClick,
useDismiss,
useFloating,
@ -17,6 +15,7 @@ import { CSSTransition } from 'react-transition-group';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { renderOrCallToRender } from '../../utils/reactUtils';
import { getPlacement } from '../../utils/tooltipUtils';
import { Portal } from '../Portal/Portal';
@ -34,6 +33,7 @@ export interface Props {
export const Dropdown = React.memo(({ children, overlay, placement, offset, onVisibleChange }: Props) => {
const [show, setShow] = useState(false);
const transitionRef = useRef(null);
const floatingUIPlacement = getPlacement(placement);
const handleOpenChange = useCallback(
(newState: boolean) => {
@ -49,18 +49,12 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, onVi
mainAxis: offset?.[0] ?? 8,
crossAxis: offset?.[1] ?? 0,
}),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
...getPositioningMiddleware(floatingUIPlacement),
];
const { context, refs, floatingStyles } = useFloating({
open: show,
placement: getPlacement(placement),
placement: floatingUIPlacement,
onOpenChange: handleOpenChange,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { autoUpdate, flip, offset, shift, Side, useFloating, useTransitionStyles } from '@floating-ui/react';
import { autoUpdate, offset, Side, useFloating, useTransitionStyles } from '@floating-ui/react';
import { useLayoutEffect } from 'react';
import * as React from 'react';
@ -7,6 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { IconName } from '../../types/icon';
import { getPositioningMiddleware } from '../../utils/floating';
import { Icon } from '../Icon/Icon';
import { Portal } from '../Portal/Portal';
@ -30,16 +31,7 @@ export function InlineToast({ referenceElement, children, suffixIcon, placement
// the order of middleware is important!
// `arrow` should almost always be at the end
// see https://floating-ui.com/docs/arrow#order
const middleware = [
offset(8),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = [offset(8), ...getPositioningMiddleware(placement)];
const { context, refs, floatingStyles } = useFloating({
open: true,

View File

@ -1,11 +1,12 @@
import { css } from '@emotion/css';
import { flip, shift, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { useDismiss, useFloating, useInteractions } from '@floating-ui/react';
import { useMemo, ReactNode } from 'react';
import { ActionModel, GrafanaTheme2, LinkModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { Portal } from '../Portal/Portal';
import { VizTooltipFooter } from '../VizTooltip/VizTooltipFooter';
import { VizTooltipWrapper } from '../VizTooltip/VizTooltipWrapper';
@ -27,17 +28,10 @@ interface Props {
export const DataLinksActionsTooltip = ({ links, actions, value, coords, onTooltipClose }: Props) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
const placement = 'right-start';
// 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 middleware = getPositioningMiddleware(placement);
const virtual = useMemo(() => {
const { clientX, clientY } = coords;
@ -66,7 +60,7 @@ export const DataLinksActionsTooltip = ({ links, actions, value, coords, onToolt
const { context, refs, floatingStyles } = useFloating({
open: true,
placement: 'right-start',
placement,
onOpenChange: onTooltipClose,
middleware,
// whileElementsMounted: autoUpdate,

View File

@ -2,11 +2,9 @@ import { css, cx } from '@emotion/css';
import {
arrow,
autoUpdate,
flip,
FloatingArrow,
FloatingFocusManager,
offset,
shift,
useClick,
useDismiss,
useFloating,
@ -19,6 +17,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { buildTooltipTheme, getPlacement } from '../../utils/tooltipUtils';
import { IconButton } from '../IconButton/IconButton';
@ -69,19 +68,14 @@ export const Toggletip = memo(
const style = styles[theme];
const [controlledVisible, setControlledVisible] = useState(show);
const isOpen = show ?? controlledVisible;
const floatingUIPlacement = getPlacement(placement);
// the order of middleware is important!
// `arrow` should almost always be at the end
// see https://floating-ui.com/docs/arrow#order
const middleware = [
offset(8),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
...getPositioningMiddleware(floatingUIPlacement),
arrow({
element: arrowRef,
}),
@ -89,7 +83,7 @@ export const Toggletip = memo(
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement: getPlacement(placement),
placement: floatingUIPlacement,
onOpenChange: (open) => {
if (show === undefined) {
setControlledVisible(open);

View File

@ -1,17 +1,9 @@
import {
FloatingArrow,
arrow,
autoUpdate,
flip,
offset,
shift,
useFloating,
useTransitionStyles,
} from '@floating-ui/react';
import { FloatingArrow, arrow, autoUpdate, offset, useFloating, useTransitionStyles } from '@floating-ui/react';
import { useLayoutEffect, useRef } from 'react';
import * as React from 'react';
import { useTheme2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { getPlacement } from '../../utils/tooltipUtils';
import { Portal } from '../Portal/Portal';
@ -42,20 +34,12 @@ export function Popover({
}: Props) {
const theme = useTheme2();
const arrowRef = useRef(null);
const floatingUIPlacement = getPlacement(placement);
// the order of middleware is important!
// `arrow` should almost always be at the end
// see https://floating-ui.com/docs/arrow#order
const middleware = [
offset(8),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = [offset(8), ...getPositioningMiddleware(floatingUIPlacement)];
if (renderArrow) {
middleware.push(
@ -67,7 +51,7 @@ export function Popover({
const { context, refs, floatingStyles } = useFloating({
open: show,
placement: getPlacement(placement),
placement: floatingUIPlacement,
middleware,
whileElementsMounted: autoUpdate,
strategy: 'fixed',

View File

@ -1,10 +1,8 @@
import {
arrow,
autoUpdate,
flip,
FloatingArrow,
offset,
shift,
useDismiss,
useFloating,
useFocus,
@ -18,6 +16,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes/ThemeContext';
import { getPositioningMiddleware } from '../../utils/floating';
import { buildTooltipTheme, getPlacement } from '../../utils/tooltipUtils';
import { Portal } from '../Portal/Portal';
@ -40,19 +39,14 @@ export const Tooltip = forwardRef<HTMLElement, TooltipProps>(
const arrowRef = useRef(null);
const [controlledVisible, setControlledVisible] = useState(show);
const isOpen = show ?? controlledVisible;
const floatingUIPlacement = getPlacement(placement);
// the order of middleware is important!
// `arrow` should almost always be at the end
// see https://floating-ui.com/docs/arrow#order
const middleware = [
offset(8),
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
...getPositioningMiddleware(floatingUIPlacement),
arrow({
element: arrowRef,
}),
@ -60,7 +54,7 @@ export const Tooltip = forwardRef<HTMLElement, TooltipProps>(
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement: getPlacement(placement),
placement: floatingUIPlacement,
onOpenChange: setControlledVisible,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -14,6 +14,7 @@ import { SecretFormField } from './components/SecretFormField/SecretFormField';
import * as commonOptionsBuilder from './options/builder';
import * as styleMixins from './themes/mixins';
import * as DOMUtil from './utils/dom';
import * as floatingUtils from './utils/floating';
import * as ReactUtils from './utils/reactUtils';
export { Icon } from './components/Icon/Icon';
@ -437,7 +438,7 @@ export { NodeGraphDataFrameFieldNames } from './utils/nodeGraph';
export { fuzzyMatch } from './utils/fuzzy';
export { logOptions } from './utils/logOptions';
export { DOMUtil, ReactUtils };
export { DOMUtil, ReactUtils, floatingUtils };
export { ThemeContext } from '@grafana/data';
export {

View File

@ -0,0 +1,25 @@
import { flip, Placement, shift } from '@floating-ui/react';
export const BOUNDARY_ELEMENT_ID = 'floating-boundary';
export function getPositioningMiddleware(placement?: Placement) {
const middleware = [];
const flipMiddleware = flip({
// Ensure we flip to the perpendicular axis if it doesn't fit
// on narrow viewports.
crossAxis: 'alignment',
fallbackAxisSideDirection: 'end',
boundary: document.getElementById(BOUNDARY_ELEMENT_ID) ?? undefined,
});
const shiftMiddleware = shift();
// Prioritize flip over shift for edge-aligned placements only.
if (placement?.includes('-')) {
middleware.push(flipMiddleware, shiftMiddleware);
} else {
middleware.push(shiftMiddleware, flipMiddleware);
}
return middleware;
}

View File

@ -6,7 +6,7 @@ import { PropsWithChildren, useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { locationSearchToObject, locationService, useScopes } from '@grafana/runtime';
import { ErrorBoundaryAlert, getDragStyles, LinkButton, useStyles2 } from '@grafana/ui';
import { ErrorBoundaryAlert, floatingUtils, getDragStyles, LinkButton, useStyles2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useMediaQueryMinWidth } from 'app/core/hooks/useMediaQueryMinWidth';
import store from 'app/core/store';
@ -87,6 +87,7 @@ export function AppChrome({ children }: Props) {
// doesn't get re-mounted when chromeless goes from true to false.
return (
<div
id={floatingUtils.BOUNDARY_ELEMENT_ID}
className={classNames('main-view', {
'main-view--chrome-hidden': state.chromeless,
})}

View File

@ -6,7 +6,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Alert, Icon, Input, LoadingBar, Stack, Text, useStyles2 } from '@grafana/ui';
import { Alert, floatingUtils, Icon, Input, LoadingBar, Stack, Text, useStyles2 } from '@grafana/ui';
import { useGetFolderQueryFacade } from 'app/api/clients/folder/v1beta1/hooks';
import { getStatusFromError } from 'app/core/utils/errors';
import { DashboardViewItemWithUIItems, DashboardsTreeItem } from 'app/features/browse-dashboards/types';
@ -150,7 +150,7 @@ export function NestedFolderPicker({
flip({
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
boundary: document.getElementById(floatingUtils.BOUNDARY_ELEMENT_ID) ?? undefined,
}),
];

View File

@ -216,6 +216,7 @@ function getStyles(theme: GrafanaTheme2) {
gridTemplateColumns: 'minmax(470px, 1fr) 330px',
gridTemplateRows: '1fr',
gap: theme.spacing(1),
position: 'static',
width: '100%',
},
}),

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { autoUpdate, flip, offset, shift, size, useFloating } from '@floating-ui/react';
import { autoUpdate, offset, size, useFloating } from '@floating-ui/react';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays';
@ -13,7 +13,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { reportInteraction, useFavoriteDatasources } from '@grafana/runtime';
import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema';
import { Button, Icon, Input, ModalsController, Portal, ScrollContainer, useStyles2 } from '@grafana/ui';
import { Button, floatingUtils, Icon, Input, ModalsController, Portal, ScrollContainer, useStyles2 } from '@grafana/ui';
import config from 'app/core/config';
import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection';
import { defaultFileUploadQuery, GrafanaQuery } from 'app/plugins/datasource/grafana/types';
@ -116,6 +116,7 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
variables: props.variables,
});
const favoriteDataSources = useFavoriteDatasources();
const placement = 'bottom-start';
// the order of middleware is important!
const middleware = [
@ -128,18 +129,12 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
elements.floating.style.minHeight = `${minSize}px`;
},
}),
flip({
fallbackStrategy: 'initialPlacement',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
...floatingUtils.getPositioningMiddleware(placement),
];
const { refs, floatingStyles } = useFloating({
open: isOpen,
placement: 'bottom-start',
placement,
onOpenChange: setOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,10 +1,18 @@
import { css } from '@emotion/css';
import { autoUpdate, flip, shift, useFloating } from '@floating-ui/react';
import { autoUpdate, useFloating } from '@floating-ui/react';
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import * as React from 'react';
import { GrafanaTheme2, VariableSuggestion } from '@grafana/data';
import { FieldValidationMessage, Input, Portal, ScrollContainer, TextArea, useTheme2 } from '@grafana/ui';
import {
FieldValidationMessage,
floatingUtils,
Input,
Portal,
ScrollContainer,
TextArea,
useTheme2,
} from '@grafana/ui';
import { DataLinkSuggestions } from '@grafana/ui/internal';
const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
@ -64,6 +72,7 @@ export const SuggestionsInput = ({
const [scrollTop, setScrollTop] = useState(0);
const [inputHeight, setInputHeight] = useState<number>(0);
const [startPos, setStartPos] = useState<number>(0);
const placement = 'bottom-start';
const theme = useTheme2();
const styles = getStyles(theme, inputHeight);
@ -75,19 +84,11 @@ export const SuggestionsInput = ({
}, [scrollTop]);
// the order of middleware is important!
const middleware = [
flip({
fallbackAxisSideDirection: 'start',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
];
const middleware = floatingUtils.getPositioningMiddleware(placement);
const { refs, floatingStyles } = useFloating({
open: showingSuggestions,
placement: 'bottom-start',
placement,
onOpenChange: setShowingSuggestions,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -264,7 +264,7 @@ describe(`Traces Filters`, () => {
{ property: 'client_Browser', filters: [{ count: 100, value: 'test-client' }], operation: 'ne', index: 1 },
rerender
);
});
}, 10000);
it('should delete a trace filter', async () => {
let mockQuery = createMockQuery({ azureTraces: { traceTypes: ['customEvents'] } });

View File

@ -1,21 +1,12 @@
import { css, cx } from '@emotion/css';
import {
autoUpdate,
flip,
safePolygon,
shift,
useDismiss,
useFloating,
useHover,
useInteractions,
} from '@floating-ui/react';
import { autoUpdate, safePolygon, useDismiss, useFloating, useHover, useInteractions } from '@floating-ui/react';
import { useCallback, useEffect, useState } from 'react';
import * as React from 'react';
import { DataFrame, Field, formattedValueToString, GrafanaTheme2, LinkModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { TimeZone } from '@grafana/schema';
import { Portal, UPlotConfigBuilder, useStyles2 } from '@grafana/ui';
import { floatingUtils, Portal, UPlotConfigBuilder, useStyles2 } from '@grafana/ui';
import { VizTooltipItem } from '@grafana/ui/internal';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { ExemplarTooltip } from 'app/features/visualization/data-hover/ExemplarTooltip';
@ -50,21 +41,14 @@ export const ExemplarMarker = ({
const styles = useStyles2(getExemplarMarkerStyles, maxWidth);
const [isOpen, setIsOpen] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const placement = 'bottom';
// 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 middleware = floatingUtils.getPositioningMiddleware(placement);
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement: 'bottom',
placement,
onOpenChange: setIsOpen,
middleware,
whileElementsMounted: autoUpdate,

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { flip, shift, autoUpdate } from '@floating-ui/dom';
import { autoUpdate } from '@floating-ui/dom';
import { useFloating } from '@floating-ui/react';
import { useState } from 'react';
import * as React from 'react';
@ -8,7 +8,7 @@ import { createPortal } from 'react-dom';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { TimeZone } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui';
import { floatingUtils, useStyles2 } from '@grafana/ui';
import { AnnotationEditor2 } from './AnnotationEditor2';
import { AnnotationTooltip2 } from './AnnotationTooltip2';
@ -37,20 +37,13 @@ export const AnnotationMarker2 = ({
portalRoot,
}: AnnoBoxProps) => {
const styles = useStyles2(getStyles);
const placement = 'bottom';
const [state, setState] = useState(exitWipEdit != null ? STATE_EDITING : STATE_DEFAULT);
const { refs, floatingStyles } = useFloating({
open: true,
placement: 'bottom',
middleware: [
flip({
fallbackAxisSideDirection: 'end',
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body,
}),
shift(),
],
placement,
middleware: floatingUtils.getPositioningMiddleware(placement),
whileElementsMounted: autoUpdate,
strategy: 'fixed',
});