🎨 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 { cn } from "@/utils/cn";
let { className, children }: { className?: string; children?: Snippet } =
$props();
interface GridProps {
columns?: "default" | "4" | "3" | "2";
className?: string;
children?: Snippet;
}
let { className, columns, children }: GridProps = $props();
</script>
<div
class={cn(
"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,
)}
>
+31 -17
View File
@@ -14,7 +14,7 @@
import SendIcon from "@/components/ui/moving-icons/send-icon.svelte";
interface HeaderProps {
githubStars: number;
githubStars?: number;
}
let { githubStars }: HeaderProps = $props();
@@ -55,22 +55,36 @@
/>
</div>
<Separator orientation="vertical" />
<a
target="_blank"
title="GitHub Repository"
href={globals.githubUrl}
class={cn(
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">
{githubStars >= 1000
? `${(githubStars / 1000).toFixed(1)}k`
: githubStars.toLocaleString()}
</span>
</a>
{#if githubStars !== undefined}
<a
target="_blank"
title="GitHub Repository"
href={globals.githubUrl}
class={cn(
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">
{githubStars >= 1000
? `${(githubStars / 1000).toFixed(1)}k`
: githubStars.toLocaleString()}
</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" />
<a
target="_blank"
+6 -2
View File
@@ -10,9 +10,12 @@
searchValue: string;
onSearch: (value: string) => void;
placeholder?: string;
iconSize?: number;
inputClass?: string;
}
let { searchValue, onSearch, placeholder }: Props = $props();
let { searchValue, onSearch, placeholder, iconSize, inputClass }: Props =
$props();
let inputElement: HTMLInputElement;
const onInput = (event: Event) => {
@@ -42,7 +45,7 @@
<div class="relative">
<SearchIcon
size={20}
size={iconSize ? iconSize : 20}
strokeWidth={2}
class={cn(
"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",
"rounded-md border border-neutral-200 dark:border-neutral-800",
"focus:border-neutral-400 focus:outline-none dark:focus:border-neutral-600",
inputClass,
)}
/>
{#if !searchValue}
-11
View File
@@ -477,17 +477,6 @@
<span>Copy JS</span>
</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
variant="outline"
class="justify-start"
+59 -39
View File
@@ -23,7 +23,10 @@
let iconSize = 16;
let iconStroke = 2;
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:
const handleDownloadSvg = async (url?: string) => {
@@ -109,28 +112,23 @@
>
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</Dialog.Trigger>
<Dialog.Content class="max-w-[630px]">
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Download {svgInfo.title} SVG</Dialog.Title>
<Dialog.Description>
This logo has multiple options to download:
</Dialog.Description>
</Dialog.Header>
<div
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",
)}
>
<div class={cn("flex flex-col gap-4 md:flex-row")}>
{#if typeof svgInfo.route === "string"}
<div class={cardDownloadStyles}>
<img
src={isDarkTheme() ? svgInfo.route : svgInfo.route}
alt={svgInfo.title}
class="my-4 h-8"
class={imgStyles}
/>
<Button
class="justify-start"
class="justify-between"
title="Download logo"
variant="outline"
onclick={() => {
@@ -140,8 +138,11 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Icon logo</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Icon logo</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
</div>
{:else}
@@ -149,10 +150,10 @@
<img
src={isDarkTheme() ? svgInfo.route.dark : svgInfo.route.light}
alt={svgInfo.title}
class="my-4 h-10"
class={imgStyles}
/>
<Button
class="justify-start"
class="justify-between"
title="Logo with light & dark variants"
variant="outline"
onclick={() => {
@@ -164,12 +165,15 @@
}
}}
>
<DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
</div>
<span class={badgeButtonStyles}>.zip</span>
</Button>
<Button
class="justify-start"
class="justify-between"
title="Download light variant"
variant="outline"
onclick={() => {
@@ -179,12 +183,15 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Only light variant</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Only light variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
<Button
class="justify-start"
class="justify-between"
title="Download dark variant"
variant="outline"
onclick={() => {
@@ -194,8 +201,11 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Only dark variant</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Only dark variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
</div>
{/if}
@@ -205,10 +215,10 @@
<img
src={isDarkTheme() ? svgInfo.wordmark : svgInfo.wordmark}
alt={svgInfo.title}
class="my-4 h-8"
class={imgStyles}
/>
<Button
class="justify-start"
class="justify-between"
title="Download Wordmark logo"
variant="outline"
onclick={() => {
@@ -218,8 +228,11 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Wordmark logo</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Wordmark logo</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
</div>
{/if}
@@ -231,10 +244,10 @@
? svgInfo.wordmark.dark
: svgInfo.wordmark.light}
alt={svgInfo.title}
class="my-4 h-10"
class={imgStyles}
/>
<Button
class="justify-start"
class="justify-between"
title="Download Wordmark light variant"
variant="outline"
onclick={() => {
@@ -248,12 +261,15 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Light & dark variants</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
</div>
<span class={badgeButtonStyles}>.zip</span>
</Button>
<Button
class="justify-start"
class="justify-between"
title="Download Wordmark light variant"
variant="outline"
onclick={() => {
@@ -263,12 +279,15 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Wordmark light variant</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Wordmark light variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
<Button
class="justify-start"
class="justify-between"
title="Download Wordmark dark variant"
variant="outline"
onclick={() => {
@@ -278,15 +297,16 @@
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Wordmark dark variant</p>
<div class="flex items-center space-x-2">
<DownloadIcon size={iconSize} />
<p>Wordmark dark variant</p>
</div>
<span class={badgeButtonStyles}>.svg</span>
</Button>
</div>
{/if}
</div>
<Dialog.Footer
class="mt-3 text-xs text-neutral-600 dark:text-neutral-400"
>
<Dialog.Footer class="text-xs text-neutral-600 dark:text-neutral-400">
<p>
Remember to request permission from the creators for the use of the
SVG. Modification is not allowed.
@@ -27,7 +27,8 @@
bind:ref
data-slot="dialog-content"
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,
)}
{...restProps}
@@ -35,7 +36,10 @@
{@render children?.()}
{#if showCloseButton}
<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 />
<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 {
const response = await fetch(globals.apiGithubUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// 1 day cache:
@@ -22,7 +18,7 @@ export const load: LayoutServerLoad = async ({ fetch, setHeaders }) => {
} catch (error) {
console.error("Error fetching GitHub data:", error);
return {
stars: 0,
stars: null,
error: "Failed to fetch repository data",
};
}
-2
View File
@@ -10,7 +10,6 @@
// Providers:
import { ModeWatcher } from "mode-watcher";
import Sidebar from "@/components/layout/sidebar.svelte";
import ViewTransitions from "@/components/viewTransitions.svelte";
import Sonner from "@/components/ui/sonner/sonner.svelte";
// SSR Data:
@@ -18,7 +17,6 @@
</script>
<ModeWatcher />
<ViewTransitions />
<Sonner />
<Header githubStars={data?.stars} />
<Sidebar>
+16 -12
View File
@@ -5,7 +5,7 @@
import { cn } from "@/utils/cn";
import { deleteParam } from "@/utils/searchParams";
import { svgsData } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse";
import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
// Components:
import Grid from "@/components/grid.svelte";
@@ -17,6 +17,7 @@
import PageCard from "@/components/pageCard.svelte";
import FolderIcon from "@lucide/svelte/icons/folder";
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
import PageHeader from "@/components/pageHeader.svelte";
import Button from "@/components/ui/button/button.svelte";
@@ -43,17 +44,9 @@
updateDisplaySvgs();
return;
}
if (searchTerm.length < 3) {
filteredSvgs = (sorted ? alphabeticallySorted : latestSorted).filter(
(svg: iSVG) =>
svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
);
} else {
filteredSvgs = searchWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
}
filteredSvgs = searchSvgsWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
updateDisplaySvgs();
};
@@ -125,5 +118,16 @@
<SvgCard svgInfo={svg} />
{/each}
</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>
</PageCard>
+5 -12
View File
@@ -2,7 +2,7 @@ import type { iSVG } from "@/types/svg";
import type { Load } from "@sveltejs/kit";
import { svgsData } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse";
import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
export const load: Load = ({ url }) => {
const searchParam = url.searchParams.get("search") || "";
@@ -17,17 +17,10 @@ export const load: Load = ({ url }) => {
if (!searchParam) {
filteredSvgs = sortParam ? alphabeticallySorted : latestSorted;
} else {
if (searchParam.length < 3) {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = baseData.filter((svg: iSVG) =>
svg.title.toLowerCase().includes(searchParam.toLowerCase()),
);
} else {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchSvgsWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
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 { PageProps } from "./$types";
import { page } from "$app/state";
import { SvelteURLSearchParams } from "svelte/reactivity";
import { cn } from "@/utils/cn";
import { searchWithFuse } from "@/utils/searchWithFuse";
import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
// Components:
import Grid from "@/components/grid.svelte";
@@ -45,17 +42,9 @@
updateDisplaySvgs();
return;
}
if (searchTerm.length < 3) {
filteredSvgs = (
sorted ? data.alphabeticallySorted : data.latestSorted
).filter((svg: iSVG) =>
svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
);
} else {
filteredSvgs = searchWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
}
filteredSvgs = searchSvgsWithFuse(filteredSvgs)
.search(searchTerm)
.map((result) => result.item);
updateDisplaySvgs();
};
+5 -12
View File
@@ -3,7 +3,7 @@ import type { iSVG } from "@/types/svg";
import { error } from "@sveltejs/kit";
import { getSvgsByCategory } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse";
import { searchSvgsWithFuse } from "@/utils/searchWithFuse";
export const load: PageLoad = (async ({ params, url }) => {
const { category } = params;
@@ -26,17 +26,10 @@ export const load: PageLoad = (async ({ params, url }) => {
if (!searchParam) {
filteredSvgs = sortParam ? alphabeticallySorted : latestSorted;
} else {
if (searchParam.length < 3) {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = baseData.filter((svg: iSVG) =>
svg.title.toLowerCase().includes(searchParam.toLowerCase()),
);
} else {
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
const baseData = sortParam ? alphabeticallySorted : latestSorted;
filteredSvgs = searchSvgsWithFuse(baseData)
.search(searchParam)
.map((result) => result.item);
}
return {
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "@sveltejs/kit";
export const load = async () => {
return redirect(307, "/");
};