diff --git a/src/components/layout/header.svelte b/src/components/layout/header.svelte index a4cf878..fcc2ab4 100644 --- a/src/components/layout/header.svelte +++ b/src/components/layout/header.svelte @@ -8,10 +8,17 @@ import Github from "@/components/logos/github.svelte"; import Twitter from "@/components/logos/twitter.svelte"; + import { Separator } from "@/components/ui/separator"; import Badge from "@/components/ui/badge/badge.svelte"; import { buttonVariants } from "@/components/ui/button"; import SendIcon from "@/components/ui/moving-icons/send-icon.svelte"; + interface HeaderProps { + githubStars: number; + } + + let { githubStars }: HeaderProps = $props(); + const headerItemsClasses = cn( buttonVariants({ variant: "ghost" }), "hover:bg-neutral-200 dark:hover:bg-neutral-800", @@ -32,10 +39,8 @@ {globals.currentVersion} -
-
+
+ + + + + + {githubStars >= 1000 + ? `${(githubStars / 1000).toFixed(1)}k` + : githubStars.toLocaleString()} + + +

{category}

{categoryCounts[category]} diff --git a/src/components/layout/showSidebarLinks.svelte b/src/components/layout/showSidebarLinks.svelte index 304917b..1e7f24b 100644 --- a/src/components/layout/showSidebarLinks.svelte +++ b/src/components/layout/showSidebarLinks.svelte @@ -1,22 +1,74 @@ -{#each sidebarLinks as sidebarLink} -
- -

{sidebarLink.title}

-
-{/each} + + +

Home

+
+ +
+ +

Favorites

+
+ {#if favoritesCount > 0} + + {favoritesCount} + + {/if} +
+ + +

API

+
+ + +

Extensions

+
diff --git a/src/components/layout/sidebarBadgeClasses.ts b/src/components/layout/sidebarBadgeClasses.ts new file mode 100644 index 0000000..b5bbfed --- /dev/null +++ b/src/components/layout/sidebarBadgeClasses.ts @@ -0,0 +1,6 @@ +import { cn } from "@/utils/cn"; + +export const sidebarBadgeClasses = cn( + "animate-in zoom-in-20 fade-in", + "dark:bg-dark rounded-lg border border-neutral-200 bg-white px-2 py-0.5 font-mono text-xs font-medium text-neutral-600 shadow-sm dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-400", +); diff --git a/src/components/layout/sidebarItemClasses.ts b/src/components/layout/sidebarItemClasses.ts index 7a668ce..324f94c 100644 --- a/src/components/layout/sidebarItemClasses.ts +++ b/src/components/layout/sidebarItemClasses.ts @@ -2,7 +2,7 @@ import { cn } from "@/utils/cn"; export const sidebarItemClasses = { base: cn( - "rounded-md px-2 py-1.5", + "rounded-md px-2 py-1.5 h-8", "flex w-full items-center justify-between space-x-3 text-sm", "text-neutral-600 dark:text-neutral-400", "hover:text-black dark:hover:text-white", diff --git a/src/components/layout/sidebarLinks.ts b/src/components/layout/sidebarLinks.ts deleted file mode 100644 index 1e9981b..0000000 --- a/src/components/layout/sidebarLinks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import BoxIcon from "@lucide/svelte/icons/box"; -import HouseIcon from "@lucide/svelte/icons/house"; -import CloudIcon from "@lucide/svelte/icons/cloud"; -import HeartIcon from "@lucide/svelte/icons/heart"; - -export const sidebarLinks = [ - { - title: "Home", - href: "/", - icon: HouseIcon, - }, - { - title: "Favorites", - href: "/favorites", - icon: HeartIcon, - }, - { - title: "API", - href: "/api", - icon: CloudIcon, - }, - { - title: "Extensions", - href: "/extensions", - icon: BoxIcon, - }, -]; diff --git a/src/components/pageCard.svelte b/src/components/pageCard.svelte new file mode 100644 index 0000000..3d13789 --- /dev/null +++ b/src/components/pageCard.svelte @@ -0,0 +1,26 @@ + + +
+
+ {@render children?.()} +
+
diff --git a/src/components/sortSvgs.svelte b/src/components/sortSvgs.svelte new file mode 100644 index 0000000..434809a --- /dev/null +++ b/src/components/sortSvgs.svelte @@ -0,0 +1,51 @@ + + + diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..98b5872 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,29 @@ +import type { LayoutServerLoad } from "./$types"; +import { globals } from "@/globals"; + +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: + setHeaders({ + "cache-control": "public, max-age=86400", + }); + + return { + stars: data.stargazers_count, + }; + } catch (error) { + console.error("Error fetching GitHub data:", error); + return { + stars: 0, + error: "Failed to fetch repository data", + }; + } +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a5f6f8f..1311282 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,4 +1,6 @@ -
+
{@render children?.()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e4b779d..ba713ff 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,21 +2,20 @@ import type { iSVG } from "@/types/svg"; import type { PageProps } from "./$types"; - import Fuse from "fuse.js"; import { cn } from "@/utils/cn"; import { svgsData } from "@/data"; + import { searchWithFuse } from "@/utils/searchWithFuse"; // Components: import Grid from "@/components/grid.svelte"; import Search from "@/components/search.svelte"; import SvgCard from "@/components/svgCard.svelte"; + import SortSvgs from "@/components/sortSvgs.svelte"; import Container from "@/components/container.svelte"; - import { buttonVariants } from "@/components/ui/button"; + import PageCard from "@/components/pageCard.svelte"; import FolderIcon from "@lucide/svelte/icons/folder"; import FolderSearchIcon from "@lucide/svelte/icons/folder-search"; - import ArrowUpDownIcon from "@lucide/svelte/icons/arrow-up-down"; - import ArrowDownUpIcon from "@lucide/svelte/icons/arrow-down-up"; // SSR Data: let { data }: PageProps = $props(); @@ -31,15 +30,6 @@ const { latestSorted, alphabeticallySorted } = data; - // Fuse.js Search (solo para búsquedas del lado cliente): - const fuse = new Fuse(svgsData, { - keys: ["title"], - threshold: 0.35, - ignoreLocation: true, - isCaseSensitive: false, - shouldSort: true, - }); - const updateDisplaySvgs = () => { displaySvgs = showAll ? filteredSvgs : filteredSvgs.slice(0, maxDisplay); }; @@ -56,17 +46,14 @@ svg.title.toLowerCase().includes(searchTerm.toLowerCase()), ); } else { - filteredSvgs = fuse.search(searchTerm).map((result) => result.item); + filteredSvgs = searchWithFuse(filteredSvgs) + .search(searchTerm) + .map((result) => result.item); } updateDisplaySvgs(); }; - const sort = () => { - sorted = !sorted; - searchSvgs(); - }; - const handleSearch = (value: string) => { searchTerm = value; searchSvgs(); @@ -83,65 +70,47 @@ placeholder="Search..." /> -
+
-
- {#if !searchTerm} - -

- {svgsData.length} - logos -

- {:else} - -

- {filteredSvgs.length} - logos -

- {/if} -
- + {#if !searchTerm} + +

+ {svgsData.length} + logos +

+ {:else} + +

+ {filteredSvgs.length} + logos +

+ {/if}
- - - {#each displaySvgs as svg} - - {/each} - - + { + sorted = value; + searchSvgs(); + }} + />
-
+ + + {#each displaySvgs as svg} + + {/each} + + + diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 6946071..6b87a58 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,8 +1,8 @@ import type { iSVG } from "@/types/svg"; import type { Load } from "@sveltejs/kit"; -import Fuse from "fuse.js"; import { svgsData } from "@/data"; +import { searchWithFuse } from "@/utils/searchWithFuse"; export const load: Load = ({ url }) => { const searchParam = url.searchParams.get("search") || ""; @@ -17,21 +17,15 @@ export const load: Load = ({ url }) => { if (!searchParam) { filteredSvgs = sortParam ? alphabeticallySorted : latestSorted; } else { - const fuse = new Fuse(svgsData, { - keys: ["title"], - threshold: 0.35, - ignoreLocation: true, - isCaseSensitive: false, - shouldSort: true, - }); - if (searchParam.length < 3) { const baseData = sortParam ? alphabeticallySorted : latestSorted; filteredSvgs = baseData.filter((svg: iSVG) => svg.title.toLowerCase().includes(searchParam.toLowerCase()), ); } else { - filteredSvgs = fuse.search(searchParam).map((result) => result.item); + filteredSvgs = searchWithFuse(filteredSvgs) + .search(searchParam) + .map((result) => result.item); } } diff --git a/src/routes/directory/[category]/+page.svelte b/src/routes/directory/[category]/+page.svelte new file mode 100644 index 0000000..c08fe8e --- /dev/null +++ b/src/routes/directory/[category]/+page.svelte @@ -0,0 +1,127 @@ + + + + + +
+
+ + + + +

+ {data.category.slice(0, 1).toUpperCase() + data.category.slice(1)} +

+ - + {#if !searchTerm} +

+ {data.svgs.length} SVGs +

+ {:else} +

+ {filteredSvgs.length} + search results +

+ {/if} +
+
+ + + {#each filteredSvgs as svg} + + {/each} + + +
diff --git a/src/routes/directory/[category]/+page.ts b/src/routes/directory/[category]/+page.ts new file mode 100644 index 0000000..5d7db34 --- /dev/null +++ b/src/routes/directory/[category]/+page.ts @@ -0,0 +1,48 @@ +import type { PageLoad } from "./$types"; +import type { iSVG } from "@/types/svg"; + +import { svgs } from "@/data/svgs"; +import { error } from "@sveltejs/kit"; +import { searchWithFuse } from "@/utils/searchWithFuse"; + +export const load: PageLoad = (async ({ params, url }) => { + const { category } = params; + const searchParam = url.searchParams.get("search") || ""; + + const svgsByCategory = svgs.filter((svg: iSVG) => { + if (Array.isArray(svg.category)) { + return svg.category.some( + (categoryItem) => categoryItem.toLowerCase() === category.toLowerCase(), + ); + } else { + return svg.category.toLowerCase() === category.toLowerCase(); + } + }); + + if (svgsByCategory.length === 0) { + throw error(404, "Category not found"); + } + + let filteredSvgs: iSVG[] = []; + + if (!searchParam) { + filteredSvgs = svgsByCategory; + } else { + if (searchParam.length < 3) { + filteredSvgs = svgsByCategory.filter((svg: iSVG) => + svg.title.toLowerCase().includes(searchParam.toLowerCase()), + ); + } else { + filteredSvgs = searchWithFuse(svgsByCategory) + .search(searchParam) + .map((result) => result.item); + } + } + + return { + category: category, + searchTerm: searchParam, + svgs: svgsByCategory, + filteredSvgs: filteredSvgs, + }; +}) satisfies PageLoad;