your personal website on atproto - mirror
blento.app
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
42 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 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"
61 placeholder="handle"
62 id=""
63 aria-label="enter your handle"
64 />
65 <Combobox.Content
66 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"
67 sideOffset={10}
68 align="start"
69 side="top"
70 >
71 <Combobox.Viewport class="w-full p-1">
72 {#each results as actor (actor.did)}
73 <Combobox.Item
74 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"
75 value={actor.handle}
76 label={actor.handle}
77 >
78 <Avatar
79 src={actor.avatar?.replace('avatar', 'avatar_thumbnail')}
80 alt=""
81 class="size-6 rounded-full"
82 />
83 {actor.handle}
84 </Combobox.Item>
85 {/each}
86 </Combobox.Viewport>
87 </Combobox.Content>
88</Combobox.Root>