Chore: replace react-popper with @floating-ui/react in DataSourcePicker (#82528)

* replace react-popper with floating-ui in DataSourcePicker

* don't need {force:true}
This commit is contained in:
Ashley Harrison 2024-02-16 09:40:16 +00:00 committed by GitHub
parent 94f544c9f6
commit 691115da7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 62 deletions

View File

@ -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();
},

View File

@ -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<HTMLInputElement | null>();
// Used to position the popper correctly
const [selectorElement, setSelectorElement] = useState<HTMLDivElement | null>();
// Used to move the focus to the footer when tabbing from the input
const [footerRef, setFooterRef] = useState<HTMLElement | null>();
const currentDataSourceInstanceSettings = useDatasource(current);
@ -91,20 +88,43 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
const prefixIcon =
filterTerm && isOpen ? <DataSourceLogoPlaceHolder /> : <DataSourceLogo dataSource={currentValue} />;
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}
></Input>
</div>
@ -225,9 +245,8 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
<div ref={ref} {...overlayProps} {...dialogProps}>
<PickerContent
{...restProps}
{...popper.attributes.popper}
style={popper.styles.popper}
ref={setSelectorElement}
style={floatingStyles}
ref={refs.setFloating}
footerRef={setFooterRef}
current={currentValue}
filterTerm={filterTerm}

View File

@ -1,42 +0,0 @@
import { detectOverflow, Modifier, ModifierArguments } from '@popperjs/core';
const MODAL_MARGIN = 20;
const FLIP_THRESHOLD = 200;
export const maxSize: Modifier<'maxSize', {}> = {
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;
},
};