BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
at main 62 lines 2.1 kB view raw
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}