Skip to content

Commit 903c7f5

Browse files
rickbijkerkRick Bijkerkn1ru4l
authored
feat: improve type search sorting with relevance-based ordering (#7540)
Co-authored-by: Rick Bijkerk <rickbijkerk@bol.com> Co-authored-by: Laurin Quast <laurinquast@googlemail.com>
1 parent d42732b commit 903c7f5

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

.changeset/cold-jars-hear.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'hive': minor
3+
---
4+
5+
Improve type sorting within the schema explorer. The changes are now sorted by relevance.
6+
7+
- Exact matches appear first (e.g., `Product` when searching `product`)
8+
- Prefix matches appear second (e.g., `ProductInfo` when searching `prod`)
9+
- Contains matches appear last, sorted alphabetically

packages/web/app/src/components/target/explorer/filter.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ChangeEvent, useCallback, useMemo } from 'react';
1+
import React, { ChangeEvent, useCallback, useDeferredValue, useMemo, useState } from 'react';
22
import { FilterIcon } from 'lucide-react';
33
import { useQuery } from 'urql';
44
import { Button } from '@/components/ui/button';
@@ -94,6 +94,8 @@ export function TypeFilter(props: {
9494
};
9595
}) {
9696
const router = useRouter();
97+
const [inputValue, setInputValue] = useState('');
98+
const deferredInputValue = useDeferredValue(inputValue);
9799
const [query] = useQuery({
98100
query: TypeFilter_AllTypes,
99101
variables: {
@@ -115,6 +117,29 @@ export function TypeFilter(props: {
115117
[allNamedTypes],
116118
);
117119

120+
const sortedTypes = useMemo(() => {
121+
if (!deferredInputValue) return types;
122+
123+
const search = deferredInputValue.toLowerCase();
124+
return [...types].sort((a, b) => {
125+
const aName = a.label.toLowerCase();
126+
const bName = b.label.toLowerCase();
127+
128+
// Exact match gets highest priority
129+
const aExact = aName === search;
130+
const bExact = bName === search;
131+
if (aExact !== bExact) return aExact ? -1 : 1;
132+
133+
// Prefix match gets second priority
134+
const aPrefix = aName.startsWith(search);
135+
const bPrefix = bName.startsWith(search);
136+
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
137+
138+
// Alphabetical within same relevance
139+
return aName.localeCompare(bName);
140+
});
141+
}, [types, deferredInputValue]);
142+
118143
const onChange = useCallback(
119144
(option: SelectOption | null) => {
120145
void router.navigate({
@@ -140,8 +165,9 @@ export function TypeFilter(props: {
140165
className="min-w-[200px] grow cursor-text"
141166
placeholder="Search for a type"
142167
defaultValue={defaultValue}
143-
options={types}
168+
options={sortedTypes}
144169
onChange={onChange}
170+
onInputChange={setInputValue}
145171
loading={query.fetching}
146172
/>
147173
);

packages/web/app/src/components/v2/autocomplete.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export function Autocomplete(props: {
108108
disabled?: boolean;
109109
loading?: boolean;
110110
className?: string;
111+
onInputChange?: (value: string) => void;
111112
}): ReactElement {
112113
return (
113114
<Select
@@ -125,6 +126,11 @@ export function Autocomplete(props: {
125126
isClearable
126127
closeMenuOnSelect
127128
onChange={option => props.onChange(option as SelectOption)}
129+
onInputChange={(value, { action }) => {
130+
if (action === 'input-change') {
131+
props.onInputChange?.(value);
132+
}
133+
}}
128134
isDisabled={props.disabled}
129135
isLoading={props.loading}
130136
placeholder={props.placeholder}

0 commit comments

Comments
 (0)