Chore: Remove react-popper from DataLinkInput and SuggestionsInput (#82160)

* replace popper with floating-ui in DataLinkInput

* replace popper with floating-ui in SuggestionsInput
This commit is contained in:
Ashley Harrison
2024-02-12 13:22:24 +00:00
committed by GitHub
parent 71e7d654de
commit 685e84b1f8
2 changed files with 94 additions and 99 deletions
@@ -1,7 +1,7 @@
import { css, cx } from '@emotion/css';
import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/react';
import Prism, { Grammar, LanguageMap } from 'prismjs';
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import { Popper as ReactPopper } from 'react-popper';
import React, { memo, useEffect, useRef, useState } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import { Value } from 'slate';
import Plain from 'slate-plain-serializer';
@@ -86,6 +86,29 @@ export const DataLinkInput = memo(
const prevLinkUrl = usePrevious<Value>(linkUrl);
const [scrollTop, setScrollTop] = useState(0);
// the order of middleware is important!
const middleware = [
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(),
];
const { refs, floatingStyles } = useFloating({
open: showingSuggestions,
placement: 'bottom-start',
onOpenChange: setShowingSuggestions,
middleware,
whileElementsMounted: autoUpdate,
strategy: 'fixed',
});
// Workaround for https://github.com/ianstormtaylor/slate/issues/2927
const stateRef = useRef({ showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange });
stateRef.current = { showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange };
@@ -96,12 +119,11 @@ export const DataLinkInput = memo(
setScrollTop(getElementPosition(activeRef.current, suggestionsIndex));
}, [suggestionsIndex]);
// SelectionReference is used to position the variables suggestion relatively to current DOM selection
const selectionRef = useMemo(() => new SelectionReference(), []);
const onKeyDown = React.useCallback((event: React.KeyboardEvent, next: () => void) => {
if (!stateRef.current.showingSuggestions) {
if (event.key === '=' || event.key === '$' || (event.keyCode === 32 && event.ctrlKey)) {
const selectionRef = new SelectionReference();
refs.setReference(selectionRef);
return setShowingSuggestions(true);
}
return next();
@@ -181,49 +203,21 @@ export const DataLinkInput = memo(
<div id="data-link-input" className="slate-query-field">
{showingSuggestions && (
<Portal>
<ReactPopper
referenceElement={selectionRef}
placement="bottom-end"
modifiers={[
{
name: 'preventOverflow',
enabled: true,
options: {
rootBoundary: 'viewport',
},
},
{
name: 'arrow',
enabled: false,
},
{
name: 'offset',
options: {
offset: [250, 0],
},
},
]}
>
{({ ref, style, placement }) => {
return (
<div ref={ref} style={style} data-placement={placement} className={styles.suggestionsWrapper}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
>
<DataLinkSuggestions
activeRef={activeRef}
suggestions={stateRef.current.suggestions}
onSuggestionSelect={onVariableSelect}
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</div>
);
}}
</ReactPopper>
<div ref={refs.setFloating} style={floatingStyles}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
>
<DataLinkSuggestions
activeRef={activeRef}
suggestions={stateRef.current.suggestions}
onSuggestionSelect={onVariableSelect}
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</div>
</Portal>
)}
<Editor
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import React, { FormEvent, useEffect, useRef, useState } from 'react';
import { Popper as ReactPopper } from 'react-popper';
import { autoUpdate, flip, shift, useFloating } from '@floating-ui/react';
import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import { GrafanaTheme2, VariableSuggestion } from '@grafana/data';
import { CustomScrollbar, FieldValidationMessage, Input, Portal, useTheme2 } from '@grafana/ui';
@@ -55,7 +55,36 @@ export const SuggestionsInput = ({
const theme = useTheme2();
const styles = getStyles(theme, inputHeight);
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<HTMLInputElement>();
// 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 { refs, floatingStyles } = useFloating({
open: showingSuggestions,
placement: 'bottom-start',
onOpenChange: setShowingSuggestions,
middleware,
whileElementsMounted: autoUpdate,
strategy: 'fixed',
});
const handleRef = useCallback(
(ref: HTMLInputElement) => {
refs.setReference(ref);
inputRef.current = ref;
},
[refs]
);
// Used to get the height of the suggestion elements in order to scroll to them.
const activeRef = useRef<HTMLDivElement>(null);
@@ -137,53 +166,25 @@ export const SuggestionsInput = ({
<div className={styles.inputWrapper}>
{showingSuggestions && (
<Portal>
<ReactPopper
referenceElement={inputRef.current!}
placement="bottom-start"
modifiers={[
{
name: 'preventOverflow',
enabled: true,
options: {
rootBoundary: 'viewport',
},
},
{
name: 'arrow',
enabled: false,
},
{
name: 'offset',
options: {
offset: [0, 0],
},
},
]}
>
{({ ref, style, placement }) => {
return (
<div ref={ref} style={style} data-placement={placement} className={styles.suggestionsWrapper}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
>
{/* This suggestion component has a specialized name,
but is rather generalistic in implementation,
so we're using it in transformations also.
We should probably rename this to something more general. */}
<DataLinkSuggestions
activeRef={activeRef}
suggestions={suggestions}
onSuggestionSelect={onVariableSelect}
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</div>
);
}}
</ReactPopper>
<div ref={refs.setFloating} style={floatingStyles} className={styles.suggestionsWrapper}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
>
{/* This suggestion component has a specialized name,
but is rather generalistic in implementation,
so we're using it in transformations also.
We should probably rename this to something more general. */}
<DataLinkSuggestions
activeRef={activeRef}
suggestions={suggestions}
onSuggestionSelect={onVariableSelect}
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</div>
</Portal>
)}
{invalid && error && (
@@ -194,7 +195,7 @@ export const SuggestionsInput = ({
<Input
placeholder={placeholder}
invalid={invalid}
ref={inputRef}
ref={handleRef}
value={variableValue}
onChange={onValueChanged}
onBlur={onBlur}