your personal website on atproto - mirror blento.app
at switch-grid-layout 91 lines 2.5 kB view raw
1<script lang="ts"> 2 import { AppBskyActorDefs } from '@atcute/bluesky'; 3 import { Combobox } from 'bits-ui'; 4 import { searchActorsTypeahead } from '$lib/atproto'; 5 import { Avatar } from '@foxui/core'; 6 7 let results: AppBskyActorDefs.ProfileViewBasic[] = $state([]); 8 9 async function search(q: string) { 10 if (!q || q.length < 2) { 11 results = []; 12 return; 13 } 14 results = (await searchActorsTypeahead(q, 5)).actors; 15 } 16 let open = $state(false); 17 18 let { 19 value = $bindable(), 20 onselected, 21 ref = $bindable() 22 }: { 23 value: string; 24 onselected: (actor: AppBskyActorDefs.ProfileViewBasic) => void; 25 ref?: HTMLInputElement | null; 26 } = $props(); 27</script> 28 29<Combobox.Root 30 type="single" 31 onOpenChangeComplete={(o) => { 32 if (!o) results = []; 33 }} 34 bind:value={ 35 () => { 36 return value; 37 }, 38 (val) => { 39 const profile = results.find((v) => v.handle === val); 40 if (profile) onselected?.(profile); 41 // Only update if val has content - prevents Combobox from clearing on Enter 42 if (val) value = val; 43 } 44 } 45 bind:open={ 46 () => { 47 return open && results.length > 0; 48 }, 49 (val) => { 50 open = val; 51 } 52 } 53> 54 <Combobox.Input 55 bind:ref 56 oninput={(e) => { 57 value = e.currentTarget.value; 58 search(e.currentTarget.value); 59 }} 60 onkeydown={(e) => { 61 if (e.key === 'Enter') e.currentTarget.form?.requestSubmit(); 62 }} 63 class="focus-within:outline-accent-600 dark:focus-within:outline-accent-500 dark:placeholder:text-base-400 w-full touch-none rounded-full border-0 bg-white ring-0 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 dark:bg-white/5 dark:outline-white/10" 64 placeholder="handle" 65 id="" 66 aria-label="enter your handle" 67 /> 68 <Combobox.Content 69 class="border-base-300 bg-base-50 dark:bg-base-900 dark:border-base-800 z-100 max-h-[30dvh] w-full rounded-2xl border shadow-lg" 70 sideOffset={10} 71 align="start" 72 side="top" 73 > 74 <Combobox.Viewport class="w-full p-1"> 75 {#each results as actor (actor.did)} 76 <Combobox.Item 77 class="rounded-button data-highlighted:bg-accent-100 dark:data-highlighted:bg-accent-600/30 my-0.5 flex w-full cursor-pointer items-center gap-2 rounded-xl p-2 px-2" 78 value={actor.handle} 79 label={actor.handle} 80 > 81 <Avatar 82 src={actor.avatar?.replace('avatar', 'avatar_thumbnail')} 83 alt="" 84 class="size-6 rounded-full" 85 /> 86 {actor.handle} 87 </Combobox.Item> 88 {/each} 89 </Combobox.Viewport> 90 </Combobox.Content> 91</Combobox.Root>