From 691115da7aec9fb63a0032555e5565643047989d Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 16 Feb 2024 09:40:16 +0000 Subject: [PATCH] Chore: replace `react-popper` with `@floating-ui/react` in `DataSourcePicker` (#82528) * replace react-popper with floating-ui in DataSourcePicker * don't need {force:true} --- e2e/various-suite/exemplars.spec.ts | 2 +- .../components/picker/DataSourcePicker.tsx | 57 ++++++++++++------- .../components/picker/popperModifiers.ts | 42 -------------- 3 files changed, 39 insertions(+), 62 deletions(-) delete mode 100644 public/app/features/datasources/components/picker/popperModifiers.ts diff --git a/e2e/various-suite/exemplars.spec.ts b/e2e/various-suite/exemplars.spec.ts index 62d1ce43ea8..b83273832a7 100644 --- a/e2e/various-suite/exemplars.spec.ts +++ b/e2e/various-suite/exemplars.spec.ts @@ -10,7 +10,7 @@ const addDataSource = () => { e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click(); e2e.components.DataSource.Prometheus.configPage.internalLinkSwitch().check({ force: true }); e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090'); - e2e.components.DataSourcePicker.inputV2().click({ force: true }).should('have.focus'); + e2e.components.DataSourcePicker.inputV2().click().should('have.focus'); cy.contains('gdev-tempo').scrollIntoView().should('be.visible').click(); }, diff --git a/public/app/features/datasources/components/picker/DataSourcePicker.tsx b/public/app/features/datasources/components/picker/DataSourcePicker.tsx index efc6de67408..0441eeb2ab7 100644 --- a/public/app/features/datasources/components/picker/DataSourcePicker.tsx +++ b/public/app/features/datasources/components/picker/DataSourcePicker.tsx @@ -1,9 +1,9 @@ import { css } from '@emotion/css'; +import { autoUpdate, flip, offset, shift, size, useFloating } from '@floating-ui/react'; import { useDialog } from '@react-aria/dialog'; import { FocusScope } from '@react-aria/focus'; import { useOverlay } from '@react-aria/overlays'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { usePopper } from 'react-popper'; import { Observable } from 'rxjs'; import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; @@ -21,7 +21,6 @@ import { useDatasource } from '../../hooks'; import { DataSourceList } from './DataSourceList'; import { DataSourceLogo, DataSourceLogoPlaceHolder } from './DataSourceLogo'; import { DataSourceModal } from './DataSourceModal'; -import { applyMaxSize, maxSize } from './popperModifiers'; import { dataSourceLabel, matchDataSourceWithSearch } from './utils'; const INTERACTION_EVENT_NAME = 'dashboards_dspicker_clicked'; @@ -81,8 +80,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) { // Used to position the popper correctly and to bring back the focus when navigating from footer to input const [markerElement, setMarkerElement] = useState(); - // Used to position the popper correctly - const [selectorElement, setSelectorElement] = useState(); // Used to move the focus to the footer when tabbing from the input const [footerRef, setFooterRef] = useState(); const currentDataSourceInstanceSettings = useDatasource(current); @@ -91,20 +88,43 @@ export function DataSourcePicker(props: DataSourcePickerProps) { const prefixIcon = filterTerm && isOpen ? : ; - const popper = usePopper(markerElement, selectorElement, { - placement: 'bottom-start', - modifiers: [ - { - name: 'offset', - options: { - offset: [0, 4], - }, + // the order of middleware is important! + const middleware = [ + offset(4), + size({ + apply({ availableHeight, elements }) { + const margin = 20; + const minSize = 200; + elements.floating.style.maxHeight = `${Math.max(minSize, availableHeight - margin)}px`; + elements.floating.style.minHeight = `${minSize}px`; }, - maxSize, - applyMaxSize, - ], + }), + flip({ + fallbackStrategy: 'initialPlacement', + // see https://floating-ui.com/docs/flip#combining-with-shift + crossAxis: false, + boundary: document.body, + }), + shift(), + ]; + + const { refs, floatingStyles } = useFloating({ + open: isOpen, + placement: 'bottom-start', + onOpenChange: setOpen, + middleware, + whileElementsMounted: autoUpdate, + strategy: 'fixed', }); + const handleReference = useCallback( + (element: HTMLInputElement | null) => { + refs.setReference(element); + setMarkerElement(element); + }, + [refs] + ); + const onClose = useCallback(() => { setFilterTerm(''); setOpen(false); @@ -215,7 +235,7 @@ export function DataSourcePicker(props: DataSourcePickerProps) { openDropdown(); setFilterTerm(e.currentTarget.value); }} - ref={setMarkerElement} + ref={handleReference} disabled={disabled} > @@ -225,9 +245,8 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
= { - name: 'maxSize', - enabled: true, - phase: 'main', - requires: ['offset', 'preventOverflow', 'flip'], - fn({ state, name, options }: ModifierArguments<{}>) { - const overflow = detectOverflow(state, options); - const { x, y } = state.modifiersData.preventOverflow || { x: 0, y: 0 }; - const { width: contentW, height: contentH } = state.rects.popper; - const { width: triggerW } = state.rects.reference; - const [basePlacement] = state.placement.split('-'); - - const widthProp = basePlacement === 'left' ? 'left' : 'right'; - const heightProp = basePlacement === 'top' ? 'top' : 'bottom'; - - state.modifiersData[name] = { - maxWidth: contentW - overflow[widthProp] - x, - maxHeight: contentH - overflow[heightProp] - y, - minWidth: triggerW, - }; - }, -}; - -export const applyMaxSize: Modifier<'applyMaxSize', {}> = { - name: 'applyMaxSize', - enabled: true, - phase: 'beforeWrite', - requires: ['maxSize'], - fn({ state }: ModifierArguments<{}>) { - const { maxHeight, maxWidth, minWidth } = state.modifiersData.maxSize; - - state.styles.popper.maxHeight ??= `${maxHeight - MODAL_MARGIN}px`; - state.styles.popper.minHeight ??= `${FLIP_THRESHOLD}px`; - state.styles.popper.maxWidth ??= maxWidth; - state.styles.popper.minWidth ??= minWidth; - }, -};