Merge branch 'main' into main

This commit is contained in:
Pablo Hdez
2023-12-24 00:50:12 +00:00
committed by GitHub
77 changed files with 5106 additions and 667 deletions
+23 -5
View File
@@ -6,13 +6,13 @@
:root {
--sb-track-color: rgb(229 229 229 / 0.5);
--sb-thumb-color: #d4d4d4;
--sb-size: 8px;
--sb-size: 10px;
}
.dark {
--sb-track-color: #171717;
--sb-thumb-color: #404040;
--sb-size: 8px;
--sb-size: 10px;
}
}
@@ -34,6 +34,18 @@
background: var(--sb-thumb-color);
}
aside::-webkit-scrollbar {
width: var(--sb-size);
}
aside::-webkit-scrollbar-track {
background: var(--sb-track-color);
}
aside::-webkit-scrollbar-thumb {
background: var(--sb-thumb-color);
}
nav::-webkit-scrollbar {
width: var(--sb-size);
}
@@ -48,9 +60,15 @@
}
@font-face {
font-family: 'General-Sans';
src: url('/fonts/GeneralSans-Variable.woff2') format('woff2');
font-weight: 200 700;
font-family: 'InterVariable';
src: url('/fonts/InterVariable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'GeistMono';
src: url('/fonts/GeistMonoVariableVF.woff2') format('woff2');
font-display: swap;
}
+9 -1
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width" />
<meta name="robots" content="index, follow" />
<meta name="author" content="@pheralb_" />
<meta name="description" content="A beautiful library with SVG logos" />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/images/logo.svg" />
@@ -23,13 +24,20 @@
<meta name="twitter:creator" content="@pheralb_" />
<meta name="twitter:image" content="https://svgl.vercel.app/images/screenshot.png" />
<!-- Analytics -->
<script
async
src="https://umami.pheralb.dev/script.js"
data-website-id="50de464e-cf2c-4b76-b5e8-21c9259bc7be"
></script>
<!-- Title -->
<title>A beautiful library with SVG logos - Svgl</title>
%sveltekit.head%
</head>
<body
data-sveltekit-preload-data="hover"
class="min-h-screen bg-light font-sans text-mini dark:bg-dark dark:text-white antialiased"
class="min-h-screen bg-white font-sans text-mini dark:bg-dark dark:text-white antialiased selection:bg-neutral-200 dark:selection:bg-neutral-700"
>
<div>%sveltekit.body%</div>
</body>
+1 -1
View File
@@ -42,7 +42,7 @@
on:blur={handleBlur}
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
class="relative flex items-center justify-center overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-900"
class="relative flex items-center justify-center overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900"
>
<div
class="pointer-events-none absolute -inset-px opacity-0 transition duration-300"
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts">
import { cn } from '@/utils/cn';
type methodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
export let method: methodType;
export let title: string;
export let description: string;
</script>
<div class={cn('border-2 border-neutral-100 dark:border-neutral-800 rounded-lg', 'p-4 mb-2')}>
<div class="flex items-center space-x-4 mb-4">
<p
class={cn(
'm-0 rounded-md font-medium px-1.5 py-0.5 text-sm leading-5',
method === 'GET' &&
' text-green-600 dark:text-green-500 bg-green-400/20 dark:bg-green-400/20',
method === 'POST' && ' text-blue-600 dark:text-blue-500 bg-blue-400/20 dark:bg-blue-400/20',
method === 'PUT' &&
' text-yellow-600 dark:text-yellow-500 bg-yellow-400/20 dark:bg-yellow-400/20',
method === 'PATCH' &&
' text-yellow-600 dark:text-yellow-500 bg-yellow-400/20 dark:bg-yellow-400/20',
method === 'DELETE' && ' text-red-600 dark:text-red-500 bg-red-400/20 dark:bg-red-400/20'
)}
>
{method}
</p>
<div class="flex flex-col space-y-0 m-0">
<h3 class="m-0 font-medium">{title}</h3>
<p class="mb-0 font-mono text-sm">{description}</p>
</div>
</div>
<slot />
</div>
+15
View File
@@ -0,0 +1,15 @@
<script lang="ts">
export let iconSize: number;
</script>
<svg
width={iconSize || 16}
height={iconSize || 16}
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
><path
d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z"
fill="#121212"
/>
</svg>
+60
View File
@@ -0,0 +1,60 @@
<svg
width="30"
name="SVGL Logo"
viewBox="0 0 512 512"
fill="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class=""
><rect
id="r4"
width="512"
height="512"
x="0"
y="0"
rx="128"
fill="#222"
stroke="#FFFFFF"
stroke-width="0"
stroke-opacity="100%"
paint-order="stroke"
></rect><rect
width="512"
height="512"
x="0"
y="0"
fill="url(#r6)"
rx="128"
style="mix-blend-mode: overlay;"
></rect><clipPath id="clip"><use xlink:href="#r4"></use></clipPath><defs
><linearGradient
id="r5"
gradientUnits="userSpaceOnUse"
gradientTransform="rotate(135)"
style="transform-origin: center center;"
><stop stop-color="#222"></stop><stop offset="1" stop-color="#222222"></stop></linearGradient
><radialGradient
id="r6"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(256) rotate(90) scale(512)"
><stop stop-color="white"></stop><stop offset="1" stop-color="white" stop-opacity="0"
></stop></radialGradient
></defs
><svg
xmlns="http://www.w3.org/2000/svg"
width="310"
height="310"
fill="#e8e8e8"
viewBox="0 0 256 256"
x="101"
y="101"
alignment-baseline="middle"
style="color: rgb(255, 255, 255);"
><path
d="M168,32H88A56.06,56.06,0,0,0,32,88v80a56.06,56.06,0,0,0,56,56h48a8.07,8.07,0,0,0,2.53-.41c26.23-8.75,76.31-58.83,85.06-85.06A8.07,8.07,0,0,0,224,136V88A56.06,56.06,0,0,0,168,32ZM48,168V88A40,40,0,0,1,88,48h80a40,40,0,0,1,40,40v40H184a56.06,56.06,0,0,0-56,56v24H88A40,40,0,0,1,48,168Zm96,35.14V184a40,40,0,0,1,40-40h19.14C191,163.5,163.5,191,144,203.14Z"
></path></svg
></svg
>

After

Width:  |  Height:  |  Size: 1.7 KiB

+118
View File
@@ -0,0 +1,118 @@
<script lang="ts">
export let currentPath: string;
import { cn } from '@/utils/cn';
import Logo from './logo.svelte';
import Theme from './theme.svelte';
import { ArrowUpRight, CloudyIcon, GithubIcon, TwitterIcon } from 'lucide-svelte';
import XIcon from './xIcon.svelte';
const socials = [
{
name: 'GitHub',
url: 'https://github.com/pheralb/svgl',
icon: GithubIcon
}
];
const externalLinks = [
{
name: 'API',
url: '/api',
icon: CloudyIcon,
external: false
},
{
name: 'Terminal',
url: 'https://github.com/pheralb/svgl?tab=readme-ov-file#-terminal',
icon: ArrowUpRight,
external: true
},
{
name: 'Submit logo',
url: 'https://github.com/pheralb/svgl#-getting-started',
icon: ArrowUpRight,
external: true
}
];
</script>
<nav
class={cn(
'dark:bg-neutral-900 bg-white',
'w-full px-5 py-4 border-b border-neutral-200 dark:border-neutral-800',
'sticky top-0 z-50',
'backdrop-blur-md opacity-95'
)}
>
<div class="flex items-center justify-between mx-auto">
<div class="flex items-center space-x-2">
<a href="/">
<div class="flex items-center space-x-2 hover:opacity-80 transition-opacity">
<svelte:component this={Logo} />
<span class="text-[19px] font-medium tracking-wide hidden md:block">svgl</span>
<p class="text-neutral-500 hidden md:block font-mono">v4.0</p>
</div>
</a>
</div>
<div class="flex items-center space-x-0 md:space-x-7">
<div
class="flex items-center md:space-x-4 divide-x divide-neutral-300 dark:divide-neutral-700"
>
{#each externalLinks as link}
<a
href={link.url}
target={link.external ? '_blank' : ''}
class={cn(
'flex items-center hover:opacity-80 transition-opacity text-[15px] pl-2 md:pl-3 group',
currentPath === link.url &&
'underline underline-offset-8 decoration-dotted decoration-neutral-500'
)}
>
{#if !link.external}
<svelte:component
this={link.icon}
size={16}
strokeWidth={1.5}
class="mr-2"
name={link.name}
/>
{/if}
<span class={cn('hidden md:block', !link.external && 'block')}>{link.name}</span>
{#if link.external}
<svelte:component
this={link.icon}
size={16}
name="External link"
strokeWidth={1.5}
class="ml-1 transition-transform duration-300 group-hover:translate-x-[1px] hidden md:block"
/>
{/if}
</a>
{/each}
</div>
<div class="flex items-center space-x-4">
<a
href="https://twitter.com/pheralb_"
target="_blank"
class="flex items-center space-x-1 hover:opacity-80 transition-opacity"
title="Twitter"
>
<XIcon iconSize={16} />
</a>
{#each socials as social}
<a
href={social.url}
target="_blank"
class="flex items-center space-x-1 hover:opacity-80 transition-opacity"
title={social.name}
>
<svelte:component this={social.icon} size={20} strokeWidth={1.5} name={social.name} />
</a>
{/each}
<Theme />
</div>
</div>
</div>
</nav>
+23 -17
View File
@@ -1,22 +1,28 @@
<script lang="ts">
export let notFoundTerm: string;
import FileSearch from 'phosphor-svelte/lib/FileMagnifyingGlass';
import ArrowUpRight from 'phosphor-svelte/lib/ArrowUpRight';
import { PackageOpen, ArrowUpRight } from 'lucide-svelte';
</script>
<div
class="mt-6 flex w-full flex-col items-center justify-center space-y-2 text-gray-600 dark:text-gray-400"
>
<FileSearch size={40} />
<p class="mt-1 text-xl font-medium">SVG not found</p>
<p class="text-lg">"{notFoundTerm}"</p>
<a
href="https://github.com/pheralb/svgl/issues/new"
target="_blank"
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
>
<span>Request SVG</span>
<ArrowUpRight size={16} />
</a>
<div class="mt-6 flex w-full flex-col items-center justify-center text-gray-600 dark:text-gray-400">
<PackageOpen size={40} class="mb-4" />
<p class="text-xl mb-1 font-medium">SVG not found</p>
<p class="text-md mb-4 font-mono">"{notFoundTerm}"</p>
<div class="flex items-center space-x-1">
<a
href="https://github.com/pheralb/svgl?tab=readme-ov-file#-getting-started"
target="_blank"
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
>
<span>Submit logo</span>
<ArrowUpRight size={16} />
</a>
<a
href="https://github.com/pheralb/svgl/issues/new"
target="_blank"
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
>
<span>Request SVG</span>
<ArrowUpRight size={16} />
</a>
</div>
</div>
+26 -24
View File
@@ -1,34 +1,36 @@
<script lang="ts">
import { SearchIcon } from 'lucide-svelte';
export let searchTerm: string;
export let placeholder: string = 'Search...';
export let clearSearch: () => void;
import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
import X from 'phosphor-svelte/lib/X';
</script>
<div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-neutral-500">
<div class="pointer-events-none">
<MagnifyingGlass size={18} weight={searchTerm ? 'duotone' : 'regular'} />
<div class="sticky top-[63px] z-50">
<div class="relative w-full text-[16px]">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-neutral-500">
<div class="pointer-events-none">
<SearchIcon size={20} strokeWidth={searchTerm ? 2.5 : 1.5} />
</div>
</div>
<input
type="text"
{placeholder}
autocomplete="off"
class="w-full border-b border-neutral-300 bg-white p-3 pl-11 placeholder-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:border-neutral-800 dark:bg-neutral-900 dark:focus:ring-neutral-700"
bind:value={searchTerm}
on:input
/>
{#if searchTerm.length > 0}
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<button
type="button"
class="focus:outline-none focus:ring-1 focus:ring-neutral-300"
on:click={clearSearch}
>
<X size={18} />
</button>
</div>
{/if}
</div>
<input
type="text"
{placeholder}
autocomplete="off"
class="w-full rounded-md border border-neutral-300 bg-neutral-200/50 p-3 pl-10 placeholder-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:border-neutral-800 dark:bg-neutral-700/10 dark:focus:ring-neutral-700"
bind:value={searchTerm}
on:input
/>
{#if searchTerm.length > 0}
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<button
type="button"
class="focus:outline-none focus:ring-1 focus:ring-neutral-300"
on:click={clearSearch}
>
<X size={18} />
</button>
</div>
{/if}
</div>
+115 -37
View File
@@ -1,15 +1,20 @@
<script lang="ts">
import type { iSVG } from '../types/svg';
import download from 'downloadjs';
import { toast } from 'svelte-sonner';
import type { iSVG } from '../types/svg';
import { MIMETYPE, getSvgContent } from '../utils/getSvgContent';
import jszip from 'jszip';
// Utils:
import { MIMETYPE, getSvgContent } from '@/utils/getSvgContent';
import { flyAndScale } from '@/utils/flyAndScale';
// Icons:
import DownloadSimple from 'phosphor-svelte/lib/DownloadSimple';
import Copy from 'phosphor-svelte/lib/Copy';
import Link from 'phosphor-svelte/lib/Link';
import Star from 'phosphor-svelte/lib/Star';
import { CopyIcon, DownloadIcon, LinkIcon, PackageIcon, PaintBucket } from 'lucide-svelte';
// Main Card:
import CardSpotlight from './cardSpotlight.svelte';
import { DropdownMenu } from 'bits-ui';
// Props:
export let svgInfo: iSVG;
@@ -17,7 +22,33 @@
// Download SVG:
const downloadSvg = (url?: string) => {
download(url || '');
toast.success('Downloading...');
toast.success(`Downloading...`, {
description: `${svgInfo.title} - ${svgInfo.category}`
});
};
// Download all variants:
const downloadAllVariants = async ({ route }: iSVG) => {
const zip = new jszip();
if (typeof route === 'string') {
downloadSvg(route);
return;
}
const lightSvg = await getSvgContent(route.light, false);
const darkSvg = await getSvgContent(route.dark, false);
zip.file(`${svgInfo.title}.svg`, lightSvg);
zip.file(`${svgInfo.title}.dark.svg`, darkSvg);
zip.generateAsync({ type: 'blob' }).then((content) => {
download(content, `${svgInfo.title}.zip`, 'application/zip');
});
toast.success('Downloading light & dark variants...', {
description: `${svgInfo.title} - ${svgInfo.category}`
});
};
// Copy SVG to clipboard:
@@ -33,31 +64,43 @@
await navigator.clipboard.writeText(content);
}
toast.success('Copied to clipboard!', {
icon: Star,
description: `${svgInfo.title} - ${svgInfo.category}`
});
};
// Icon Stroke & Size:
let iconStroke = 1.8;
let iconSize = 16;
</script>
<CardSpotlight>
<div class="flex flex-col items-center justify-center rounded-md p-4">
<!-- Image -->
<img
class="hidden dark:block mb-4 mt-2 h-10"
src={typeof svgInfo.route !== 'string' ? svgInfo.route.dark : svgInfo.route}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
<img
class="block dark:hidden mb-4 mt-2 h-10"
src={typeof svgInfo.route !== 'string' ? svgInfo.route.light : svgInfo.route}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
<!-- Title -->
<div class="mb-3 flex flex-col items-center justify-center">
<p class="truncate text-[15px] font-medium text-balance text-center">{svgInfo.title}</p>
<p class="truncate text-[15px] font-medium text-balance text-center select-all">
{svgInfo.title}
</p>
<a
href={`/directory/${svgInfo.category.toLowerCase()}`}
class="text-sm lowercase text-neutral-500 hover:underline">{svgInfo.category}</a
class="text-sm lowercase text-neutral-500 hover:underline font-mono">{svgInfo.category}</a
>
</div>
<!-- Actions -->
<div class="flex items-center space-x-1">
<button
title="Copy to clipboard"
@@ -85,43 +128,78 @@
}}
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<Copy size={17} />
<CopyIcon size={iconSize} strokeWidth={iconStroke} />
</button>
<button
title="Download"
on:click={() => {
const svgHasTheme = typeof svgInfo.route !== 'string';
if (!svgHasTheme) {
downloadSvg(
typeof svgInfo.route === 'string'
? svgInfo.route
: "Something went wrong. Couldn't copy the SVG."
);
return;
}
{#if typeof svgInfo.route !== 'string'}
<DropdownMenu.Root>
<DropdownMenu.Trigger
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-full shadow-md max-w-[229px] rounded-md border border-neutral-100 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-1 py-1.5 shadow-popover"
transition={flyAndScale}
sideOffset={3}
>
<DropdownMenu.Item
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
on:click={() => {
downloadAllVariants(svgInfo);
}}
>
<PackageIcon class="mr-2" size={18} />
<p>Light & Dark variants</p>
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
on:click={() => {
const svgHasTheme = typeof svgInfo.route !== 'string';
const dark = document.documentElement.classList.contains('dark');
if (!svgHasTheme) {
downloadSvg(
typeof svgInfo.route === 'string'
? svgInfo.route
: "Something went wrong. Couldn't copy the SVG."
);
return;
}
downloadSvg(
typeof svgInfo.route !== 'string'
? dark
? svgInfo.route.dark
: svgInfo.route.light
: svgInfo.route
);
}}
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<DownloadSimple size={17} />
</button>
const dark = document.documentElement.classList.contains('dark');
downloadSvg(
typeof svgInfo.route !== 'string'
? dark
? svgInfo.route.dark
: svgInfo.route.light
: svgInfo.route
);
}}
>
<PaintBucket class="mr-2" size={18} />
Only {document.documentElement.classList.contains('dark') ? 'dark' : 'light'} variant
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
{:else}
<button
title="Download SVG"
on:click={() => {
if (typeof svgInfo.route === 'string') downloadSvg(svgInfo.route);
}}
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</button>
{/if}
<a
href={svgInfo.url}
title="Website"
target="_blank"
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<Link size={17} />
<LinkIcon size={iconSize} strokeWidth={iconStroke} />
</a>
</div>
</div>
+4 -10
View File
@@ -35,12 +35,10 @@
}
// Icons:
import Moon from 'phosphor-svelte/lib/Moon';
import Sun from 'phosphor-svelte/lib/Sun';
import { MoonIcon, SunIcon } from 'lucide-svelte';
</script>
<svelte:head>
<!-- set dark mode class based on user preference / device settings (in head to avoid FOUC) -->
<script>
if (
localStorage.theme === 'dark' ||
@@ -53,15 +51,11 @@
</script>
</svelte:head>
<button
on:click={toggle}
class="duration-100 hover:-translate-y-0.5 focus:outline-none"
aria-label="Toggle dark mode"
>
<button on:click={toggle} aria-label="Toggle dark mode" class="hover:opacity-80">
<!-- moon icon -->
{#if dark}
<Sun size={18} />
<SunIcon size={20} strokeWidth={1.5} />
{:else}
<Moon size={18} />
<MoonIcon size={20} strokeWidth={1.5} />
{/if}
</button>
+37
View File
@@ -0,0 +1,37 @@
<script lang="ts">
import { AlertTriangleIcon, Check } from 'lucide-svelte';
import { browser } from '$app/environment';
let warning = false;
const initialValue = browser ? window.localStorage.getItem('warning') : true;
</script>
{#if !warning && !initialValue}
<div
class="flex items-center md:flex-row flex-col md:space-x-2 space-x-0 space-y-2 md:space-y-0 py-2 px-3 bg-neutral-100 dark:bg-neutral-800/40 text-neutral-700 dark:text-neutral-300"
>
<AlertTriangleIcon size={18} strokeWidth={2} class="mr-1" />
<p>
Each svg provides the link to the product or company that owns it, <strong
>please contact them</strong
>
if you are going to use their logo. If you are the owner of an svg and do not want it to appear
here, please
<a
target="_blank"
class="underline underline-offset-4 decoration-dotted decoration-neutral-500"
href="https://github.com/pheralb/svgl/issues/new">create an issue</a
> on Github.
</p>
<button
class="ml-auto transition-colors flex items-center space-x-1 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40 bg-neutral-200/50 dark:bg-neutral-800/40 text-neutral-700 dark:text-neutral-300"
on:click={() => {
localStorage.setItem('warning', 'true');
warning = true;
}}
>
<Check size={14} strokeWidth={2} />
<span>Accept</span>
</button>
</div>
{/if}
+16
View File
@@ -0,0 +1,16 @@
<script lang="ts">
export let iconSize: number;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize || 16}
height={iconSize || 16}
name="Twitter"
fill="none"
viewBox="0 0 1200 1227"
><path
fill="currentColor"
d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
/>
</svg>
+1 -2
View File
@@ -2,6 +2,5 @@ import type { iSVG } from '@/types/svg';
import { svgs } from './svgs';
export const svgsData = svgs.map((svg: iSVG, index: number) => {
svg.id = index;
return svg;
return { id: index, ...svg };
});
+122 -50
View File
@@ -46,6 +46,12 @@ export const svgs: iSVG[] = [
route: '/library/vue.svg',
url: 'https://vuejs.org/'
},
{
title: "Vuetify",
category: "Library",
route: "/library/vuetify.svg",
url: "https://vuetifyjs.com/"
},
{
title: 'Nuxt',
category: 'Framework',
@@ -325,12 +331,6 @@ export const svgs: iSVG[] = [
route: '/library/mongodb.svg',
url: 'https://www.mongodb.com/'
},
{
title: 'Babel',
category: 'Compiler',
route: '/library/babel.svg',
url: 'https://babeljs.io'
},
{
title: 'Moon',
category: 'Framework',
@@ -436,6 +436,15 @@ export const svgs: iSVG[] = [
},
url: 'https://arc.dev'
},
{
title: 'Arc',
category: 'Fintech',
route: {
light: '/library/arc_fintech_light.svg',
dark: '/library/arc_fintech_dark.svg'
},
url: 'https://arc.tech'
},
{
title: 'Qwik',
category: 'Framework',
@@ -799,12 +808,6 @@ export const svgs: iSVG[] = [
route: '/library/swift.svg',
url: 'https://swift.org/'
},
{
title: 'Testing Library',
category: 'Framework',
route: '/library/testinglibrary.svg',
url: 'https://testing-library.com/'
},
{
title: 'TypeORM',
category: 'Database',
@@ -858,7 +861,7 @@ export const svgs: iSVG[] = [
},
{
title: 'Developer Student Club',
category: 'Education',
category: 'Community',
route: '/library/gdsc.svg',
url: 'https://gdsc.community.dev/'
},
@@ -888,7 +891,7 @@ export const svgs: iSVG[] = [
},
{
title: 'midudev',
category: 'Education',
category: 'Community',
route: '/library/midudev.svg',
url: 'https://midu.dev'
},
@@ -943,12 +946,6 @@ export const svgs: iSVG[] = [
route: '/library/jetbrains-space.svg',
url: 'https://www.jetbrains.com/space/'
},
{
title: 'Gin',
category: 'Framework',
route: '/library/gin.svg',
url: 'https://gin-gonic.com/'
},
{
title: 'Stimulus',
category: 'Framework',
@@ -1327,12 +1324,6 @@ export const svgs: iSVG[] = [
route: '/library/bnb.svg',
url: 'https://www.bnbchain.org/'
},
{
title: 'Cardano',
category: 'Crypto',
route: '/library/ada.svg',
url: 'https://cardano.org/'
},
{
title: 'TRON',
category: 'Crypto',
@@ -1351,6 +1342,18 @@ export const svgs: iSVG[] = [
route: '/library/matic.svg',
url: 'https://polygon.technology/'
},
{
title: 'Algorand',
category: 'Crypto',
route: '/library/algorand.svg',
url: 'https://algorand.org/'
},
{
title: 'Tether',
category: 'Crypto',
route: '/library/tether.svg',
url: 'https://tether.to/'
},
{
title: 'X',
category: 'Social',
@@ -1552,16 +1555,13 @@ export const svgs: iSVG[] = [
route: '/library/rowy.svg',
url: 'https://www.rowy.io/'
},
{
title: 'BuildShip',
category: 'AI',
route: '/library/buildship.svg',
url: 'https://buildship.com/'
},
{
title: 'Cal.com',
category: 'Software',
route: '/library/cal.svg',
route: {
light: '/library/cal.svg',
dark: '/library/cal_dark.svg'
},
url: 'https://cal.com'
},
{
@@ -1590,7 +1590,7 @@ export const svgs: iSVG[] = [
},
{
title: 'Patreon',
category: 'Software',
category: 'Social',
route: {
light: '/library/patreon.svg',
dark: '/library/patreon_dark.svg'
@@ -1735,12 +1735,6 @@ export const svgs: iSVG[] = [
},
url: 'https://axiom.co/'
},
{
title: 'ArtisanLabs',
category: 'Software',
route: '/library/ArtisanLabs.svg',
url: 'https://artisanlabs.io'
},
{
title: 'Django',
category: 'Framework',
@@ -1779,7 +1773,7 @@ export const svgs: iSVG[] = [
},
{
title: 'Pinterest',
category: 'Software',
category: 'Social',
route: '/library/pinterest.svg',
url: 'https://pinterest.com/'
},
@@ -1819,13 +1813,19 @@ export const svgs: iSVG[] = [
{
title: 'Penpot',
category: 'Design',
route: '/library/penpot.svg',
route: {
light: '/library/penpot.svg',
dark: '/library/penpot_dark.svg'
},
url: 'https://penpot.app/'
},
{
title: 'Sketch',
category: 'Design',
route: '/library/sketch.svg',
route: {
light: '/library/sketch_light.svg',
dark: '/library/sketch.svg'
},
url: 'https://www.sketch.com/'
},
{
@@ -1840,12 +1840,6 @@ export const svgs: iSVG[] = [
route: '/library/ubuntu.svg',
url: 'https://ubuntu.com/'
},
{
title: 'Meta',
category: 'Social',
route: '/library/meta.svg',
url: 'https://meta.com/'
},
{
title: 'Cypress',
category: 'Framework',
@@ -1889,7 +1883,13 @@ export const svgs: iSVG[] = [
url: 'https://www.jetbrains.com/phpstorm/'
},
{
title: 'JetBrains PyCharm',
title: 'MonkeyType',
category: 'Typing',
route: '/library/monkeytype.svg',
url: 'https://monkeytype.com/'
},
{
title: 'Raycast',
category: 'Software',
route: '/library/pycharm.svg',
url: 'https://www.jetbrains.com/pycharm/'
@@ -1978,4 +1978,76 @@ export const svgs: iSVG[] = [
route: '/library/trustpilot.svg',
url: 'https://www.trustpilot.com'
},
{
title: 'Raycast',
category: 'Software',
route: '/library/raycast.svg',
url: 'https://raycast.com/'
},
{
title: 'Procure',
category: 'Marketplace',
route: '/library/procure.svg',
url: 'https://procure.biz/'
},
{
title: 'Julia',
category: 'Language',
route: '/library/julia.svg',
url: 'https://julialang.org/'
},
{
title: 'SWC',
category: 'Compiler',
route: '/library/swc.svg',
url: 'https://swc.rs/'
},
{
title: 'PlayStation',
category: 'Software',
route: '/library/playstation.svg',
url: 'https://www.playstation.com/'
},
{
title: 'Xbox',
category: 'Software',
route: '/library/xbox.svg',
url: 'https://www.xbox.com/'
},
{
title: 'Cody',
category: 'AI',
route: '/library/cody.svg',
url: 'https://about.sourcegraph.com/'
},
{
title: 'Sourcegraph',
category: 'AI',
route: '/library/sourcegraph.svg',
url: 'https://about.sourcegraph.com/'
},
{
title: 'Claude AI',
category: 'AI',
route: '/library/claude-ai.svg',
url: 'https://claude.ai/'
},
{
title: 'Perplexity AI',
category: 'AI',
route: '/library/perplexity.svg',
url: 'https://perplexity.ai/'
},
{
title: 'Spring',
category: 'Framework',
route: '/library/spring.svg',
url: 'https://spring.io/'
},
{
"title": "Directus",
"category": "CMS",
"route": "/library/directus.svg",
"url": "https://directus.io/"
},
];
+181
View File
@@ -0,0 +1,181 @@
---
title: API Reference
description: The API reference is a detailed documentation of all the endpoints available in the SVGL API.
---
<script>
import Endpoint from '../components/endpoints.svelte';
</script>
## Introduction
SVGL API is a RESTFul API that allows you to get all the information of the SVGs that are in the repository.
## Limitations
The API is currently open to everyone and does not require any authentication. However, to prevent abusive use of the API, there is a limit to the number of requests: **5 requests** with **60s** cooldown.
## Base URL
The base URL for the API is:
```bash
https://svgl.app/api/svgs
# or
https://svgl.app/api/categories
```
## Typescript usage
- For SVGs:
```ts
export interface svg {
id: number;
title: string;
category: string;
route:
| string
| {
dark: string;
light: string;
};
url: string;
}
```
- For categories:
```ts
export interface category {
category: string;
total: number;
}
```
## Endpoints
<Endpoint title="Get all SVGs" method="GET" description="Returns all the SVGs in the repository.">
```bash
/api/svgs
```
<p></p>
```json
// Returns:
[
{
"id": 0,
"title": "Discord",
"category": "Software",
"route": "https://svgl.app/library/discord.svg",
"url": "https://discord.com/"
},
...
]
```
</Endpoint>
<Endpoint title="Get a limited number of SVGs" method="GET" description="Returns a limited number of SVGs in the repository. Start from the first SVG.">
```bash
/api/svgs?limit=10
```
<p></p>
```json
// Returns:
[
{
"id": 0,
"title": "Discord",
"category": "Software",
"route": "https://svgl.app/library/discord.svg",
"url": "https://discord.com/"
},
...
]
```
</Endpoint>
<Endpoint title="Filter SVGs by category" method="GET" description="Returns all the SVGs in the repository that match the category.">
```bash
/api/svgs?category=software
```
<p></p>
```json
// Returns:
[
{
"id": 0,
"title": "Discord",
"category": "Software",
"route": "https://svgl.app/library/discord.svg",
"url": "https://discord.com/"
},
...
]
```
The list of categories is available [here](https://github.com/pheralb/svgl/blob/main/src/types/categories.ts) (except for the _all_ category).
</Endpoint>
<Endpoint title="Get only categories" method="GET" description="Returns only categories with the number of SVGs in each category.">
```bash
/api/categories
```
<p></p>
```json
// Returns:
[
{
"category": "Software",
"total": 97
},
{
"category": "Library",
"total": 25
},
...
]
```
</Endpoint>
<Endpoint title="Search SVGs by name" method="GET" description="Returns all the SVGs in the repository that match the name.">
```bash
/api/svgs?search=axiom
```
<p></p>
```json
// Returns:
[
{
"id": 267,
"title": "Axiom",
"category": "Software",
"route": {
"light": "https://svgl.app/library/axiom-light.svg",
"dark": "https://svgl.app/library/axiom-dark.svg"
},
"url": "https://axiom.co/"
}
]
```
</Endpoint>
+39 -84
View File
@@ -13,42 +13,34 @@
// Icons:
import Heart from 'phosphor-svelte/lib/Heart';
import ArrowUpRight from 'phosphor-svelte/lib/ArrowUpRight';
import ArrowLeft from 'phosphor-svelte/lib/ArrowLeft';
import Star from 'phosphor-svelte/lib/Star';
import GithubLogo from 'phosphor-svelte/lib/GithubLogo';
import Box from 'phosphor-svelte/lib/Cube';
import { ArrowUpRight } from 'lucide-svelte';
// Toaster:
import { Toaster } from 'svelte-sonner';
// Components for all pages:
import Transition from '@/components/transition.svelte';
import Container from '@/components/container.svelte';
import Theme from '@/components/theme.svelte';
import Warning from '@/components/warning.svelte';
// Layout:
import Navbar from '@/components/navbar.svelte';
import { cn } from '@/utils/cn';
</script>
<Navbar currentPath={data.pathname} />
<main>
<nav
class="z-50 w-full overflow-y-auto overflow-x-hidden border-b border-neutral-300 dark:border-neutral-800 md:fixed md:left-0 md:top-0 md:h-full md:w-60 md:border-none md:pb-10"
<aside
class={cn(
'z-50 w-full overflow-y-auto overflow-x-hidden',
'dark:border-neutral-800 md:fixed md:left-0 md:h-full md:w-56 md:pb-10',
'bg-white dark:bg-neutral-900',
'backdrop-blur-md opacity-95',
'border-r border-neutral-200 dark:border-neutral-800'
)}
>
<div class="px-6 py-6">
<div class="mb-3 border-b border-neutral-300 pb-3 dark:border-neutral-700/40">
<div class="flex items-center justify-between">
<a href="/">
<div
class="flex items-center space-x-2 duration-150 hover:text-neutral-500 dark:hover:text-neutral-300"
>
<h3 class="text-xl font-medium">svgl</h3>
<p class="text-neutral-500">v3.2.2</p>
</div>
</a>
<Theme />
</div>
<p class="mt-2 font-medium text-neutral-400">✨ Optimized SVGs for web</p>
</div>
<div
class="flex items-center space-x-1 overflow-y-auto border-b border-neutral-300 pb-3 dark:border-neutral-700/40 md:mb-3 md:flex-col md:space-x-0 md:space-y-1 md:overflow-y-visible"
<div class="md:px-6 md:py-6">
<nav
class="flex items-center space-x-1 overflow-y-auto border-b border-neutral-300 dark:border-neutral-700/40 md:mb-3 md:flex-col md:space-x-0 md:space-y-1 md:overflow-y-visible px-5 md:px-0 pb-2 pt-3 md:pt-0"
>
<a
href="/"
@@ -63,83 +55,46 @@
{#each categories.sort() as category}
<a
href={`/directory/${category.toLowerCase()}`}
class={`flex w-full items-center rounded-md p-2 transition-none duration-100 text-neutral-600 hover:text-dark dark:hover:text-white dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700/40
${
data.pathname === `/directory/${category.toLowerCase()}`
? 'bg-neutral-200 dark:bg-neutral-700/30 font-medium dark:text-white text-dark'
: ''
}`}
data-sveltekit-preload-data>{category}</a
class={cn(
'flex w-full items-center justify-between rounded-md p-2 transition-none duration-100 text-neutral-600 hover:text-dark dark:hover:text-white dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700/40',
data.pathname === `/directory/${category.toLowerCase()}`
? 'bg-neutral-200 dark:bg-neutral-700/30 font-medium dark:text-white text-dark'
: ''
)}
>
<span>{category}</span>
</a>
{/each}
</div>
<div
class="mt-3 flex flex-row items-center space-x-2 border-b border-neutral-300 pb-3 dark:border-neutral-700/40 md:mt-0 md:flex-col md:space-x-0 md:space-y-1"
>
<a
href="https://github.com/pheralb/svgl#-getting-started"
target="_blank"
class="flex w-full items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<div><Box size={18} /></div>
<span>Submit logo</span>
<div><ArrowUpRight size={12} /></div>
</a>
<a
href="https://github.com/pheralb/svgl"
target="_blank"
class="flex w-full items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
<div class="flex items-center space-x-2">
<div><GithubLogo size={18} /></div>
<span class="flex items-center justify-center">Repository</span>
{#if data.stars}
<div class="flex items-center space-x-1 text-neutral-600 dark:text-neutral-400">
<div>
<Star size={14} weight="duotone" class="text-yellow-700 dark:text-yellow-500" />
</div>
<span>{data.stars}</span>
</div>
{/if}
<div><ArrowUpRight size={12} /></div>
</div>
</a>
</div>
</nav>
<a
href="https://twitter.com/pheralb_"
target="_blank"
class="mt-5 flex items-center space-x-2 duration-100 hover:text-dark dark:text-neutral-400 dark:hover:text-white"
class="group mt-5 md:flex hidden items-center space-x-2 duration-100 hover:text-dark dark:text-neutral-400 dark:hover:text-white"
>
<Heart color="#991b1b" size={18} weight={'duotone'} />
<div class="flex items-center space-x-1">
<p class="text-muted text-sm">Created by pheralb</p>
<ArrowUpRight size={12} />
<ArrowUpRight
size={14}
class="transition-transform duration-300 group-hover:-translate-y-[1px]"
/>
</div>
</a>
</div>
</nav>
<div class="py-2 md:ml-60 md:py-6">
{#if data.pathname !== '/'}
<Container>
<a href="/">
<div
class="flex items-center space-x-2 duration-100 hover:text-neutral-500 dark:text-neutral-400 dark:hover:text-white"
>
<ArrowLeft size={20} />
<span>View all</span>
</div>
</a>
</Container>
{/if}
</aside>
<div class="ml-0 md:ml-56 pb-6">
<Warning />
<Transition pathname={data.pathname}>
<slot />
</Transition>
<Toaster
position="bottom-right"
toastOptions={{
style: `background-color: #171717;
class: 'font-sans',
descriptionClass: 'font-mono',
style: `background-color: #262626;
color: #ffff;
border-radius: 0.4rem; border: 1px solid #262626;`
border-radius: 0.4rem; border: 1px solid #121212;`
}}
/>
</div>
+25 -22
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import type { iSVG } from '@/types/svg';
import { cn } from '@/utils/cn';
// Get all svgs:
import { svgsData } from '@/data';
@@ -13,19 +14,22 @@
import NotFound from '@/components/notFound.svelte';
// Icons:
import ArrowsClockWise from 'phosphor-svelte/lib/ArrowsClockwise';
import ArrowSquareOut from 'phosphor-svelte/lib/ArrowSquareOut';
import { ArrowDownUpIcon, ArrowUpDownIcon } from 'lucide-svelte';
let sorted: boolean = false;
// Search:
let searchTerm = '';
let filteredSvgs: iSVG[] = [];
// Order by last added:
if (searchTerm.length === 0) {
filteredSvgs = allSvgs.sort((a: iSVG, b: iSVG) => {
return b.id! - a.id!;
});
}
// Search svgs:
const searchSvgs = () => {
return (filteredSvgs = allSvgs.filter((svg: iSVG) => {
let svgTitle = svg.title.toLowerCase();
@@ -33,14 +37,13 @@
}));
};
// Clear search:
const clearSearch = () => {
searchTerm = '';
searchSvgs();
};
// Sort:
let sorted: boolean = false;
const sort = () => {
if (sorted) {
sortByLatest();
@@ -52,7 +55,7 @@
// Sort alphabetically:
const sortAlphabetically = () => {
filteredSvgs = filteredSvgs.sort((a: iSVG, b: iSVG) => {
filteredSvgs = allSvgs.sort((a: iSVG, b: iSVG) => {
return a.title.localeCompare(b.title);
});
};
@@ -69,29 +72,29 @@
<title>A beautiful library with SVG logos - Svgl</title>
</svelte:head>
<Search
bind:searchTerm
on:input={searchSvgs}
clearSearch={() => clearSearch()}
placeholder={`Search ${filteredSvgs.length} logos...`}
/>
<Container>
<Search
bind:searchTerm
on:input={searchSvgs}
clearSearch={() => clearSearch()}
placeholder={`Search ${filteredSvgs.length} logos...`}
/>
<div class={`flex items-center justify-between my-4 ${filteredSvgs.length === 0 && 'hidden'}`}>
<div class="flex items-center justify-end mb-4">
<button
class="flex items-center justify-center space-x-2 rounded-md py-1.5 text-sm font-medium text-neutral-500 dark:text-neutral-400 hover:text-dark dark:hover:text-white duration-100 transition-colors"
class={cn(
'flex items-center justify-center space-x-1 rounded-md px-3 py-1.5 text-sm font-medium hover:opacity-80 transition-opacity',
filteredSvgs.length === 0 && 'hidden'
)}
on:click={() => sort()}
>
<ArrowsClockWise size={14} />
{#if sorted}
<ArrowDownUpIcon size={16} strokeWidth={2} class="mr-1" />
{:else}
<ArrowUpDownIcon size={16} strokeWidth={2} class="mr-1" />
{/if}
<span>{sorted ? 'Sort by latest' : 'Sort alphabetically'}</span>
</button>
<a
class="flex items-center justify-center space-x-2 rounded-md py-1.5 text-sm font-medium text-neutral-500 dark:text-neutral-400 hover:text-dark dark:hover:text-white duration-100 transition-colors"
href="https://github.com/pheralb/svgl?tab=readme-ov-file#-getting-started"
target="_blank"
>
<span>Submit SVG</span>
<ArrowSquareOut size={14} />
</a>
</div>
<Grid>
{#each filteredSvgs as svg}
+49
View File
@@ -0,0 +1,49 @@
<script>
import { cn } from '@/utils/cn';
export let data;
</script>
<svelte:head>
<title>{data.meta.title} - SVGL</title>
<meta property="og:type" content="article" />
<meta property="og:title" content={data.meta.title} />
<meta property="og:description" content={data.meta.description} />
</svelte:head>
<section
class="bg-white dark:bg-neutral-900 bg-[url('/images/hero-pattern_light.svg')] dark:bg-[url('/images/hero-pattern_dark.svg')]"
>
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-20 z-10 relative">
<div class="flex items-center space-x-4 text-center justify-center">
<h1
class="mb-4 text-4xl font-bold tracking-tight leading-none text-neutral-900 md:text-5xl lg:text-6xl dark:text-white"
>
API Reference
</h1>
<span class="relative inline-block overflow-hidden rounded-full p-[1px] shadow-sm">
<span
class="absolute inset-[-1000%] animate-[spin_4s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#f4f4f5_0%,#f4f4f5_50%,#737373_100%)] dark:bg-[conic-gradient(from_90deg_at_50%_50%,#121212_0%,#121212_50%,#737373_100%)]"
/>
<div
class="inline-flex h-full w-full cursor-default items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-900 px-3 py-1 text-xs font-medium dark:text-white backdrop-blur-3xl border border-neutral-100 dark:border-neutral-800 font-mono"
>
beta
</div>
</span>
</div>
<p class="text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48 dark:text-gray-200">
The API reference is a detailed documentation of all the endpoints available in the API.
</p>
</div>
</section>
<article
class={cn(
'prose dark:prose-invert',
'mx-auto py-10 px-4 max-w-3xl',
'prose-h2:font-medium',
'prose-pre:m-0'
)}
>
<svelte:component this={data.content} />
</article>
+14
View File
@@ -0,0 +1,14 @@
import { error } from '@sveltejs/kit';
export async function load() {
try {
const documentTitle = 'api';
const post = await import(`../../docs/${documentTitle}.md`);
return {
content: post.default,
meta: post.metadata
};
} catch (e) {
throw error(404, `Could not find this page`);
}
}
+38
View File
@@ -0,0 +1,38 @@
import type { RequestEvent } from './$types';
import { json } from '@sveltejs/kit';
import { ratelimit } from '@/server/redis';
// Data:
import { svgs } from '@/data/svgs';
export const GET = async ({ request }: RequestEvent) => {
const categories = svgs
.map((svg) => svg.category)
.filter((category, index, array) => array.indexOf(category) === index);
const ip = request.headers.get('x-forwarded-for') ?? '';
const { success, reset } = await ratelimit.limit(ip);
// Error 429 | If rate limit is exceeded:
if (!success) {
const now = Date.now();
const retryAfter = Math.floor((reset - now) / 1000);
return new Response('Too Many Requests', {
status: 429,
headers: {
'Retry-After': retryAfter.toString()
}
});
}
// Status 200 | If limit is a number:
return json(
categories.map((category) => {
return {
category,
total: svgs.filter((svg) => svg.category === category).length
};
}),
{ status: 200 }
);
};
+120
View File
@@ -0,0 +1,120 @@
import type { RequestEvent } from './$types';
import type { iSVG } from '@/types/svg';
import { error, json } from '@sveltejs/kit';
import { ratelimit } from '@/server/redis';
// Data:
import { svgsData } from '@/data';
export const GET = async ({ url, request }: RequestEvent) => {
const fullUrl = url.origin ?? 'svgl.vercel.app';
const ip = request.headers.get('x-forwarded-for') ?? '';
const { success, reset } = await ratelimit.limit(ip);
// Error 429 | If rate limit is exceeded:
if (!success) {
const now = Date.now();
const retryAfter = Math.floor((reset - now) / 1000);
return new Response('Too Many Requests', {
status: 429,
headers: {
'Retry-After': retryAfter.toString()
}
});
}
// Params:
const getLimitParams = url.searchParams.get('limit');
const getCategoryParams = url.searchParams.get('category');
const getSearchParams = url.searchParams.get('search');
// Add full route to svgs:
const fullRouteSvgsData: iSVG[] = svgsData.map((svg) => {
if (typeof svg.route === 'object' && svg.route !== null) {
return {
...svg,
route: {
light: `${fullUrl}${svg.route.light}`,
dark: `${fullUrl}${svg.route.dark}`
}
};
} else if (typeof svg.route === 'string') {
return {
...svg,
route: `${fullUrl}${svg.route}`
};
}
return svg;
});
// Status 200 | If no limit is provided, return all svgs:
if (!getLimitParams && !getCategoryParams && !getSearchParams) {
return json(fullRouteSvgsData, { status: 200 });
}
const limit = Number(getLimitParams);
const category = getCategoryParams;
if (category) {
const categorySvgs = fullRouteSvgsData.filter((svg) => {
return svg.category === category.charAt(0).toUpperCase() + category.slice(1);
});
// Error 400 | If category does not exist:
if (categorySvgs.length === 0) {
error(400, {
message: 'Category does not exist.'
});
}
if (!getLimitParams) {
return json(categorySvgs, { status: 200 });
}
return json(categorySvgs.slice(0, limit), { status: 200 });
}
if (getSearchParams) {
const searchSvgs = fullRouteSvgsData.filter((svg) => {
return svg.title.toLowerCase().includes(getSearchParams.toLowerCase());
});
// Error 400 | If search does not exist:
if (searchSvgs.length === 0) {
error(400, {
message: 'Search does not exist.'
});
}
if (!getLimitParams) {
return json(searchSvgs, { status: 200 });
}
return json(searchSvgs.slice(0, limit), { status: 200 });
}
// Error 400 | if limit is not a number:
if (isNaN(limit)) {
error(400, {
message: 'Limit must be a number.'
});
}
// Error 400 | If limit is not positive:
if (limit < 1) {
error(400, {
message: 'Limit must be a positive number.'
});
}
// Error 400 | If limit is greater than the number of svgs:
if (limit > fullRouteSvgsData.length) {
error(400, {
message: 'Limit is greater than the number of svgs.'
});
}
// Status 200 | If limit is a number:
return json(fullRouteSvgsData.slice(0, limit), { status: 200 });
};
@@ -0,0 +1,16 @@
<script>
import Container from '@/components/container.svelte';
import { ArrowLeft } from 'lucide-svelte';
</script>
<Container>
<a href="/">
<div
class="flex items-center space-x-2 duration-100 hover:text-neutral-500 dark:text-neutral-400 dark:hover:text-white group md:mt-2"
>
<ArrowLeft size={20} class="group-hover:-translate-x-[2px] group-hover:duration-200" />
<span>View all</span>
</div>
</a>
</Container>
<slot />
+14
View File
@@ -0,0 +1,14 @@
import { Redis } from '@upstash/redis';
import { Ratelimit } from '@upstash/ratelimit';
import { UPSTASH_REDIS_TOKEN, UPSTASH_REDIS_URL, SVGL_API_REQUESTS } from '$env/static/private';
const redis = new Redis({
url: UPSTASH_REDIS_URL,
token: UPSTASH_REDIS_TOKEN
});
export const ratelimit = new Ratelimit({
redis: redis,
limiter: Ratelimit.slidingWindow(Number(SVGL_API_REQUESTS), '60s'),
analytics: true
});
+5 -1
View File
@@ -14,4 +14,8 @@ export type tCategory =
| 'Browser'
| 'Language'
| 'Education'
| 'Design';
| 'Design'
| 'Typing'
| 'Community'
| 'Marketplace'
| 'Fintech';
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+43
View File
@@ -0,0 +1,43 @@
import { cubicOut } from 'svelte/easing';
import type { TransitionConfig } from 'svelte/transition';
type FlyAndScaleParams = {
y?: number;
x?: number;
start?: number;
duration?: number;
};
export const flyAndScale = (
node: Element,
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => {
const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB;
const percentage = (valueA - minA) / (maxA - minA);
const valueB = percentage * (maxB - minB) + minB;
return valueB;
};
const styleToString = (style: Record<string, number | string | undefined>): string => {
return Object.keys(style).reduce((str, key) => {
if (style[key] === undefined) return str;
return str + key + ':' + style[key] + ';';
}, '');
};
return {
duration: params.duration ?? 200,
delay: 0,
css: (t) => {
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
return styleToString({
transform: transform + 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')',
opacity: t
});
},
easing: cubicOut
};
};