mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -04:00 
			
		
		
		
	feat(web): search filter form (#6651)
* refactor: search history box * filter box component * start adding forms * styling * combo box * styling * media types * album option * update * Updated * refactor: search history box * filter box component * start adding forms * styling * combo box * styling * media types * album option * update * Updated * Version v1.94.0 * Add people * add select even for combobox * Remove unused data * remove unused code * remove unused code
This commit is contained in:
		
							parent
							
								
									d3404f927c
								
							
						
					
					
						commit
						2d278d9ab8
					
				| @ -4,6 +4,7 @@ | ||||
|     | 'primary' | ||||
|     | 'secondary' | ||||
|     | 'transparent-primary' | ||||
|     | 'text-primary' | ||||
|     | 'light-red' | ||||
|     | 'red' | ||||
|     | 'green' | ||||
| @ -36,6 +37,8 @@ | ||||
|       'bg-gray-500 dark:bg-gray-200 text-white dark:text-immich-dark-gray enabled:hover:bg-gray-500/90 enabled:dark:hover:bg-gray-200/90', | ||||
|     'transparent-primary': | ||||
|       'text-gray-500 dark:text-immich-dark-primary enabled:hover:bg-gray-100 enabled:dark:hover:bg-gray-700', | ||||
|     'text-primary': | ||||
|       'text-immich-primary dark:text-immich-dark-primary enabled:dark:hover:bg-immich-dark-primary/10 enabled:hover:bg-immich-primary/10', | ||||
|     'light-red': 'bg-[#F9DEDC] text-[#410E0B] enabled:hover:bg-red-50', | ||||
|     red: 'bg-red-500 text-white enabled:hover:bg-red-400', | ||||
|     green: 'bg-green-500 text-gray-800 enabled:hover:bg-green-400/90', | ||||
|  | ||||
| @ -85,7 +85,7 @@ | ||||
|       <div class="flex flex-col"> | ||||
|         <label for="datetime">Date and Time</label> | ||||
|         <input | ||||
|           class="text-sm my-4 w-full bg-gray-200 p-4 rounded-lg dark:text-white dark:bg-gray-600" | ||||
|           class="immich-form-input text-sm my-4 w-full" | ||||
|           id="datetime" | ||||
|           type="datetime-local" | ||||
|           bind:value={selectedDate} | ||||
|  | ||||
| @ -2,27 +2,37 @@ | ||||
|   // Necessary for eslint | ||||
|   /* eslint-disable @typescript-eslint/no-explicit-any */ | ||||
|   type T = any; | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts" generics="T"> | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { clickOutside } from '$lib/utils/click-outside'; | ||||
|   import { mdiMagnify, mdiUnfoldMoreHorizontal } from '@mdi/js'; | ||||
| 
 | ||||
|   type ComboBoxOption = { | ||||
|   export type Type = 'button' | 'submit' | 'reset'; | ||||
|   export type ComboBoxOption = { | ||||
|     label: string; | ||||
|     value: T; | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|   import { fly } from 'svelte/transition'; | ||||
| 
 | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { clickOutside } from '$lib/utils/click-outside'; | ||||
|   import { mdiMagnify, mdiUnfoldMoreHorizontal } from '@mdi/js'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
| 
 | ||||
|   export let type: Type = 'button'; | ||||
|   export let options: ComboBoxOption[] = []; | ||||
|   export let selectedOption: ComboBoxOption; | ||||
|   export let selectedOption: ComboBoxOption | undefined = undefined; | ||||
|   export let placeholder = ''; | ||||
|   export const label = ''; | ||||
| 
 | ||||
|   let isOpen = false; | ||||
|   let searchQuery = ''; | ||||
| 
 | ||||
|   $: filteredOptions = options.filter((option) => option.label.toLowerCase().includes(searchQuery.toLowerCase())); | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ | ||||
|     select: ComboBoxOption; | ||||
|   }>(); | ||||
| 
 | ||||
|   let handleClick = () => { | ||||
|     searchQuery = ''; | ||||
|     isOpen = !isOpen; | ||||
| @ -35,15 +45,14 @@ | ||||
| 
 | ||||
|   let handleSelect = (option: ComboBoxOption) => { | ||||
|     selectedOption = option; | ||||
|     dispatch('select', option); | ||||
|     isOpen = false; | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div class="relative" use:clickOutside on:outclick={handleOutClick}> | ||||
|   <button | ||||
|     class="text-sm text-left w-full bg-gray-200 p-3 rounded-lg dark:text-white dark:bg-gray-600 dark:hover:bg-gray-500 transition-all" | ||||
|     on:click={handleClick} | ||||
|     >{selectedOption.label} | ||||
|   <button {type} class="immich-form-input text-sm text-left w-full min-h-[48px] transition-all" on:click={handleClick} | ||||
|     >{selectedOption?.label} | ||||
|     <div class="absolute right-0 top-0 h-full flex px-4 justify-center items-center content-between"> | ||||
|       <Icon path={mdiUnfoldMoreHorizontal} /> | ||||
|     </div> | ||||
| @ -51,12 +60,13 @@ | ||||
| 
 | ||||
|   {#if isOpen} | ||||
|     <div | ||||
|       class="absolute w-full top-full mt-2 bg-white dark:bg-gray-800 rounded-lg border border-gray-300 dark:border-gray-900" | ||||
|       transition:fly={{ y: 25, duration: 250 }} | ||||
|       class="absolute w-full top-full mt-2 bg-white dark:bg-gray-800 rounded-lg border border-gray-300 dark:border-gray-900 z-10" | ||||
|     > | ||||
|       <div class="relative border-b flex"> | ||||
|         <div class="absolute inset-y-0 left-0 flex items-center pl-3"> | ||||
|           <div class="dark:text-immich-dark-fg/75"> | ||||
|             <button class="flex items-center"> | ||||
|             <button {type} class="flex items-center"> | ||||
|               <Icon path={mdiMagnify} /> | ||||
|             </button> | ||||
|           </div> | ||||
| @ -68,10 +78,11 @@ | ||||
|       <div class="h-64 overflow-y-auto"> | ||||
|         {#each filteredOptions as option (option.label)} | ||||
|           <button | ||||
|             {type} | ||||
|             class="block text-left w-full px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-all | ||||
|              ${option.label === selectedOption.label ? 'bg-gray-300 dark:bg-gray-600' : ''} | ||||
|              ${option.label === selectedOption?.label ? 'bg-gray-300 dark:bg-gray-600' : ''} | ||||
|             " | ||||
|             class:bg-gray-300={option.label === selectedOption.label} | ||||
|             class:bg-gray-300={option.label === selectedOption?.label} | ||||
|             on:click={() => handleSelect(option)} | ||||
|           > | ||||
|             {option.label} | ||||
|  | ||||
| @ -3,15 +3,18 @@ | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { goto } from '$app/navigation'; | ||||
|   import { isSearchEnabled, preventRaceConditionSearchBar, savedSearchTerms } from '$lib/stores/search.store'; | ||||
|   import { fly } from 'svelte/transition'; | ||||
|   import { clickOutside } from '$lib/utils/click-outside'; | ||||
|   import { mdiClose, mdiMagnify } from '@mdi/js'; | ||||
|   import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js'; | ||||
|   import IconButton from '$lib/components/elements/buttons/icon-button.svelte'; | ||||
|   import SearchHistoryBox from './search-history-box.svelte'; | ||||
|   import SearchFilterBox from './search-filter-box.svelte'; | ||||
|   export let value = ''; | ||||
|   export let grayTheme: boolean; | ||||
| 
 | ||||
|   let input: HTMLInputElement; | ||||
| 
 | ||||
|   let showBigSearchBar = false; | ||||
|   let showHistory = false; | ||||
|   let showFilter = false; | ||||
|   $: showClearIcon = value.length > 0; | ||||
| 
 | ||||
|   function onSearch() { | ||||
| @ -31,7 +34,7 @@ | ||||
|       smart: smartSearch, | ||||
|     }); | ||||
| 
 | ||||
|     showBigSearchBar = false; | ||||
|     showHistory = false; | ||||
|     $isSearchEnabled = false; | ||||
|     goto(`${AppRoute.SEARCH}?${parameters}`, { invalidateAll: true }); | ||||
|   } | ||||
| @ -55,7 +58,7 @@ | ||||
|   }; | ||||
| 
 | ||||
|   const onFocusIn = () => { | ||||
|     showBigSearchBar = true; | ||||
|     showHistory = true; | ||||
|     $isSearchEnabled = true; | ||||
|   }; | ||||
| 
 | ||||
| @ -64,7 +67,7 @@ | ||||
|       $preventRaceConditionSearchBar = true; | ||||
|     } | ||||
| 
 | ||||
|     showBigSearchBar = false; | ||||
|     showHistory = false; | ||||
|     $isSearchEnabled = false; | ||||
|   }; | ||||
| </script> | ||||
| @ -91,7 +94,7 @@ | ||||
|         name="q" | ||||
|         class="w-full {grayTheme | ||||
|           ? 'dark:bg-immich-dark-gray' | ||||
|           : 'dark:bg-immich-dark-bg'} px-14 py-4 text-immich-fg/75 dark:text-immich-dark-fg {showBigSearchBar | ||||
|           : 'dark:bg-immich-dark-bg'} px-14 py-4 text-immich-fg/75 dark:text-immich-dark-fg {showHistory || showFilter | ||||
|           ? 'rounded-t-3xl border  border-gray-200 bg-white dark:border-gray-800' | ||||
|           : 'rounded-3xl border border-transparent bg-gray-200'}" | ||||
|         placeholder="Search your photos" | ||||
| @ -100,7 +103,16 @@ | ||||
|         bind:value | ||||
|         bind:this={input} | ||||
|         on:click={onFocusIn} | ||||
|         disabled={showFilter} | ||||
|       /> | ||||
| 
 | ||||
|       <div class="absolute inset-y-0 right-5 flex items-center pl-6"> | ||||
|         <div class="dark:text-immich-dark-fg/75"> | ||||
|           <IconButton on:click={() => (showFilter = !showFilter)} title="Show search options"> | ||||
|             <Icon path={mdiTune} size="1.5em" /> | ||||
|           </IconButton> | ||||
|         </div> | ||||
|       </div> | ||||
|     </label> | ||||
|     {#if showClearIcon} | ||||
|       <div class="absolute inset-y-0 right-0 flex items-center pr-4"> | ||||
| @ -113,58 +125,20 @@ | ||||
|       </div> | ||||
|     {/if} | ||||
| 
 | ||||
|     {#if showBigSearchBar} | ||||
|       <div | ||||
|         transition:fly={{ y: 25, duration: 250 }} | ||||
|         class="absolute w-full rounded-b-3xl border border-gray-200 bg-white pb-5 shadow-2xl transition-all dark:border-gray-800 dark:bg-immich-dark-gray dark:text-gray-300" | ||||
|       > | ||||
|         <div class="flex px-5 pt-5 text-left text-sm"> | ||||
|           <p> | ||||
|             Smart search is enabled by default, to search for metadata use the syntax <span | ||||
|               class="rounded-lg bg-gray-100 p-2 font-mono font-semibold leading-7 text-immich-primary dark:bg-gray-900 dark:text-immich-dark-primary" | ||||
|               >m:your-search-term</span | ||||
|             > | ||||
|           </p> | ||||
|         </div> | ||||
|     <!-- SEARCH HISTORY BOX --> | ||||
|     {#if showHistory} | ||||
|       <SearchHistoryBox | ||||
|         on:clearAllSearchTerms={clearAllSearchTerms} | ||||
|         on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)} | ||||
|         on:selectSearchTerm={({ detail: searchTerm }) => { | ||||
|           value = searchTerm; | ||||
|           onSearch(); | ||||
|         }} | ||||
|       /> | ||||
|     {/if} | ||||
| 
 | ||||
|         {#if $savedSearchTerms.length > 0} | ||||
|           <div class="flex items-center justify-between px-5 pt-5 text-xs"> | ||||
|             <p>RECENT SEARCHES</p> | ||||
|             <div class="flex w-18 items-center justify-center"> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 class="rounded-lg p-2 font-semibold text-immich-primary hover:bg-immich-primary/25 dark:text-immich-dark-primary" | ||||
|                 on:click={clearAllSearchTerms}>Clear all</button | ||||
|               > | ||||
|             </div> | ||||
|           </div> | ||||
|         {/if} | ||||
| 
 | ||||
|         {#each $savedSearchTerms as savedSearchTerm, index (index)} | ||||
|           <div | ||||
|             class="flex w-full items-center justify-between text-sm text-black hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-500/10" | ||||
|           > | ||||
|             <div class="relative w-full items-center"> | ||||
|               <button | ||||
|                 type="button" | ||||
|                 class="relative flex w-full cursor-pointer gap-3 py-3 pl-5" | ||||
|                 on:click={() => { | ||||
|                   value = savedSearchTerm; | ||||
|                   onSearch(); | ||||
|                 }} | ||||
|               > | ||||
|                 <Icon path={mdiMagnify} size="1.5em" /> | ||||
|                 {savedSearchTerm} | ||||
|               </button> | ||||
|               <div class="absolute right-5 top-0 items-center justify-center py-3"> | ||||
|                 <button type="button" on:click={() => clearSearchTerm(savedSearchTerm)} | ||||
|                   ><Icon path={mdiClose} size="18" /></button | ||||
|                 > | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         {/each} | ||||
|       </div> | ||||
|     {#if showFilter} | ||||
|       <SearchFilterBox /> | ||||
|     {/if} | ||||
|   </form> | ||||
| </div> | ||||
|  | ||||
| @ -0,0 +1,198 @@ | ||||
| <script lang="ts"> | ||||
|   import Button from '$lib/components/elements/buttons/button.svelte'; | ||||
|   import { fly } from 'svelte/transition'; | ||||
|   import Combobox, { type ComboBoxOption } from '../combobox.svelte'; | ||||
| 
 | ||||
|   enum MediaType { | ||||
|     All = 'all', | ||||
|     Image = 'image', | ||||
|     Video = 'video', | ||||
|   } | ||||
| 
 | ||||
|   let selectedCountry: ComboBoxOption = { label: '', value: '' }; | ||||
|   let selectedState: ComboBoxOption = { label: '', value: '' }; | ||||
|   let selectedCity: ComboBoxOption = { label: '', value: '' }; | ||||
| 
 | ||||
|   let mediaType: MediaType = MediaType.All; | ||||
|   let notInAlbum = false; | ||||
|   let inArchive = false; | ||||
|   let inFavorite = false; | ||||
| </script> | ||||
| 
 | ||||
| <div | ||||
|   transition:fly={{ y: 25, duration: 250 }} | ||||
|   class="absolute w-full rounded-b-3xl border border-gray-200 bg-white pb-5 shadow-2xl transition-all dark:border-gray-800 dark:bg-immich-dark-gray dark:text-gray-300 p-6" | ||||
| > | ||||
|   <p class="text-xs py-2">FILTERS</p> | ||||
|   <hr class="py-2" /> | ||||
| 
 | ||||
|   <form id="search-filter-form" autocomplete="off"> | ||||
|     <div class="py-3"> | ||||
|       <label class="immich-form-label" for="context">CONTEXT</label> | ||||
|       <input | ||||
|         class="immich-form-input hover:cursor-text w-full mt-3" | ||||
|         type="text" | ||||
|         id="context" | ||||
|         name="context" | ||||
|         placeholder="Sunrise on the beach" | ||||
|       /> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="py-3 grid grid-cols-2"> | ||||
|       <!-- MEDIA TYPE --> | ||||
|       <div id="media-type-selection"> | ||||
|         <p class="immich-form-label">MEDIA TYPE</p> | ||||
| 
 | ||||
|         <div class="flex gap-5 mt-3"> | ||||
|           <label | ||||
|             for="type-all" | ||||
|             class="text-base flex place-items-center gap-1 hover:cursor-pointer text-black dark:text-white" | ||||
|           > | ||||
|             <input | ||||
|               bind:group={mediaType} | ||||
|               value={MediaType.All} | ||||
|               type="radio" | ||||
|               name="radio-type" | ||||
|               id="type-all" | ||||
|             />All</label | ||||
|           > | ||||
| 
 | ||||
|           <label | ||||
|             for="type-image" | ||||
|             class="text-base flex place-items-center gap-1 hover:cursor-pointer text-black dark:text-white" | ||||
|           > | ||||
|             <input | ||||
|               bind:group={mediaType} | ||||
|               value={MediaType.Image} | ||||
|               type="radio" | ||||
|               name="radio-type" | ||||
|               id="type-image" | ||||
|             />Image</label | ||||
|           > | ||||
| 
 | ||||
|           <label | ||||
|             for="type-video" | ||||
|             class="text-base flex place-items-center gap-1 hover:cursor-pointer text-black dark:text-white" | ||||
|           > | ||||
|             <input | ||||
|               bind:group={mediaType} | ||||
|               value={MediaType.Video} | ||||
|               type="radio" | ||||
|               name="radio-type" | ||||
|               id="type-video" | ||||
|             />Video</label | ||||
|           > | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- DISPLAY OPTIONS --> | ||||
|       <div id="display-options-selection"> | ||||
|         <p class="immich-form-label">DISPLAY OPTIONS</p> | ||||
| 
 | ||||
|         <div class="flex gap-5 mt-3"> | ||||
|           <label class="flex items-center mb-2"> | ||||
|             <input type="checkbox" class="form-checkbox h-5 w-5 color" bind:checked={notInAlbum} /> | ||||
|             <span class="ml-2 text-sm text-black dark:text-white pt-1">Not in any album</span> | ||||
|           </label> | ||||
| 
 | ||||
|           <label class="flex items-center mb-2"> | ||||
|             <input type="checkbox" class="form-checkbox h-5 w-5 color" bind:checked={inArchive} /> | ||||
|             <span class="ml-2 text-sm text-black dark:text-white pt-1">Archive</span> | ||||
|           </label> | ||||
| 
 | ||||
|           <label class="flex items-center mb-2"> | ||||
|             <input type="checkbox" class="form-checkbox h-5 w-5 color" bind:checked={inFavorite} /> | ||||
|             <span class="ml-2 text-sm text-black dark:text-white pt-1">Favorite</span> | ||||
|           </label> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <hr /> | ||||
| 
 | ||||
|     <!-- PEOPLE --> | ||||
|     <div id="people-selection" class="my-4"> | ||||
|       <div class="flex justify-between place-items-center gap-6"> | ||||
|         <div class="flex-1"> | ||||
|           <p class="immich-form-label">PEOPLE</p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="flex-1"> | ||||
|           <Combobox options={[]} selectedOption={selectedCountry} placeholder="Search people..." /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <hr /> | ||||
|     <!-- LOCATION --> | ||||
|     <div id="location-selection" class="my-4"> | ||||
|       <p class="immich-form-label">PLACE</p> | ||||
| 
 | ||||
|       <div class="flex justify-between gap-5 mt-3"> | ||||
|         <div class="w-full"> | ||||
|           <p class="text-sm text-black dark:text-white">Country</p> | ||||
|           <Combobox options={[]} selectedOption={selectedCountry} placeholder="Search country..." /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="w-full"> | ||||
|           <p class="text-sm text-black dark:text-white">State</p> | ||||
|           <Combobox options={[]} selectedOption={selectedState} placeholder="Search state..." /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="w-full"> | ||||
|           <p class="text-sm text-black dark:text-white">City</p> | ||||
|           <Combobox options={[]} selectedOption={selectedCity} placeholder="Search city..." /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <hr /> | ||||
|     <!-- CAMERA MODEL --> | ||||
|     <div id="camera-selection" class="my-4"> | ||||
|       <p class="immich-form-label">CAMERA</p> | ||||
| 
 | ||||
|       <div class="flex justify-between gap-5 mt-3"> | ||||
|         <div class="w-full"> | ||||
|           <p class="text-sm text-black dark:text-white">Make</p> | ||||
|           <Combobox options={[]} selectedOption={selectedCountry} placeholder="Search country..." /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="w-full"> | ||||
|           <p class="text-sm text-black dark:text-white">Model</p> | ||||
|           <Combobox options={[]} selectedOption={selectedState} placeholder="Search state..." /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <hr /> | ||||
| 
 | ||||
|     <!-- DATE RANGE --> | ||||
|     <div id="date-range-selection" class="my-4 flex justify-between gap-5"> | ||||
|       <div class="mb-3 flex-1 mt"> | ||||
|         <label class="immich-form-label" for="start-date">START DATE</label> | ||||
|         <input | ||||
|           class="immich-form-input w-full mt-3 hover:cursor-pointer" | ||||
|           type="date" | ||||
|           id="start-date" | ||||
|           name="start-date" | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="mb-3 flex-1"> | ||||
|         <label class="immich-form-label" for="end-date">END DATE</label> | ||||
|         <input | ||||
|           class="immich-form-input w-full mt-3 hover:cursor-pointer" | ||||
|           type="date" | ||||
|           id="end-date" | ||||
|           name="end-date" | ||||
|           placeholder="" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="button-row" class="flex justify-end gap-4 mt-5"> | ||||
|       <Button color="gray">CLEAR ALL</Button> | ||||
|       <Button type="submit">SEARCH</Button> | ||||
|     </div> | ||||
|   </form> | ||||
| </div> | ||||
| @ -0,0 +1,62 @@ | ||||
| <script lang="ts"> | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { savedSearchTerms } from '$lib/stores/search.store'; | ||||
|   import { mdiMagnify, mdiClose } from '@mdi/js'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
|   import { fly } from 'svelte/transition'; | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ | ||||
|     selectSearchTerm: string; | ||||
|     clearSearchTerm: string; | ||||
|     clearAllSearchTerms: void; | ||||
|   }>(); | ||||
| </script> | ||||
| 
 | ||||
| <div | ||||
|   transition:fly={{ y: 25, duration: 250 }} | ||||
|   class="absolute w-full rounded-b-3xl border border-gray-200 bg-white pb-5 shadow-2xl transition-all dark:border-gray-800 dark:bg-immich-dark-gray dark:text-gray-300" | ||||
| > | ||||
|   <div class="flex px-5 pt-5 text-left text-sm"> | ||||
|     <p> | ||||
|       Smart search is enabled by default, to search for metadata use the syntax <span | ||||
|         class="rounded-lg bg-gray-100 p-2 font-mono font-semibold leading-7 text-immich-primary dark:bg-gray-900 dark:text-immich-dark-primary" | ||||
|         >m:your-search-term</span | ||||
|       > | ||||
|     </p> | ||||
|   </div> | ||||
| 
 | ||||
|   {#if $savedSearchTerms.length > 0} | ||||
|     <div class="flex items-center justify-between px-5 pt-5 text-xs"> | ||||
|       <p>RECENT SEARCHES</p> | ||||
|       <div class="flex w-18 items-center justify-center"> | ||||
|         <button | ||||
|           type="button" | ||||
|           class="rounded-lg p-2 font-semibold text-immich-primary hover:bg-immich-primary/25 dark:text-immich-dark-primary" | ||||
|           on:click={() => dispatch('clearAllSearchTerms')}>Clear all</button | ||||
|         > | ||||
|       </div> | ||||
|     </div> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#each $savedSearchTerms as savedSearchTerm, i (i)} | ||||
|     <div | ||||
|       class="flex w-full items-center justify-between text-sm text-black hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-500/10" | ||||
|     > | ||||
|       <div class="relative w-full items-center"> | ||||
|         <button | ||||
|           type="button" | ||||
|           class="relative flex w-full cursor-pointer gap-3 py-3 pl-5" | ||||
|           on:click={() => dispatch('selectSearchTerm', savedSearchTerm)} | ||||
|         > | ||||
|           <Icon path={mdiMagnify} size="1.5em" /> | ||||
|           {savedSearchTerm} | ||||
|         </button> | ||||
|         <div class="absolute right-5 top-0 items-center justify-center py-3"> | ||||
|           <button type="button" on:click={() => dispatch('clearSearchTerm', savedSearchTerm)} | ||||
|             ><Icon path={mdiClose} size="18" /></button | ||||
|           > | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   {/each} | ||||
| </div> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user