1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-19 21:46:43 -04:00

Use floating UI for AutoSuggestInput

This commit is contained in:
Mark McDowall
2025-03-09 21:12:49 -07:00
parent e9f59188b1
commit 01f7783519
@@ -1,3 +1,4 @@
import { autoUpdate, flip, size, useFloating } from '@floating-ui/react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { import React, {
FocusEvent, FocusEvent,
@@ -19,8 +20,6 @@ import Autosuggest, {
RenderInputComponentProps, RenderInputComponentProps,
RenderSuggestionsContainerParams, RenderSuggestionsContainerParams,
} from 'react-autosuggest'; } from 'react-autosuggest';
import { Manager, Popper, Reference } from 'react-popper';
import Portal from 'Components/Portal';
import usePrevious from 'Helpers/Hooks/usePrevious'; import usePrevious from 'Helpers/Hooks/usePrevious';
import { InputChanged } from 'typings/inputs'; import { InputChanged } from 'typings/inputs';
import styles from './AutoSuggestInput.css'; import styles from './AutoSuggestInput.css';
@@ -37,7 +36,6 @@ interface AutoSuggestInputProps<T>
hasError?: boolean; hasError?: boolean;
hasWarning?: boolean; hasWarning?: boolean;
enforceMaxHeight?: boolean; enforceMaxHeight?: boolean;
minHeight?: number;
maxHeight?: number; maxHeight?: number;
renderInputComponent?: ( renderInputComponent?: (
inputProps: RenderInputComponentProps, inputProps: RenderInputComponentProps,
@@ -70,7 +68,6 @@ function AutoSuggestInput<T = any>(props: AutoSuggestInputProps<T>) {
enforceMaxHeight = true, enforceMaxHeight = true,
hasError, hasError,
hasWarning, hasWarning,
minHeight = 50,
maxHeight = 200, maxHeight = 200,
getSuggestionValue, getSuggestionValue,
renderSuggestion, renderSuggestion,
@@ -89,95 +86,59 @@ function AutoSuggestInput<T = any>(props: AutoSuggestInputProps<T>) {
const updater = useRef<(() => void) | null>(null); const updater = useRef<(() => void) | null>(null);
const previousSuggestions = usePrevious(suggestions); const previousSuggestions = usePrevious(suggestions);
const handleComputeMaxHeight = useCallback( const { refs, floatingStyles } = useFloating({
// eslint-disable-next-line @typescript-eslint/no-explicit-any middleware: [
(data: any) => { flip({
const { top, bottom, width } = data.offsets.reference; crossAxis: false,
mainAxis: true,
if (enforceMaxHeight) { }),
data.styles.maxHeight = maxHeight; size({
} else { apply({ rects, elements }) {
const windowHeight = window.innerHeight; Object.assign(elements.floating.style, {
width: `${rects.reference.width}px`,
if (/^botton/.test(data.placement)) { });
data.styles.maxHeight = windowHeight - bottom; },
} else { }),
data.styles.maxHeight = top; ],
} placement: 'bottom-start',
} whileElementsMounted: autoUpdate,
});
data.styles.width = width;
return data;
},
[enforceMaxHeight, maxHeight]
);
const createRenderInputComponent = useCallback( const createRenderInputComponent = useCallback(
(inputProps: RenderInputComponentProps) => { (inputProps: RenderInputComponentProps) => {
return ( if (renderInputComponent) {
<Reference> return renderInputComponent(inputProps, refs.setReference);
{({ ref }) => { }
if (renderInputComponent) {
return renderInputComponent(inputProps, ref);
}
return ( return (
<div ref={ref}> <div ref={refs.setReference}>
<input {...inputProps} /> <input {...inputProps} />
</div> </div>
);
}}
</Reference>
); );
}, },
[renderInputComponent] [refs.setReference, renderInputComponent]
); );
const renderSuggestionsContainer = useCallback( const renderSuggestionsContainer = useCallback(
({ containerProps, children }: RenderSuggestionsContainerParams) => { ({ containerProps, children }: RenderSuggestionsContainerParams) => {
return ( return (
<Portal> <div
<Popper ref={refs.setFloating}
placement="bottom-start" style={floatingStyles}
modifiers={{ className={children ? styles.suggestionsContainerOpen : undefined}
computeMaxHeight: { >
order: 851, <div
enabled: true, {...containerProps}
fn: handleComputeMaxHeight, style={{
}, maxHeight: enforceMaxHeight ? maxHeight : undefined,
flip: {
padding: minHeight,
},
}} }}
> >
{({ ref: popperRef, style, scheduleUpdate }) => { {children}
updater.current = scheduleUpdate; </div>
</div>
return (
<div
ref={popperRef}
style={style}
className={
children ? styles.suggestionsContainerOpen : undefined
}
>
<div
{...containerProps}
style={{
maxHeight: style.maxHeight,
}}
>
{children}
</div>
</div>
);
}}
</Popper>
</Portal>
); );
}, },
[minHeight, handleComputeMaxHeight] [enforceMaxHeight, floatingStyles, maxHeight, refs.setFloating]
); );
const handleInputKeyDown = useCallback( const handleInputKeyDown = useCallback(
@@ -236,23 +197,21 @@ function AutoSuggestInput<T = any>(props: AutoSuggestInputProps<T>) {
}, [suggestions, previousSuggestions]); }, [suggestions, previousSuggestions]);
return ( return (
<Manager> <Autosuggest
<Autosuggest {...otherProps}
{...otherProps} ref={forwardedRef}
ref={forwardedRef} id={name}
id={name} inputProps={inputProps}
inputProps={inputProps} theme={theme}
theme={theme} suggestions={suggestions}
suggestions={suggestions} getSuggestionValue={getSuggestionValue}
getSuggestionValue={getSuggestionValue} renderInputComponent={createRenderInputComponent}
renderInputComponent={createRenderInputComponent} renderSuggestionsContainer={renderSuggestionsContainer}
renderSuggestionsContainer={renderSuggestionsContainer} renderSuggestion={renderSuggestion}
renderSuggestion={renderSuggestion} onSuggestionSelected={onSuggestionSelected}
onSuggestionSelected={onSuggestionSelected} onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsFetchRequested={onSuggestionsFetchRequested} onSuggestionsClearRequested={onSuggestionsClearRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested} />
/>
</Manager>
); );
} }