🛠️ Refactor search handling in search.svelte and +page.svelte; implement custom addParams and deleteParam utility

This commit is contained in:
pheralb
2025-09-01 11:34:24 +01:00
parent e6d441e9f2
commit 2a38b834c3
4 changed files with 110 additions and 55 deletions
+5 -15
View File
@@ -2,12 +2,9 @@
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { page } from "$app/state"; import { addParams } from "@/utils/searchParams";
import { goto } from "$app/navigation";
import SearchIcon from "@lucide/svelte/icons/search"; import SearchIcon from "@lucide/svelte/icons/search";
import CommandIcon from "@lucide/svelte/icons/command"; import CommandIcon from "@lucide/svelte/icons/command";
import { SvelteURLSearchParams } from "svelte/reactivity";
interface Props { interface Props {
searchValue: string; searchValue: string;
@@ -19,19 +16,12 @@
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement;
const onInput = (event: Event) => { const onInput = (event: Event) => {
const param = "search";
const value = (event.target as HTMLInputElement).value; const value = (event.target as HTMLInputElement).value;
onSearch(value); onSearch(value);
const params = new SvelteURLSearchParams(page.url.searchParams); addParams({
if (value) { params: {
params.set(param, value); search: value,
} else { },
params.delete(param);
}
goto(`?${params.toString()}`, {
keepFocus: true,
noScroll: true,
replaceState: true,
}); });
}; };
+19 -3
View File
@@ -3,6 +3,7 @@
import type { PageProps } from "./$types"; import type { PageProps } from "./$types";
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
import { deleteParam } from "@/utils/searchParams";
import { svgsData } from "@/data"; import { svgsData } from "@/data";
import { searchWithFuse } from "@/utils/searchWithFuse"; import { searchWithFuse } from "@/utils/searchWithFuse";
@@ -12,11 +13,12 @@
import SvgCard from "@/components/svgs/svgCard.svelte"; import SvgCard from "@/components/svgs/svgCard.svelte";
import SortSvgs from "@/components/svgs/sortSvgs.svelte"; import SortSvgs from "@/components/svgs/sortSvgs.svelte";
import Container from "@/components/container.svelte"; import Container from "@/components/container.svelte";
import SearchXIcon from "@lucide/svelte/icons/search-x";
import PageCard from "@/components/pageCard.svelte"; import PageCard from "@/components/pageCard.svelte";
import PageHeader from "@/components/pageHeader.svelte";
import FolderIcon from "@lucide/svelte/icons/folder"; import FolderIcon from "@lucide/svelte/icons/folder";
import FolderSearchIcon from "@lucide/svelte/icons/folder-search"; import PageHeader from "@/components/pageHeader.svelte";
import Button from "@/components/ui/button/button.svelte";
// SSR Data: // SSR Data:
let { data }: PageProps = $props(); let { data }: PageProps = $props();
@@ -60,6 +62,13 @@
searchSvgs(); searchSvgs();
}; };
const handleClearSearch = () => {
searchTerm = "";
filteredSvgs = sorted ? alphabeticallySorted : latestSorted;
deleteParam("search");
updateDisplaySvgs();
};
$effect(() => { $effect(() => {
updateDisplaySvgs(); updateDisplaySvgs();
}); });
@@ -87,7 +96,14 @@
<span>logos</span> <span>logos</span>
</p> </p>
{:else} {:else}
<FolderSearchIcon size={18} strokeWidth={1.5} /> <Button
title="Clear Search"
onclick={handleClearSearch}
variant="ghost"
size="icon"
>
<SearchXIcon size={18} strokeWidth={1.5} />
</Button>
<p> <p>
<span class="font-mono">{filteredSvgs.length}</span> <span class="font-mono">{filteredSvgs.length}</span>
<span>logos</span> <span>logos</span>
+51 -37
View File
@@ -3,7 +3,6 @@
import type { PageProps } from "./$types"; import type { PageProps } from "./$types";
import { page } from "$app/state"; import { page } from "$app/state";
import { goto } from "$app/navigation";
import { SvelteURLSearchParams } from "svelte/reactivity"; import { SvelteURLSearchParams } from "svelte/reactivity";
import { cn } from "@/utils/cn"; import { cn } from "@/utils/cn";
@@ -14,68 +13,70 @@
import Search from "@/components/search.svelte"; import Search from "@/components/search.svelte";
import SvgCard from "@/components/svgs/svgCard.svelte"; import SvgCard from "@/components/svgs/svgCard.svelte";
import Container from "@/components/container.svelte"; import Container from "@/components/container.svelte";
import SearchXIcon from "@lucide/svelte/icons/search-x";
import PageCard from "@/components/pageCard.svelte"; import PageCard from "@/components/pageCard.svelte";
import PageHeader from "@/components/pageHeader.svelte"; import PageHeader from "@/components/pageHeader.svelte";
import FolderIcon from "@lucide/svelte/icons/folder-open"; import FolderIcon from "@lucide/svelte/icons/folder-open";
import ArrowLeftIcon from "@lucide/svelte/icons/arrow-left"; import ArrowLeftIcon from "@lucide/svelte/icons/arrow-left";
import { buttonVariants } from "@/components/ui/button"; import { Button, buttonVariants } from "@/components/ui/button";
import SortSvgs from "@/components/svgs/sortSvgs.svelte";
import { deleteParam } from "@/utils/searchParams";
// SSR Data: // SSR Data:
let { data }: PageProps = $props(); let { data }: PageProps = $props();
const directoryData = $derived(data); const directoryData = $derived(data);
// States: // States:
let maxDisplay = 30;
let searchTerm = $state<string>(data.searchTerm || ""); let searchTerm = $state<string>(data.searchTerm || "");
let filteredSvgs = $derived<iSVG[]>(data.filteredSvgs); let filteredSvgs = $derived<iSVG[]>(data.initialSvgs);
let sorted = $state<boolean>(data.sorted);
let displaySvgs = $state<iSVG[]>([]);
let showAll = $state<boolean>(false);
const updateDisplaySvgs = () => {
displaySvgs = showAll ? filteredSvgs : filteredSvgs.slice(0, maxDisplay);
};
const searchSvgs = () => { const searchSvgs = () => {
if (!searchTerm) { if (!searchTerm) {
filteredSvgs = data.svgs; filteredSvgs = sorted ? data.alphabeticallySorted : data.latestSorted;
updateDisplaySvgs();
return; return;
} }
if (searchTerm.length < 3) { if (searchTerm.length < 3) {
filteredSvgs = data.svgs.filter((svg: iSVG) => filteredSvgs = (
sorted ? data.alphabeticallySorted : data.latestSorted
).filter((svg: iSVG) =>
svg.title.toLowerCase().includes(searchTerm.toLowerCase()), svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
); );
} else { } else {
filteredSvgs = searchWithFuse(data.svgs) filteredSvgs = searchWithFuse(filteredSvgs)
.search(searchTerm) .search(searchTerm)
.map((result) => result.item); .map((result) => result.item);
} }
updateDisplaySvgs();
}; };
const handleSearch = (value: string) => { const handleSearch = (value: string) => {
searchTerm = value; searchTerm = value;
const params = new SvelteURLSearchParams(page.url.searchParams);
if (value) {
params.set("search", value);
} else {
params.delete("search");
}
goto(`?${params.toString()}`, {
keepFocus: true,
noScroll: true,
replaceState: true,
});
searchSvgs(); searchSvgs();
}; };
const formatCategory = (category: string) => const handleClearSearch = () => {
category.charAt(0).toUpperCase() + category.slice(1); searchTerm = "";
deleteParam("search");
updateDisplaySvgs();
};
$effect(() => { $effect(() => {
filteredSvgs = data.svgs.filter((svg: iSVG) => updateDisplaySvgs();
svg.title.toLowerCase().includes(searchTerm.toLowerCase()),
);
}); });
</script> </script>
<svelte:head> <svelte:head>
<title>{formatCategory(directoryData.category)} SVG logos - Svgl</title> <title>{directoryData.category} SVG logos - Svgl</title>
</svelte:head> </svelte:head>
<Search <Search
@@ -91,24 +92,29 @@
> >
<a <a
href="/" href="/"
class={cn( class={cn(buttonVariants({ variant: "ghost", size: "icon" }))}
buttonVariants({ class: "group", variant: "ghost", size: "icon" }),
)}
> >
<ArrowLeftIcon <ArrowLeftIcon size={18} strokeWidth={1.5} />
size={18}
strokeWidth={1.5}
class="transition-transform group-hover:translate-x-[-2px]"
/>
</a> </a>
<FolderIcon size={18} strokeWidth={1.5} /> {#if searchTerm}
<Button
title="Clear Search"
onclick={handleClearSearch}
variant="ghost"
size="icon"
>
<SearchXIcon size={18} strokeWidth={1.5} />
</Button>
{:else}
<FolderIcon class="ml-1" size={18} strokeWidth={1.5} />
{/if}
<p> <p>
{formatCategory(directoryData.category)} {directoryData.category}
</p> </p>
<span>-</span> <span>-</span>
{#if !searchTerm} {#if !searchTerm}
<p> <p>
<span>{data.svgs.length} SVGs </span> <span>{data.initialSvgs.length} SVGs </span>
</p> </p>
{:else} {:else}
<p> <p>
@@ -117,6 +123,14 @@
</p> </p>
{/if} {/if}
</div> </div>
<SortSvgs
className={cn(filteredSvgs.length === 0 && "hidden")}
isSorted={sorted}
onSortedChange={(value) => {
sorted = value;
searchSvgs();
}}
/>
</PageHeader> </PageHeader>
<Container className="my-6"> <Container className="my-6">
<Grid> <Grid>
+35
View File
@@ -0,0 +1,35 @@
import { page } from "$app/state";
import { goto } from "$app/navigation";
import { SvelteURLSearchParams } from "svelte/reactivity";
interface SearchParams {
params: Record<string, string | null>;
}
const addParams = ({ params }: SearchParams) => {
const searchParams = new SvelteURLSearchParams(page.url.searchParams);
Object.entries(params).forEach(([key, value]) => {
if (value) {
searchParams.set(key, value);
} else {
searchParams.delete(key);
}
});
goto(`?${searchParams.toString()}`, {
keepFocus: true,
noScroll: true,
replaceState: true,
});
};
const deleteParam = (key: string) => {
const params = new SvelteURLSearchParams(page.url.searchParams);
params.delete(key);
goto(`?${params.toString()}`, {
keepFocus: true,
noScroll: true,
replaceState: true,
});
};
export { addParams, deleteParam };