pesde/website/src/routes/search/+page.svelte
2024-10-28 17:07:01 +01:00

169 lines
5.2 KiB
Svelte

<script lang="ts">
import { goto } from "$app/navigation"
import { TARGET_KIND_DISPLAY_NAMES } from "$lib/registry-api.js"
import { Pagination } from "bits-ui"
import { formatDistanceToNow } from "date-fns"
import { ChevronLeft, ChevronRight, SearchX } from "lucide-svelte"
const { data } = $props()
let displayDates = $state(false)
$effect(() => {
displayDates = true
})
</script>
<div class="mx-auto max-w-screen-lg px-4">
{#await data.result}
<div class="sr-only">Loading...</div>
<div
class="my-8 flex flex-col sm:flex-row sm:items-center sm:justify-between"
aria-hidden="true"
>
<div class="bg-card-hover w-20 animate-pulse rounded text-transparent">...</div>
<div class="bg-card-hover mx-auto mt-8 h-8 w-48 animate-pulse rounded sm:m-0"></div>
</div>
<div class="mb-8 space-y-4">
{#each Array.from({ length: 10 }).map((_, i) => i) as i}
<div class="bg-card/50 overflow-hidden rounded px-5 py-4">
<div class="mb-1 flex items-center justify-between">
<div class="bg-card-hover w-52 animate-pulse rounded text-xl text-transparent">...</div>
<div
class="bg-card-hover hidden w-32 animate-pulse rounded text-sm text-transparent sm:block"
>
...
</div>
</div>
<div
class="bg-card-hover mb-3 w-96 max-w-full animate-pulse rounded text-sm text-transparent"
>
...
</div>
<div class="bg-card-hover w-16 max-w-full animate-pulse rounded text-sm text-transparent">
...
</div>
</div>
{/each}
</div>
<div class="bg-card-hover mx-auto my-8 h-8 w-48 animate-pulse rounded"></div>
{:then result}
{#if result.data.length === 0}
<div class="flex flex-col items-center py-32 text-center">
<SearchX class="mb-6 size-16" />
<h1 class="text-heading font-bold">No results</h1>
<p class="text-balance">We didn't find any packages matching your search query.</p>
</div>
{:else}
{#snippet pagination()}
<Pagination.Root
count={result.count}
page={data.page}
perPage={data.pageSize}
onPageChange={(page) => {
const params = new URLSearchParams()
params.set("q", data.query)
params.set("page", page.toString())
goto(`/search?${params}`)
}}
let:pages
>
<div class="flex items-center space-x-1">
<Pagination.PrevButton
class="hover:enabled:bg-card-hover inline-flex size-8 items-center justify-center rounded transition disabled:opacity-50"
>
<ChevronLeft />
</Pagination.PrevButton>
{#each pages as page (page.key)}
{#if page.type === "ellipsis"}
<div class="px-2">...</div>
{:else}
<Pagination.Page
{page}
class="hover:bg-card-hover hover:text-heading data-[selected]:bg-primary-bg data-[selected]:text-primary-fg inline-flex size-8 items-center justify-center rounded font-bold transition"
>
{page.value}
</Pagination.Page>
{/if}
{/each}
<Pagination.NextButton
class="hover:enabled:bg-card-hover inline-flex size-8 items-center justify-center rounded transition disabled:opacity-50"
>
<ChevronRight />
</Pagination.NextButton>
</div>
</Pagination.Root>
{/snippet}
<div class="my-8 flex flex-col sm:flex-row sm:items-center sm:justify-between">
<h1 class="font-bold">{result.count} {result.count > 1 ? "results" : "result"}</h1>
<div class="mx-auto mt-8 sm:m-0">
{@render pagination()}
</div>
</div>
<div class="mb-8 space-y-4">
{#each result.data as pkg}
{@const [scope, name] = pkg.name.split("/")}
{#snippet timeAndVersion()}
<time datetime={pkg.published_at}>
{#if displayDates}
{formatDistanceToNow(new Date(pkg.published_at), { addSuffix: true })}
{:else}
...
{/if}
</time>
<span>{" · "}</span>
<span class="truncate">v{pkg.version}</span>
{/snippet}
<article
class="bg-card hover:bg-card-hover relative overflow-hidden rounded px-5 py-4 transition"
>
<div class="mb-1 flex items-center justify-between">
<h3 class="truncate text-xl font-semibold">
<a
href={`/packages/${pkg.name}`}
class="after:absolute after:inset-0 after:content-['']"
>
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
</a>
</h3>
<div
class="text-heading hidden text-sm font-semibold sm:block"
class:invisible={!displayDates}
>
{@render timeAndVersion()}
</div>
</div>
<p class="mb-3 h-[1lh] overflow-hidden truncate text-sm">{pkg.description}</p>
<div
class={`text-primary text-sm font-bold ${displayDates ? "" : "invisible sm:visible"}`}
>
{pkg.targets.map((target) => TARGET_KIND_DISPLAY_NAMES[target.kind]).join(", ")}
<span class="sm:hidden">
<span>{" · "}</span>
{@render timeAndVersion()}
</span>
</div>
</article>
{/each}
</div>
<div class="mx-auto my-8 max-w-min">
{@render pagination()}
</div>
{/if}
{:catch error}
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
<h1 class="text-heading mb-1 text-4xl font-bold">Error</h1>
<p class="text-lg">{error.message}</p>
</div>
{/await}
</div>