BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import type { LocalPostResult } from "$/lib/api/types/search";
2import { For } from "solid-js";
3import { Motion } from "solid-motionone";
4import { SearchResultCard } from "./SearchResultCard";
5
6function LocalPostResultsSkeleton() {
7 return (
8 <div class="flex animate-pulse items-start gap-4 rounded-2xl bg-surface px-4 py-4" aria-hidden>
9 <div class="h-10 w-10 shrink-0 rounded-full bg-white/5" />
10 <div class="min-w-0 flex-1 space-y-2">
11 <For each={["w-48", "w-full", "w-2/3"]}>
12 {(width) => <div class={`h-3 rounded-full bg-white/5 ${width}`} />}
13 </For>
14 </div>
15 </div>
16 );
17}
18
19export function LocalPostResultsSkeletons(props: { count?: number }) {
20 return (
21 <div class="grid gap-2 py-1">
22 <For each={Array.from({ length: props.count ?? 5 })}>{() => <LocalPostResultsSkeleton />}</For>
23 </div>
24 );
25}
26
27export function LocalPostResultsList(
28 props: { onOpenThread?: (uri: string) => void; query: string; results: LocalPostResult[] },
29) {
30 return (
31 <Motion.div
32 class="grid gap-2"
33 initial={{ opacity: 0 }}
34 animate={{ opacity: 1 }}
35 exit={{ opacity: 0 }}
36 transition={{ duration: 0.15 }}>
37 <div class="grid gap-2" role="list">
38 <For each={props.results}>
39 {(result, index) => (
40 <Motion.div
41 initial={{ opacity: 0, y: -6 }}
42 animate={{ opacity: 1, y: 0 }}
43 transition={{ duration: 0.2, delay: Math.min(index() * 0.03, 0.18) }}
44 role="listitem">
45 <SearchResultCard
46 authorDid={result.authorDid}
47 authorHandle={result.authorHandle ?? "unknown"}
48 source={result.source}
49 text={result.text ?? ""}
50 createdAt={result.createdAt ?? ""}
51 isSemanticMatch={result.semanticMatch && !result.keywordMatch}
52 onOpenThread={props.onOpenThread
53 ? () => props.onOpenThread?.(result.uri)
54 : undefined}
55 query={props.query} />
56 </Motion.div>
57 )}
58 </For>
59 </div>
60 </Motion.div>
61 );
62}