Skip to content

Commit ba6687d

Browse files
feat(web): search type selection dropdown (#24091)
* feat(web): search type selection dropdown * chore: implement suggestions * lint --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
1 parent bbba1bf commit ba6687d

File tree

1 file changed

+68
-9
lines changed

1 file changed

+68
-9
lines changed

web/src/lib/components/shared-components/search-bar/search-bar.svelte

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import { generateId } from '$lib/utils/generate-id';
1010
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
1111
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
12-
import { IconButton, modalManager } from '@immich/ui';
12+
import { Button, IconButton, modalManager } from '@immich/ui';
1313
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
14-
import { onDestroy, tick } from 'svelte';
14+
import { onDestroy, onMount, tick } from 'svelte';
1515
import { t } from 'svelte-i18n';
1616
import SearchHistoryBox from './search-history-box.svelte';
1717
@@ -31,6 +31,8 @@
3131
let isSearchSuggestions = $state(false);
3232
let selectedId: string | undefined = $state();
3333
let close: (() => Promise<void>) | undefined;
34+
let showSearchTypeDropdown = $state(false);
35+
let currentSearchType = $state('smart');
3436
3537
const listboxId = generateId();
3638
const searchTypeId = generateId();
@@ -70,6 +72,7 @@
7072
7173
const onFocusIn = () => {
7274
searchStore.isSearchEnabled = true;
75+
getSearchType();
7376
};
7477
7578
const onFocusOut = () => {
@@ -98,6 +101,9 @@
98101
const searchResult = await result.onClose;
99102
close = undefined;
100103
104+
// Refresh search type after modal closes
105+
getSearchType();
106+
101107
if (!searchResult) {
102108
return;
103109
}
@@ -139,6 +145,7 @@
139145
140146
const onEscape = () => {
141147
closeDropdown();
148+
closeSearchTypeDropdown();
142149
};
143150
144151
const onArrow = async (direction: 1 | -1) => {
@@ -168,6 +175,20 @@
168175
searchHistoryBox?.clearSelection();
169176
};
170177
178+
const toggleSearchTypeDropdown = () => {
179+
showSearchTypeDropdown = !showSearchTypeDropdown;
180+
};
181+
182+
const closeSearchTypeDropdown = () => {
183+
showSearchTypeDropdown = false;
184+
};
185+
186+
const selectSearchType = (type: string) => {
187+
localStorage.setItem('searchQueryType', type);
188+
currentSearchType = type;
189+
showSearchTypeDropdown = false;
190+
};
191+
171192
const onsubmit = (event: Event) => {
172193
event.preventDefault();
173194
onSubmit();
@@ -180,17 +201,18 @@
180201
case 'metadata':
181202
case 'description':
182203
case 'ocr': {
204+
currentSearchType = searchType;
183205
return searchType;
184206
}
185207
default: {
208+
currentSearchType = 'smart';
186209
return 'smart';
187210
}
188211
}
189212
}
190213
191214
function getSearchTypeText(): string {
192-
const searchType = getSearchType();
193-
switch (searchType) {
215+
switch (currentSearchType) {
194216
case 'smart': {
195217
return $t('context');
196218
}
@@ -203,8 +225,22 @@
203225
case 'ocr': {
204226
return $t('ocr');
205227
}
228+
default: {
229+
return $t('context');
230+
}
206231
}
207232
}
233+
234+
onMount(() => {
235+
getSearchType();
236+
});
237+
238+
const searchTypes = [
239+
{ value: 'smart', label: () => $t('context') },
240+
{ value: 'metadata', label: () => $t('filename') },
241+
{ value: 'description', label: () => $t('description') },
242+
{ value: 'ocr', label: () => $t('ocr') },
243+
] as const;
208244
</script>
209245

210246
<svelte:document
@@ -293,11 +329,34 @@
293329
class:max-md:hidden={value}
294330
class:end-28={value.length > 0}
295331
>
296-
<p
297-
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs"
298-
>
299-
{getSearchTypeText()}
300-
</p>
332+
<div class="relative">
333+
<Button
334+
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs hover:opacity-80 transition-opacity cursor-pointer"
335+
onclick={toggleSearchTypeDropdown}
336+
aria-expanded={showSearchTypeDropdown}
337+
aria-haspopup="listbox"
338+
>
339+
{getSearchTypeText()}
340+
</Button>
341+
342+
{#if showSearchTypeDropdown}
343+
<div
344+
class="absolute top-full right-0 mt-1 bg-white dark:bg-immich-dark-gray border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 min-w-32 z-9999"
345+
use:focusOutside={{ onFocusOut: closeSearchTypeDropdown }}
346+
>
347+
{#each searchTypes as searchType (searchType.value)}
348+
<button
349+
type="button"
350+
class="w-full text-left px-3 py-2 text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors
351+
{currentSearchType === searchType.value ? 'bg-gray-100 dark:bg-gray-700' : ''}"
352+
onclick={() => selectSearchType(searchType.value)}
353+
>
354+
{searchType.label()}
355+
</button>
356+
{/each}
357+
</div>
358+
{/if}
359+
</div>
301360
</div>
302361
{/if}
303362

0 commit comments

Comments
 (0)