mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
✨ Add extension page, create extension component for displaying items & add search functionality
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Extension } from "@/types/extensions";
|
||||||
|
|
||||||
|
import { cn } from "@/utils/cn";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
import ArrowUpRightIcon from "@lucide/svelte/icons/arrow-up-right";
|
||||||
|
|
||||||
|
interface ExtensionProps {
|
||||||
|
data: Extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: ExtensionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn(
|
||||||
|
"relative h-48 max-h-48",
|
||||||
|
"rounded-md transition-shadow hover:shadow-sm",
|
||||||
|
"bg-white dark:bg-neutral-900",
|
||||||
|
"border border-neutral-200 dark:border-neutral-800",
|
||||||
|
"flex flex-col",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<img
|
||||||
|
src={data.image}
|
||||||
|
alt={data.name}
|
||||||
|
loading="lazy"
|
||||||
|
class="h-8 w-8 rounded-md object-contain"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={data.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class={buttonVariants({ variant: "outline" })}
|
||||||
|
>
|
||||||
|
<span>Install</span>
|
||||||
|
<ArrowUpRightIcon
|
||||||
|
size={12}
|
||||||
|
strokeWidth={2}
|
||||||
|
class="text-neutral-600 dark:text-neutral-400"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1 font-medium">{data.name}</p>
|
||||||
|
<p
|
||||||
|
title={data.description}
|
||||||
|
class="truncate text-sm text-pretty text-neutral-700 dark:text-neutral-300"
|
||||||
|
>
|
||||||
|
{data.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 flex w-full items-center justify-end gap-1 border-t border-neutral-200 bg-neutral-100/60 px-4 py-2 text-sm dark:border-neutral-800 dark:bg-neutral-800/40"
|
||||||
|
>
|
||||||
|
<span>Created by</span>
|
||||||
|
<a
|
||||||
|
href={data.created_by.socialUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-medium text-pretty decoration-neutral-400 underline-offset-2 transition-colors hover:text-neutral-600 hover:underline dark:decoration-neutral-600 dark:hover:text-neutral-400"
|
||||||
|
>
|
||||||
|
{data.created_by.name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from "./$types";
|
||||||
|
import type { Extension } from "@/types/extensions";
|
||||||
|
|
||||||
|
// Utils:
|
||||||
|
import { cn } from "@/utils/cn";
|
||||||
|
import { searchExtensionsWithFuse } from "@/utils/searchWithFuse";
|
||||||
|
|
||||||
|
// UI Components:
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
|
// Page components:
|
||||||
|
import PageCard from "@/components/pageCard.svelte";
|
||||||
|
import PageHeader from "@/components/pageHeader.svelte";
|
||||||
|
import Grid from "@/components/grid.svelte";
|
||||||
|
import Container from "@/components/container.svelte";
|
||||||
|
import ExtensionItem from "@/components/extension.svelte";
|
||||||
|
|
||||||
|
// Svgs:
|
||||||
|
import RocketIcon from "@lucide/svelte/icons/rocket";
|
||||||
|
import PackageOpen from "@lucide/svelte/icons/package-open";
|
||||||
|
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||||
|
import Search from "@/components/search.svelte";
|
||||||
|
|
||||||
|
// SSR Data:
|
||||||
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
|
// States:
|
||||||
|
let searchTerm = $state<string>(data.searchTerm || "");
|
||||||
|
let filteredExtensions = $state<Extension[]>(data.initialExtensions);
|
||||||
|
|
||||||
|
const searchExtensions = () => {
|
||||||
|
if (!searchTerm) {
|
||||||
|
filteredExtensions = data.allExtensions;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filteredExtensions = searchExtensionsWithFuse(data.allExtensions)
|
||||||
|
.search(searchTerm)
|
||||||
|
.map((result) => result.item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (value: string) => {
|
||||||
|
searchTerm = value;
|
||||||
|
searchExtensions();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Extensions - Svgl</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Integrate SVGL with your favorite tools and apps to streamline your workflow. Created by the community."
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<PageCard
|
||||||
|
containerClass="mt-0"
|
||||||
|
contentCardClass="max-h-[calc(100vh-5.2rem)] min-h-[calc(100vh-5.2rem)]"
|
||||||
|
>
|
||||||
|
<PageHeader>
|
||||||
|
<div
|
||||||
|
class="flex items-center space-x-2 font-medium text-neutral-950 dark:text-neutral-50"
|
||||||
|
>
|
||||||
|
<PackageOpen size={18} strokeWidth={1.5} />
|
||||||
|
<p>Extensions</p>
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
<section class="flex flex-col px-4 py-12">
|
||||||
|
<h2
|
||||||
|
class={cn(
|
||||||
|
"mb-4 text-center",
|
||||||
|
"font-onest text-4xl font-semibold",
|
||||||
|
"animate-in delay-100 duration-500 fill-mode-backwards fade-in slide-in-from-bottom-4",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Extensions
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class={cn(
|
||||||
|
"flex w-full flex-col items-center justify-center space-y-4",
|
||||||
|
"animate-in delay-300 duration-500 fill-mode-backwards fade-in slide-in-from-bottom-4",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Integrate SVGL with your favorite tools and apps to streamline your
|
||||||
|
workflow. Created by the community.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<a href="/docs/api" class={cn(buttonVariants({ variant: "radial" }))}>
|
||||||
|
<RocketIcon size={16} strokeWidth={2} />
|
||||||
|
<span>Start Building</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/pheralb/svgl/issues/new/choose"
|
||||||
|
class={cn(buttonVariants({ variant: "outline" }))}
|
||||||
|
>
|
||||||
|
<PlusIcon size={16} strokeWidth={2} />
|
||||||
|
<span>Add your extension</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<Container
|
||||||
|
className={cn(
|
||||||
|
"max-w-3xl",
|
||||||
|
"animate-in delay-500 duration-500 fill-mode-backwards fade-in slide-in-from-bottom-4",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sticky top-12 z-10 bg-white/80 pt-4 backdrop-blur-sm dark:bg-transparent"
|
||||||
|
>
|
||||||
|
<Search
|
||||||
|
iconSize={18}
|
||||||
|
searchValue={searchTerm}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
placeholder="Search..."
|
||||||
|
inputClass="text-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Grid columns="2" className="mt-5 mb-7">
|
||||||
|
{#each filteredExtensions as extension (extension.id)}
|
||||||
|
<ExtensionItem data={extension} />
|
||||||
|
{/each}
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</PageCard>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { Load } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
import { extensionsData } from "@/data";
|
||||||
|
import { searchExtensionsWithFuse } from "@/utils/searchWithFuse";
|
||||||
|
|
||||||
|
export const load: Load = ({ url }) => {
|
||||||
|
const searchParam = url.searchParams.get("search") || "";
|
||||||
|
let filteredExtensions = [...extensionsData];
|
||||||
|
|
||||||
|
if (searchParam) {
|
||||||
|
const fuseSearch = searchExtensionsWithFuse(extensionsData);
|
||||||
|
filteredExtensions = fuseSearch
|
||||||
|
.search(searchParam)
|
||||||
|
.map((result) => result.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchTerm: searchParam,
|
||||||
|
initialExtensions: filteredExtensions,
|
||||||
|
allExtensions: extensionsData,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user