From 6dc47755ec239983a4d8fa56b3de9e34b4247a4a Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 10 Mar 2025 16:41:06 -0700 Subject: [PATCH] Use floating UI for ImportSeriesSelectSeries --- .../SelectSeries/ImportSeriesSelectSeries.tsx | 301 ++++++++---------- 1 file changed, 128 insertions(+), 173 deletions(-) diff --git a/frontend/src/AddSeries/ImportSeries/Import/SelectSeries/ImportSeriesSelectSeries.tsx b/frontend/src/AddSeries/ImportSeries/Import/SelectSeries/ImportSeriesSelectSeries.tsx index 31dfc58eb..ffcaa1bf7 100644 --- a/frontend/src/AddSeries/ImportSeries/Import/SelectSeries/ImportSeriesSelectSeries.tsx +++ b/frontend/src/AddSeries/ImportSeries/Import/SelectSeries/ImportSeriesSelectSeries.tsx @@ -1,5 +1,13 @@ -import React, { useCallback, useEffect, useId, useRef, useState } from 'react'; -import { Manager, Popper, Reference } from 'react-popper'; +import { + autoUpdate, + flip, + FloatingPortal, + useClick, + useDismiss, + useFloating, + useInteractions, +} from '@floating-ui/react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import AppState from 'App/State/AppState'; import FormInputButton from 'Components/Form/FormInputButton'; @@ -7,7 +15,6 @@ import TextInput from 'Components/Form/TextInput'; import Icon from 'Components/Icon'; import Link from 'Components/Link/Link'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import Portal from 'Components/Portal'; import { icons, kinds } from 'Helpers/Props'; import { queueLookupSeries, @@ -47,9 +54,6 @@ function ImportSeriesSelectSeries({ // @ts-expect-error - ignoring this for now } = useSelector(createImportSeriesItemSelector(id, { id })); - const buttonId = useId(); - const contentId = useId(); - const updater = useRef<(() => void) | null>(null); const seriesLookupTimeout = useRef>(); const [term, setTerm] = useState(''); @@ -57,37 +61,6 @@ function ImportSeriesSelectSeries({ const errorMessage = getErrorMessage(error); - const handleWindowClick = useCallback( - (event: MouseEvent) => { - const button = document.getElementById(buttonId); - const content = document.getElementById(contentId); - const eventTarget = event.target as HTMLElement; - - if (!button || !eventTarget.isConnected) { - return; - } - - if ( - !button.contains(eventTarget) && - content && - !content.contains(eventTarget) && - isOpen - ) { - setIsOpen(false); - window.removeEventListener('click', handleWindowClick); - } - }, - [isOpen, buttonId, contentId, setIsOpen] - ); - - const addListener = useCallback(() => { - window.addEventListener('click', handleWindowClick); - }, [handleWindowClick]); - - const removeListener = useCallback(() => { - window.removeEventListener('click', handleWindowClick); - }, [handleWindowClick]); - const handlePress = useCallback(() => { setIsOpen((prevIsOpen) => !prevIsOpen); }, []); @@ -147,157 +120,139 @@ function ImportSeriesSelectSeries({ [id, items, dispatch, onInputChange] ); - useEffect(() => { - if (updater.current) { - updater.current(); - } - }); - - useEffect(() => { - if (isOpen) { - addListener(); - } else { - removeListener(); - } - - return removeListener; - }, [isOpen, addListener, removeListener]); - useEffect(() => { setTerm(itemTerm); }, [itemTerm]); + const { refs, context, floatingStyles } = useFloating({ + middleware: [ + flip({ + crossAxis: false, + mainAxis: true, + }), + ], + open: isOpen, + placement: 'bottom', + whileElementsMounted: autoUpdate, + onOpenChange: setIsOpen, + }); + + const click = useClick(context); + const dismiss = useDismiss(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + click, + dismiss, + ]); + return ( - - - {({ ref }) => ( -
- - {isLookingUpSeries && isQueued && !isPopulated ? ( - - ) : null} + <> +
+ + {isLookingUpSeries && isQueued && !isPopulated ? ( + + ) : null} - {isPopulated && selectedSeries && isExistingSeries ? ( - - ) : null} + {isPopulated && selectedSeries && isExistingSeries ? ( + + ) : null} - {isPopulated && selectedSeries ? ( - - ) : null} + {isPopulated && selectedSeries ? ( + + ) : null} - {isPopulated && !selectedSeries ? ( -
- + {isPopulated && !selectedSeries ? ( +
+ - {translate('NoMatchFound')} -
- ) : null} + {translate('NoMatchFound')} +
+ ) : null} - {!isFetching && !!error ? ( -
- + {!isFetching && !!error ? ( +
+ - {translate('SearchFailedError')} -
- ) : null} + {translate('SearchFailedError')} +
+ ) : null} -
- -
- +
+
- )} - - - - - {({ ref, style, scheduleUpdate }) => { - updater.current = scheduleUpdate; - - return ( -
- {isOpen ? ( -
-
-
- -
- - - - - - -
- -
- {items.map((item) => { - return ( - - ); - })} -
+ +
+ {isOpen ? ( + +
+ {isOpen ? ( +
+
+
+
- ) : null} + + + + + + +
+ +
+ {items.map((item) => { + return ( + + ); + })} +
- ); - }} - - - + ) : null} +
+
+ ) : null} + ); }