🎨 Refactor grid and header components; improve props handling and layout consistency, update search functions to use new naming convention, and remove unused view transitions component

This commit is contained in:
pheralb
2025-09-04 10:45:12 +01:00
parent 8e27a8053d
commit 74e42b00dc
15 changed files with 153 additions and 143 deletions
+10 -2
View File
@@ -2,13 +2,21 @@
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
let { className, children }: { className?: string; children?: Snippet } = interface GridProps {
$props(); columns?: "default" | "4" | "3" | "2";
className?: string;
children?: Snippet;
}
let { className, columns, children }: GridProps = $props();
</script> </script>
<div <div
class={cn( class={cn(
"grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5", "grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5",
columns === "4" && "lg:grid-cols-3 xl:grid-cols-4",
columns === "3" && "lg:grid-cols-2 xl:grid-cols-3",
columns === "2" && "md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2",
className, className,
)} )}
> >
+31 -17
View File
@@ -14,7 +14,7 @@
import SendIcon from "@/components/ui/moving-icons/send-icon.svelte"; import SendIcon from "@/components/ui/moving-icons/send-icon.svelte";
interface HeaderProps { interface HeaderProps {
githubStars: number; githubStars?: number;
} }
let { githubStars }: HeaderProps = $props(); let { githubStars }: HeaderProps = $props();
@@ -55,22 +55,36 @@
/> />
</div> </div>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
<a {#if githubStars !== undefined}
target="_blank" <a
title="GitHub Repository" target="_blank"
href={globals.githubUrl} title="GitHub Repository"
class={cn( href={globals.githubUrl}
buttonVariants({ variant: "ghost" }), class={cn(
"w-fit hover:bg-neutral-200 dark:hover:bg-neutral-800", buttonVariants({ variant: "ghost" }),
)} "w-fit hover:bg-neutral-200 dark:hover:bg-neutral-800",
> )}
<Github size={20} /> >
<span class="text-neutral-600 dark:text-neutral-400"> <Github size={20} />
{githubStars >= 1000 <span class="text-neutral-600 dark:text-neutral-400">
? `${(githubStars / 1000).toFixed(1)}k` {githubStars >= 1000
: githubStars.toLocaleString()} ? `${(githubStars / 1000).toFixed(1)}k`
</span> : githubStars.toLocaleString()}
</a> </span>
</a>
{:else}
<a
target="_blank"
title="GitHub Repository"
href={globals.githubUrl}
class={cn(
buttonVariants({ variant: "ghost", size: "icon" }),
"hover:bg-neutral-200 dark:hover:bg-neutral-800",
)}
>
<Github size={20} />
</a>
{/if}
<Separator orientation="vertical" /> <Separator orientation="vertical" />
<a <a
target="_blank" target="_blank"
+6 -2
View File
@@ -10,9 +10,12 @@
searchValue: string; searchValue: string;
onSearch: (value: string) => void; onSearch: (value: string) => void;
placeholder?: string; placeholder?: string;
iconSize?: number;
inputClass?: string;
} }
let { searchValue, onSearch, placeholder }: Props = $props(); let { searchValue, onSearch, placeholder, iconSize, inputClass }: Props =
$props();
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement;
const onInput = (event: Event) => { const onInput = (event: Event) => {
@@ -42,7 +45,7 @@
<div class="relative"> <div class="relative">
<SearchIcon <SearchIcon
size={20} size={iconSize ? iconSize : 20}
strokeWidth={2} strokeWidth={2}
class={cn( class={cn(
"pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 transition-colors", "pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 transition-colors",
@@ -65,6 +68,7 @@
"bg-white dark:bg-neutral-900", "bg-white dark:bg-neutral-900",
"rounded-md border border-neutral-200 dark:border-neutral-800", "rounded-md border border-neutral-200 dark:border-neutral-800",
"focus:border-neutral-400 focus:outline-none dark:focus:border-neutral-600", "focus:border-neutral-400 focus:outline-none dark:focus:border-neutral-600",
inputClass,
)} )}
/> />
{#if !searchValue} {#if !searchValue}
-11
View File
@@ -477,17 +477,6 @@
<span>Copy JS</span> <span>Copy JS</span>
</Button> </Button>
<Button
variant="outline"
class="justify-start"
title="Copy as Svelte component"
disabled={isLoading}
onclick={() => convertSvgSvelteComponent(false)}
>
<Svelte size={18} />
<span>Copy JS</span>
</Button>
<Button <Button
variant="outline" variant="outline"
class="justify-start" class="justify-start"
+59 -39
View File
@@ -23,7 +23,10 @@
let iconSize = 16; let iconSize = 16;
let iconStroke = 2; let iconStroke = 2;
let cardDownloadStyles = let cardDownloadStyles =
"flex w-full h-full flex-col p-4 rounded-md shadow-sm dark:bg-neutral-800/20 bg-neutral-200/10 border border-neutral-200 dark:border-neutral-800 space-y-2"; "flex w-full h-full flex-col p-4 rounded-md shadow-sm dark:bg-neutral-800/20 bg-neutral-200/10 border border-neutral-200 dark:border-neutral-800 space-y-1.5";
let imgStyles = "my-7 h-10 select-none pointer-events-none";
let badgeButtonStyles =
"font-mono text-neutral-600 dark:text-neutral-400 text-xs";
// Functions: // Functions:
const handleDownloadSvg = async (url?: string) => { const handleDownloadSvg = async (url?: string) => {
@@ -109,28 +112,23 @@
> >
<DownloadIcon size={iconSize} strokeWidth={iconStroke} /> <DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="max-w-[630px]"> <Dialog.Content>
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Download {svgInfo.title} SVG</Dialog.Title> <Dialog.Title>Download {svgInfo.title} SVG</Dialog.Title>
<Dialog.Description> <Dialog.Description>
This logo has multiple options to download: This logo has multiple options to download:
</Dialog.Description> </Dialog.Description>
</Dialog.Header> </Dialog.Header>
<div <div class={cn("flex flex-col gap-4 md:flex-row")}>
class={cn(
"flex h-full flex-col space-y-2 pt-2 pb-0.5",
"md:flex-row md:items-center md:justify-center md:space-y-0 md:space-x-2",
)}
>
{#if typeof svgInfo.route === "string"} {#if typeof svgInfo.route === "string"}
<div class={cardDownloadStyles}> <div class={cardDownloadStyles}>
<img <img
src={isDarkTheme() ? svgInfo.route : svgInfo.route} src={isDarkTheme() ? svgInfo.route : svgInfo.route}
alt={svgInfo.title} alt={svgInfo.title}
class="my-4 h-8" class={imgStyles}
/> />
<Button <Button
class="justify-start" class="justify-between"
title="Download logo" title="Download logo"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -140,8 +138,11 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Icon logo</p> <DownloadIcon size={iconSize} />
<p>Icon logo</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
</div> </div>
{:else} {:else}
@@ -149,10 +150,10 @@
<img <img
src={isDarkTheme() ? svgInfo.route.dark : svgInfo.route.light} src={isDarkTheme() ? svgInfo.route.dark : svgInfo.route.light}
alt={svgInfo.title} alt={svgInfo.title}
class="my-4 h-10" class={imgStyles}
/> />
<Button <Button
class="justify-start" class="justify-between"
title="Logo with light & dark variants" title="Logo with light & dark variants"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -164,12 +165,15 @@
} }
}} }}
> >
<DownloadIcon size={iconSize} /> <div class="flex items-center space-x-2">
<p>Light & dark variants</p> <DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
</div>
<span class={badgeButtonStyles}>.zip</span>
</Button> </Button>
<Button <Button
class="justify-start" class="justify-between"
title="Download light variant" title="Download light variant"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -179,12 +183,15 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Only light variant</p> <DownloadIcon size={iconSize} />
<p>Only light variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
<Button <Button
class="justify-start" class="justify-between"
title="Download dark variant" title="Download dark variant"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -194,8 +201,11 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Only dark variant</p> <DownloadIcon size={iconSize} />
<p>Only dark variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
</div> </div>
{/if} {/if}
@@ -205,10 +215,10 @@
<img <img
src={isDarkTheme() ? svgInfo.wordmark : svgInfo.wordmark} src={isDarkTheme() ? svgInfo.wordmark : svgInfo.wordmark}
alt={svgInfo.title} alt={svgInfo.title}
class="my-4 h-8" class={imgStyles}
/> />
<Button <Button
class="justify-start" class="justify-between"
title="Download Wordmark logo" title="Download Wordmark logo"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -218,8 +228,11 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Wordmark logo</p> <DownloadIcon size={iconSize} />
<p>Wordmark logo</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
</div> </div>
{/if} {/if}
@@ -231,10 +244,10 @@
? svgInfo.wordmark.dark ? svgInfo.wordmark.dark
: svgInfo.wordmark.light} : svgInfo.wordmark.light}
alt={svgInfo.title} alt={svgInfo.title}
class="my-4 h-10" class={imgStyles}
/> />
<Button <Button
class="justify-start" class="justify-between"
title="Download Wordmark light variant" title="Download Wordmark light variant"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -248,12 +261,15 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Light & dark variants</p> <DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
</div>
<span class={badgeButtonStyles}>.zip</span>
</Button> </Button>
<Button <Button
class="justify-start" class="justify-between"
title="Download Wordmark light variant" title="Download Wordmark light variant"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -263,12 +279,15 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Wordmark light variant</p> <DownloadIcon size={iconSize} />
<p>Wordmark light variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
<Button <Button
class="justify-start" class="justify-between"
title="Download Wordmark dark variant" title="Download Wordmark dark variant"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -278,15 +297,16 @@
} }
}} }}
> >
<DownloadIcon class="mr-2" size={iconSize} /> <div class="flex items-center space-x-2">
<p>Wordmark dark variant</p> <DownloadIcon size={iconSize} />
<p>Wordmark dark variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button> </Button>
</div> </div>
{/if} {/if}
</div> </div>
<Dialog.Footer <Dialog.Footer class="text-xs text-neutral-600 dark:text-neutral-400">
class="mt-3 text-xs text-neutral-600 dark:text-neutral-400"
>
<p> <p>
Remember to request permission from the creators for the use of the Remember to request permission from the creators for the use of the
SVG. Modification is not allowed. SVG. Modification is not allowed.
@@ -27,7 +27,8 @@
bind:ref bind:ref
data-slot="dialog-content" data-slot="dialog-content"
class={cn( class={cn(
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-2 rounded-lg border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg dark:border-neutral-800 dark:bg-neutral-900", "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-2 rounded-lg p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-2xl",
"border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900",
className, className,
)} )}
{...restProps} {...restProps}
@@ -35,7 +36,10 @@
{@render children?.()} {@render children?.()}
{#if showCloseButton} {#if showCloseButton}
<DialogPrimitive.Close <DialogPrimitive.Close
class="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:ring-2 focus:ring-neutral-900 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-900 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" class={cn(
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"ring-offset-white focus:ring-neutral-400 focus:ring-offset-2 data-[state=open]:bg-white data-[state=open]:text-neutral-500 dark:ring-offset-neutral-300 dark:focus:ring-neutral-700 dark:data-[state=open]:bg-neutral-900 dark:data-[state=open]:text-neutral-400",
)}
> >
<XIcon /> <XIcon />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
-12
View File
@@ -1,12 +0,0 @@
<script lang="ts">
import { onNavigate } from "$app/navigation";
onNavigate((navigation) => {
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
</script>
+1 -5
View File
@@ -5,10 +5,6 @@ export const load: LayoutServerLoad = async ({ fetch, setHeaders }) => {
try { try {
const response = await fetch(globals.apiGithubUrl); const response = await fetch(globals.apiGithubUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json(); const data = await response.json();
// 1 day cache: // 1 day cache:
@@ -22,7 +18,7 @@ export const load: LayoutServerLoad = async ({ fetch, setHeaders }) => {
} catch (error) { } catch (error) {
console.error("Error fetching GitHub data:", error); console.error("Error fetching GitHub data:", error);
return { return {
stars: 0, stars: null,
error: "Failed to fetch repository data", error: "Failed to fetch repository data",
}; };
} }
-2
View File
@@ -10,7 +10,6 @@
// Providers: // Providers:
import { ModeWatcher } from "mode-watcher"; import { ModeWatcher } from "mode-watcher";
import Sidebar from "@/components/layout/sidebar.svelte"; import Sidebar from "@/components/layout/sidebar.svelte";
import ViewTransitions from "@/components/viewTransitions.svelte";
import Sonner from "@/components/ui/sonner/sonner.svelte"; import Sonner from "@/components/ui/sonner/sonner.svelte";
// SSR Data: // SSR Data:
@@ -18,7 +17,6 @@
</script> </script>
<ModeWatcher /> <ModeWatcher />
<ViewTransitions />
<Sonner /> <Sonner />
<Header githubStars={data?.stars} /> <Header githubStars={data?.stars} />
<Sidebar> <Sidebar>
+16 -12
View File
@@ -5,7 +5,7 @@
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import { deleteParam } from "@/utils/searchParams"; import { deleteParam } from "@/utils/searchParams";
import { svgsData } from "@/data"; import { svgsData } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse"; import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
// Components: // Components:
import Grid from "@/components/grid.svelte"; import Grid from "@/components/grid.svelte";
@@ -17,6 +17,7 @@
import PageCard from "@/components/pageCard.svelte"; import PageCard from "@/components/pageCard.svelte";
import FolderIcon from "@lucide/svelte/icons/folder"; import FolderIcon from "@lucide/svelte/icons/folder";
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
import PageHeader from "@/components/pageHeader.svelte"; import PageHeader from "@/components/pageHeader.svelte";
import Button from "@/components/ui/button/button.svelte"; import Button from "@/components/ui/button/button.svelte";
@@ -43,17 +44,9 @@
updateDisplaySvgs(); updateDisplaySvgs();
return; return;
} }
if (searchTerm.length < 3) { filteredSvgs = searchSvgsWithFuse(filteredSvgs)
filteredSvgs = (sorted ? alphabeticallySorted : latestSorted).filter( .search(searchTerm)
(svg: iSVG) => .map((result) => result.item);
svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
);
} else {
filteredSvgs = searchWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
}
updateDisplaySvgs(); updateDisplaySvgs();
}; };
@@ -125,5 +118,16 @@
<SvgCard svgInfo={svg} /> <SvgCard svgInfo={svg} />
{/each} {/each}
</Grid> </Grid>
{#if showAll === false && filteredSvgs.length > maxDisplay}
<div class="mt-6 flex justify-center">
<Button variant="outline" size="lg" onclick={() => (showAll = true)}>
<span>Show All</span>
<span class="text-neutral-500 dark:text-neutral-700">
(+ {filteredSvgs.length - maxDisplay} SVGs)
</span>
<ChevronDownIcon size={16} strokeWidth={2} />
</Button>
</div>
{/if}
</Container> </Container>
</PageCard> </PageCard>
+5 -12
View File
@@ -2,7 +2,7 @@ import type { iSVG } from "@/types/svg";
import type { Load } from "@sveltejs/kit"; import type { Load } from "@sveltejs/kit";
import { svgsData } from "@/data"; import { svgsData } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse"; import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
export const load: Load = ({ url }) => { export const load: Load = ({ url }) => {
const searchParam = url.searchParams.get("search") || ""; const searchParam = url.searchParams.get("search") || "";
@@ -17,17 +17,10 @@ export const load: Load = ({ url }) => {
if (!searchParam) { if (!searchParam) {
filteredSvgs = sortParam ? alphabeticallySorted : latestSorted; filteredSvgs = sortParam ? alphabeticallySorted : latestSorted;
} else { } else {
if (searchParam.length < 3) { const baseData = sortParam ? alphabeticallySorted : latestSorted;
const baseData = sortParam ? alphabeticallySorted : latestSorted; filteredSvgs = searchSvgsWithFuse(baseData)
filteredSvgs = baseData.filter((svg: iSVG) => .search(searchParam)
svg.title.toLowerCase().includes(searchParam.toLowerCase()), .map((result) => result.item);
);
} else {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
} }
return { return {
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "@sveltejs/kit";
export const load = async () => {
return redirect(307, "/");
};
+4 -15
View File
@@ -2,11 +2,8 @@
import type { iSVG } from "@/types/svg"; import type { iSVG } from "@/types/svg";
import type { PageProps } from "./$types"; import type { PageProps } from "./$types";
import { page } from "$app/state";
import { SvelteURLSearchParams } from "svelte/reactivity";
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import { searchWithFuse } from "@/utils/searchWithFuse"; import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
// Components: // Components:
import Grid from "@/components/grid.svelte"; import Grid from "@/components/grid.svelte";
@@ -45,17 +42,9 @@
updateDisplaySvgs(); updateDisplaySvgs();
return; return;
} }
if (searchTerm.length < 3) { filteredSvgs = searchSvgsWithFuse(filteredSvgs)
filteredSvgs = ( .search(searchTerm)
sorted ? data.alphabeticallySorted : data.latestSorted .map((result) => result.item);
).filter((svg: iSVG) =>
svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
);
} else {
filteredSvgs = searchWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
}
updateDisplaySvgs(); updateDisplaySvgs();
}; };
+5 -12
View File
@@ -3,7 +3,7 @@ import type { iSVG } from "@/types/svg";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { getSvgsByCategory } from "@/data"; import { getSvgsByCategory } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse"; import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
export const load: PageLoad = (async ({ params, url }) => { export const load: PageLoad = (async ({ params, url }) => {
const { category } = params; const { category } = params;
@@ -26,17 +26,10 @@ export const load: PageLoad = (async ({ params, url }) => {
if (!searchParam) { if (!searchParam) {
filteredSvgs = sortParam ? alphabeticallySorted : latestSorted; filteredSvgs = sortParam ? alphabeticallySorted : latestSorted;
} else { } else {
if (searchParam.length < 3) { const baseData = sortParam ? alphabeticallySorted : latestSorted;
const baseData = sortParam ? alphabeticallySorted : latestSorted; filteredSvgs = searchSvgsWithFuse(baseData)
filteredSvgs = baseData.filter((svg: iSVG) => .search(searchParam)
svg.title.toLowerCase().includes(searchParam.toLowerCase()), .map((result) => result.item);
);
} else {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
} }
return { return {
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "@sveltejs/kit";
export const load = async () => {
return redirect(307, "/");
};