diff --git a/src/components/app/form/input/search.tsx b/src/components/app/form/input/search.tsx index 3daab4cc..538e3905 100644 --- a/src/components/app/form/input/search.tsx +++ b/src/components/app/form/input/search.tsx @@ -1,6 +1,6 @@ import { msg } from "@lingui/macro"; import { useLingui } from "@lingui/react"; -import { useState } from "react"; +import { useState, useRef, useEffect } from "react"; import { Loader, Search } from "react-feather"; import { Input } from "@/components/ui/input"; @@ -9,12 +9,24 @@ interface IProps { onChange: (value: string) => void; loading?: boolean; initialValue?: string; + shouldAutoFocus?: boolean; } -export function FormSearch({ onChange, loading, initialValue }: IProps) { +export function FormSearch({ + onChange, + loading, + initialValue, + shouldAutoFocus, +}: IProps) { const { _ } = useLingui(); - const [value, setValue] = useState(initialValue || ""); + const inputRef = useRef(null); + + useEffect(() => { + if (shouldAutoFocus && inputRef.current) { + inputRef.current.focus(); + } + }, [shouldAutoFocus, value]); return (
@@ -37,6 +49,7 @@ export function FormSearch({ onChange, loading, initialValue }: IProps) { setValue(e.target.value); }} placeholder={_(msg`Search`)} + ref={inputRef} />
); diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index d164a024..95c21c19 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -176,24 +176,60 @@ export interface ISelectProps { }; } +const LOCAL_SEARCH_THRESHOLD = 10; + export function Select({ onChange, - options, + options: fullOptions, disabled, isLoading, value, name, placeholder, className, - onSearch, + onSearch: onSearchForAsync, disabledOptions, }: ISelectProps) { const { _ } = useLingui(); - const valueLabel = options.find( + const valueLabel = fullOptions.find( (option) => String(option.value) === String(value) )?.label; + const [searchString, setSearchString] = React.useState(""); + + const onSearch = React.useMemo(() => { + if (onSearchForAsync) { + return onSearchForAsync; + } + if (fullOptions.length <= LOCAL_SEARCH_THRESHOLD) { + return undefined; + } + + return { + isLoading: false, + value: searchString, + onChange: setSearchString, + }; + }, [fullOptions, searchString]); + + const optionsToRender = React.useMemo(() => { + if (!onSearch) { + return fullOptions; + } + + if (!searchString) { + return fullOptions; + } + + const searchStringInLower = searchString.toLowerCase(); + + return fullOptions.filter(({ label }) => + _(label).toLocaleLowerCase().includes(searchStringInLower) + ); + }, [fullOptions, searchString]); + const [isOpen, setIsOpen] = React.useState(false); + return ( { @@ -202,6 +238,7 @@ export function Select({ } }} value={value} + onOpenChange={setIsOpen} > )} - {onSearch?.value && options.length === 0 && !onSearch?.isLoading && ( - - )} - {options.length === 0 && !onSearch && ( + {onSearch?.value && + optionsToRender.length === 0 && + !onSearch?.isLoading && ( + + )} + {fullOptions.length === 0 && !onSearch && ( )} {onSearch?.isLoading && } - {options.map(({ value: value$1, label }) => ( + {optionsToRender.map(({ value: value$1, label }) => (