diff --git a/packages/web/app/src/components/target/explorer/filter.tsx b/packages/web/app/src/components/target/explorer/filter.tsx index f4d8d3efb60..9aa4c84927e 100644 --- a/packages/web/app/src/components/target/explorer/filter.tsx +++ b/packages/web/app/src/components/target/explorer/filter.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, useCallback, useMemo } from 'react'; +import React, { ChangeEvent, useCallback, useMemo, useState, useDeferredValue } from 'react'; import { FilterIcon } from 'lucide-react'; import { useQuery } from 'urql'; import { Button } from '@/components/ui/button'; @@ -94,6 +94,8 @@ export function TypeFilter(props: { }; }) { const router = useRouter(); + const [inputValue, setInputValue] = useState(''); + const deferredInputValue = useDeferredValue(inputValue); const [query] = useQuery({ query: TypeFilter_AllTypes, variables: { @@ -115,6 +117,29 @@ export function TypeFilter(props: { [allNamedTypes], ); + const sortedTypes = useMemo(() => { + if (!deferredInputValue) return types; + + const search = deferredInputValue.toLowerCase(); + return [...types].sort((a, b) => { + const aName = a.label.toLowerCase(); + const bName = b.label.toLowerCase(); + + // Exact match gets highest priority + const aExact = aName === search; + const bExact = bName === search; + if (aExact !== bExact) return aExact ? -1 : 1; + + // Prefix match gets second priority + const aPrefix = aName.startsWith(search); + const bPrefix = bName.startsWith(search); + if (aPrefix !== bPrefix) return aPrefix ? -1 : 1; + + // Alphabetical within same relevance + return aName.localeCompare(bName); + }); + }, [types, deferredInputValue]); + const onChange = useCallback( (option: SelectOption | null) => { void router.navigate({ @@ -140,8 +165,9 @@ export function TypeFilter(props: { className="min-w-[200px] grow cursor-text" placeholder="Search for a type" defaultValue={defaultValue} - options={types} + options={sortedTypes} onChange={onChange} + onInputChange={setInputValue} loading={query.fetching} /> ); diff --git a/packages/web/app/src/components/v2/autocomplete.tsx b/packages/web/app/src/components/v2/autocomplete.tsx index c5c0a3924a3..8bd912c2424 100644 --- a/packages/web/app/src/components/v2/autocomplete.tsx +++ b/packages/web/app/src/components/v2/autocomplete.tsx @@ -108,6 +108,7 @@ export function Autocomplete(props: { disabled?: boolean; loading?: boolean; className?: string; + onInputChange?: (value: string) => void; }): ReactElement { return (