atproto explorer

add up down keys for suggestion list

juli.ee 37bb900a 388a931b

verified
+29 -4
+29 -4
src/components/search.tsx
··· 64 64 }; 65 65 66 66 const [input, setInput] = createSignal<string>(); 67 + const [selectedIndex, setSelectedIndex] = createSignal(-1); 67 68 const [search] = createResource(createDebouncedValue(input, 250), fetchTypeahead); 68 69 69 70 const processInput = async (input: string) => { 70 71 input = input.trim().replace(/^@/, ""); 71 72 if (!input.length) return; 73 + const index = selectedIndex() >= 0 ? selectedIndex() : 0; 72 74 setShowSearch(false); 75 + setInput(undefined); 76 + setSelectedIndex(-1); 73 77 if (search()?.length) { 74 - navigate(`/at://${search()![0].did}`); 78 + navigate(`/at://${search()![index].did}`); 75 79 } else if (input.startsWith("https://") || input.startsWith("http://")) { 76 80 const hostLength = input.indexOf("/", 8); 77 81 const host = input.slice(0, hostLength).replace("https://", "").replace("http://", ""); ··· 118 122 id="input" 119 123 class="grow py-1 select-none placeholder:text-sm focus:outline-none" 120 124 value={input() ?? ""} 121 - onInput={(e) => setInput(e.currentTarget.value)} 125 + onInput={(e) => { 126 + setInput(e.currentTarget.value); 127 + setSelectedIndex(-1); 128 + }} 129 + onKeyDown={(e) => { 130 + const results = search(); 131 + if (!results?.length) return; 132 + 133 + if (e.key === "ArrowDown") { 134 + e.preventDefault(); 135 + setSelectedIndex((prev) => (prev === -1 ? 0 : (prev + 1) % results.length)); 136 + } else if (e.key === "ArrowUp") { 137 + e.preventDefault(); 138 + setSelectedIndex((prev) => 139 + prev === -1 ? results.length - 1 : (prev - 1 + results.length) % results.length, 140 + ); 141 + } 142 + }} 122 143 /> 123 144 <Show when={input()} fallback={ListUrlsTooltip()}> 124 145 <button ··· 133 154 <Show when={search()?.length && input()}> 134 155 <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute z-30 mt-1 flex w-full flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 135 156 <For each={search()}> 136 - {(actor) => ( 157 + {(actor, index) => ( 137 158 <A 138 - class="flex items-center gap-2 rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 159 + class={`flex items-center gap-2 rounded-lg p-1 transition-colors duration-150 ${ 160 + index() === selectedIndex() ? 161 + "bg-neutral-200 dark:bg-neutral-700" 162 + : "hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 163 + }`} 139 164 href={`/at://${actor.did}`} 140 165 onClick={() => setShowSearch(false)} 141 166 >