[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto skywatched.app
0

Configure Feed

Select the types of activity you want to include in your feed.

skywatched / src / lib / Components / SearchCombobox.svelte
4.7 kB 166 lines
1<script lang="ts"> 2 import { rateMovieModal } from '$lib/state.svelte'; 3 import { createCombobox, melt } from '@melt-ui/svelte'; 4 import { fly } from 'svelte/transition'; 5 6 const { 7 elements: { menu, input, option, label }, 8 states: { open, inputValue, touchedInput }, 9 helpers: { isSelected } 10 } = createCombobox({ 11 forceVisible: true 12 }); 13 14 let debounceTimer: ReturnType<typeof setTimeout>; 15 16 const debounce = (callback: () => void) => { 17 clearTimeout(debounceTimer); 18 debounceTimer = setTimeout(callback, 500); 19 }; 20 21 let results: { 22 movieId: number | null; 23 showId: number | null; 24 title: string; 25 poster_path: string; 26 media_type: string; 27 kind: 'movie' | 'show'; 28 }[] = []; 29 30 let searching = false; 31 32 function onInput() { 33 if ($inputValue.length < 2) { 34 results = []; 35 } else { 36 searching = true; 37 debounce(async () => { 38 if ($inputValue.length < 2) return; 39 40 const response = await fetch(`/api/search?q=${$inputValue}`); 41 const data = await response.json(); 42 results = data 43 .filter( 44 (item: any) => 45 (item.media_type === 'movie' || item.media_type === 'tv') && item.poster_path 46 ) 47 .map((item: any) => ({ 48 movieId: item.media_type === 'movie' ? item.id : null, 49 showId: item.media_type === 'tv' ? item.id : null, 50 title: item.title || item.name, 51 poster_path: item.poster_path, 52 media_type: item.media_type, 53 kind: item.media_type === 'movie' ? 'movie' : 'show' 54 })); 55 56 searching = false; 57 }); 58 } 59 } 60</script> 61 62<div class="flex flex-col gap-1"> 63 <!-- svelte-ignore a11y-label-has-associated-control - $label contains the 'for' attribute --> 64 <!-- <label use:melt={$label}> 65 <span class="text-sm font-medium text-accent-900">Find a movie or show:</span> 66 </label> --> 67 68 <div class="relative"> 69 <input 70 use:melt={$input} 71 class="flex h-10 w-full items-center justify-between rounded-lg border-base-800 bg-black px-3 pr-12 text-base-50 72 placeholder:text-base-400 focus:border-none focus:outline-none focus:ring-2 focus:ring-accent-500" 73 placeholder="Find a movie or show" 74 oninput={onInput} 75 /> 76 <div class="absolute right-2 top-1/2 z-10 -translate-y-1/2 text-accent-500"> 77 {#if $open && $inputValue.length > 1} 78 <svg 79 xmlns="http://www.w3.org/2000/svg" 80 fill="none" 81 viewBox="0 0 24 24" 82 stroke-width="1.5" 83 stroke="currentColor" 84 class="size-4" 85 > 86 <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" /> 87 </svg> 88 {:else} 89 <svg 90 xmlns="http://www.w3.org/2000/svg" 91 fill="none" 92 viewBox="0 0 24 24" 93 stroke-width="1.5" 94 stroke="currentColor" 95 class="size-4" 96 > 97 <path 98 stroke-linecap="round" 99 stroke-linejoin="round" 100 d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" 101 /> 102 </svg> 103 {/if} 104 </div> 105 </div> 106</div> 107{#if $open && $inputValue.length > 1} 108 <ul 109 class=" z-50 flex max-h-[300px] flex-col overflow-hidden rounded-lg" 110 use:melt={$menu} 111 transition:fly={{ duration: 150, y: -5 }} 112 > 113 <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 114 <div 115 class="mx-2 flex max-h-full flex-col gap-0 divide-y divide-base-700/50 overflow-y-auto rounded-lg border border-base-700 bg-base-800 px-2 py-2 text-base-50" 116 tabindex="0" 117 > 118 {#each results as item, index (index)} 119 <li 120 use:melt={$option({ 121 value: item, 122 label: item.title 123 })} 124 class="relative w-full cursor-pointer scroll-my-2 px-2 py-2 data-[highlighted]:text-accent-200" 125 > 126 <button 127 onclick={() => { 128 rateMovieModal.selectedItem = { 129 movieId: item.movieId ?? undefined, 130 showId: item.showId ?? undefined, 131 kind: item.kind, 132 name: item.title, 133 posterPath: item.poster_path, 134 currentRating: undefined, 135 currentReview: undefined 136 }; 137 rateMovieModal.showModal = true; 138 }} 139 class="flex items-center justify-start gap-2 w-full" 140 > 141 <div 142 class="relative z-20 aspect-[2/3] h-12 w-auto shrink-0 overflow-hidden rounded-md border border-base-800 bg-base-900/50" 143 > 144 <img 145 src="https://image.tmdb.org/t/p/w154{item.poster_path}" 146 alt="movie poster for {item.title}" 147 class="size-full object-cover object-center lg:size-full" 148 /> 149 </div> 150 <span class="text-left text-sm font-medium">{item.title}</span> 151 </button> 152 </li> 153 {:else} 154 <li 155 class="relative cursor-pointer rounded-md py-1 pl-4 pr-4 text-sm data-[highlighted]:text-accent-200" 156 > 157 {#if searching} 158 Searching... 159 {:else if $inputValue.length > 1} 160 No results found 161 {/if} 162 </li> 163 {/each} 164 </div> 165 </ul> 166{/if}