Initial commit with Sveltekit + format files

This commit is contained in:
pheralb
2025-08-21 10:26:07 +01:00
parent ca4f397e0a
commit 459457a7e1
97 changed files with 3892 additions and 9893 deletions
-3
View File
@@ -1,3 +0,0 @@
<div class="container mx-auto px-6 pt-4 xl:px-[18px]">
<slot />
</div>
-514
View File
@@ -1,514 +0,0 @@
<script lang="ts">
import type { iSVG } from '@/types/svg';
import { ClipboardIcon, CopyIcon, Loader, X } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import * as Popover from '@/ui/popover';
import * as Tabs from '@/ui/tabs';
import { buttonStyles } from '@/ui/styles';
// Utils:
import { cn } from '@/utils/cn';
import { clipboard } from '@/utils/clipboard';
import { copyToClipboard as figmaCopyToClipboard } from '@/figma/copy-to-clipboard';
import { getPrefixFromSvgUrl, prefixSvgIds } from '@/utils/prefixSvgIds';
// Templates:
import { getSource } from '@/templates/getSource';
import { getReactCode } from '@/templates/getReactCode';
import { getVueCode } from '@/templates/getVueCode';
import { getSvelteCode } from '@/templates/getSvelteCode';
import { getAngularCode } from '@/templates/getAngularCode';
import { getWebComponent } from '@/templates/getWebComponent';
import { getAstroCode } from '@/templates/getAstroCode';
//Icons:
import ReactIcon from '@/components/icons/reactIcon.svelte';
import VueIcon from '@/components/icons/vueIcon.svelte';
import SvelteIcon from '@/components/icons/svelteIcon.svelte';
import AngularIcon from '@/components/icons/angularIcon.svelte';
import WebComponentIcon from '@/components/icons/webComponentIcon.svelte';
import AstroIcon from '@/components/icons/astroIcon.svelte';
// Props:
export let iconSize = 24;
export let iconStroke = 2;
export let isInFigma = false;
export let isWordmarkSvg = false;
export let svgInfo: iSVG;
// Variables:
let optionsOpen = false;
let isLoading = false;
const getSvgUrl = () => {
let svgUrlToCopy;
const dark = document.documentElement.classList.contains('dark');
if (isWordmarkSvg) {
const svgHasTheme = typeof svgInfo.wordmark !== 'string';
if (!svgHasTheme) {
svgUrlToCopy =
typeof svgInfo.wordmark === 'string'
? svgInfo.wordmark
: "Something went wrong. Couldn't copy the SVG.";
}
svgUrlToCopy =
typeof svgInfo.wordmark !== 'string'
? dark
? svgInfo.wordmark?.dark
: svgInfo.wordmark?.light
: svgInfo.wordmark;
} else {
const svgHasTheme = typeof svgInfo.route !== 'string';
if (!svgHasTheme) {
svgUrlToCopy =
typeof svgInfo.route === 'string'
? svgInfo.route
: "Something went wrong. Couldn't copy the SVG.";
}
svgUrlToCopy =
typeof svgInfo.route !== 'string'
? dark
? svgInfo.route.dark
: svgInfo.route.light
: svgInfo.route;
}
return svgUrlToCopy;
};
// Copy SVG to clipboard:
const copyToClipboard = async () => {
const svgUrlToCopy = getSvgUrl();
optionsOpen = false;
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
if (isInFigma) {
figmaCopyToClipboard(content);
}
await clipboard(content);
const category = Array.isArray(svgInfo.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
if (isInFigma) {
toast.success('Ready to paste in Figma', {
description: `${svgInfo.title} - ${category}`
});
return;
}
if (isWordmarkSvg) {
toast.success('Copied wordmark SVG to clipboard', {
description: `${svgInfo.title} - ${category}`
});
return;
}
toast.success('Copied to clipboard', {
description: `${svgInfo.title} - ${category}`
});
};
// Convert SVG as React component:
const convertSvgReactComponent = async (tsx: boolean) => {
const svgUrlToCopy = getSvgUrl();
optionsOpen = false;
isLoading = true;
const title = svgInfo.title.split(' ').join('');
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
const dataComponent = { code: content, typescript: tsx, name: title };
const { data, error } = await getReactCode(dataComponent);
if (error || !data) {
toast.error('Failed to fetch React component', {
description: `${error ?? ''}`,
duration: 5000
});
isLoading = false;
return;
}
await clipboard(data);
toast.success(`Copied as React ${tsx ? 'TSX' : 'JSX'} component`, {
description: `${svgInfo.title} - ${svgInfo.category}`
});
isLoading = false;
};
// Copy SVG as Vue Component:
const convertSvgVueComponent = async (ts: boolean) => {
try {
const svgUrlToCopy = getSvgUrl();
optionsOpen = false;
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
const copyCode = getVueCode({
content: content,
lang: ts ? 'ts' : 'js'
});
if (copyCode) {
await clipboard(copyCode);
}
const category = Array.isArray(svgInfo?.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
toast.success(`Copied as Vue ${ts ? 'TS' : 'JS'} component`, {
description: `${svgInfo?.title} - ${category}`
});
} catch (err) {
console.error(`Error copying Vue component:`, err);
toast.error(`Failed to copy Vue component`);
}
};
// Copy SVG as Svelte Component:
const convertSvgSvelteComponent = async (ts: boolean) => {
try {
const svgUrlToCopy = getSvgUrl();
optionsOpen = false;
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
const copyCode = getSvelteCode({
content: content,
lang: ts ? 'ts' : 'js'
});
if (copyCode) {
await clipboard(copyCode);
}
const category = Array.isArray(svgInfo?.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
toast.success(`Copied as Svelte ${ts ? 'TS' : 'JS'} component`, {
description: `${svgInfo?.title} - ${category}`
});
} catch (err) {
console.error(`Error copying Svelte component:`, err);
toast.error(`Failed to copy Svelte component`);
}
};
// Copy SVG as Standalone Angular component:
const convertSvgAngularComponent = async () => {
isLoading = true;
optionsOpen = false;
const title = svgInfo.title.split(' ').join('');
const svgUrlToCopy = getSvgUrl();
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
if (!content) {
toast.error('Failed to fetch the SVG content', {
duration: 5000
});
isLoading = false;
return;
}
const angularComponent = getAngularCode({
componentName: title,
svgContent: content
});
await clipboard(angularComponent);
toast.success(`Copied as Standalone Angular component`, {
description: `${svgInfo.title} - ${svgInfo.category}`
});
isLoading = false;
};
// Copy SVG as Web Component:
const convertSvgWebComponent = async () => {
isLoading = true;
optionsOpen = false;
const title = svgInfo.title.split(' ').join('');
const svgUrlToCopy = getSvgUrl();
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
if (!content) {
toast.error('Failed to fetch the SVG content', {
duration: 5000
});
isLoading = false;
return;
}
const webComponentCode = getWebComponent({
name: title,
content: content
});
await clipboard(webComponentCode);
toast.success(`Copied as Web Component`, {
description: `${svgInfo.title} - ${svgInfo.category}`
});
isLoading = false;
};
// Copy SVG as Astro component:
const convertSvgAstroComponent = async () => {
isLoading = true;
optionsOpen = false;
const svgUrlToCopy = getSvgUrl();
let content = await getSource({
url: svgUrlToCopy
});
if (svgUrlToCopy) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy));
}
if (!content) {
toast.error('Failed to fetch the SVG content', {
duration: 5000
});
isLoading = false;
return;
}
const astroComponentCode = getAstroCode({
svgContent: content
});
await clipboard(astroComponentCode);
toast.success(`Copied as Astro Component`, {
description: `${svgInfo.title} - ${svgInfo.category}`
});
isLoading = false;
};
</script>
<Popover.Root open={optionsOpen} onOpenChange={(isOpen) => (optionsOpen = isOpen)}>
<Popover.Trigger
title="Copy SVG element as svg file, React TSX code, or React JSX code"
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
>
{#if optionsOpen}
<X size={iconSize} strokeWidth={iconStroke} />
{:else if isLoading}
<Loader size={iconSize} strokeWidth={iconStroke} class="animate-spin" />
{:else}
<CopyIcon size={iconSize} strokeWidth={iconStroke} />
{/if}
</Popover.Trigger>
<Popover.Content class="flex flex-col space-y-2 p-4" sideOffset={2}>
<Tabs.Root value="source" class="flex w-full flex-col space-y-1">
<Tabs.List>
<Tabs.Trigger value="source">Source</Tabs.Trigger>
<div
class="ml-3 flex flex-row space-x-0.5 border-l border-dashed border-neutral-200 pl-3 dark:border-neutral-800"
>
<Tabs.Trigger value="web-component" title="Web Component">
<WebComponentIcon iconSize={21} />
</Tabs.Trigger>
<Tabs.Trigger value="react" title="React">
<ReactIcon iconSize={20} />
</Tabs.Trigger>
<Tabs.Trigger value="vue" title="Vue">
<VueIcon iconSize={20} />
</Tabs.Trigger>
<Tabs.Trigger value="svelte" title="Svelte">
<SvelteIcon iconSize={20} />
</Tabs.Trigger>
<Tabs.Trigger value="angular" title="Angular">
<AngularIcon iconSize={20} />
</Tabs.Trigger>
<Tabs.Trigger value="astro" title="Astro" class="text-black dark:text-white">
<AstroIcon iconSize={21} />
</Tabs.Trigger>
</div>
</Tabs.List>
<Tabs.Content value="source">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title={isWordmarkSvg ? 'Copy wordmark SVG to clipboard' : 'Copy SVG to clipboard'}
on:click={() => copyToClipboard()}
>
<ClipboardIcon size={16} strokeWidth={2} />
<span>Copy SVG</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="react">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as React component"
disabled={isLoading}
on:click={() => convertSvgReactComponent(true)}
>
<ReactIcon iconSize={18} />
<span>Copy TSX</span>
</button>
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as React component"
disabled={isLoading}
on:click={() => convertSvgReactComponent(false)}
>
<ReactIcon iconSize={18} />
<span>Copy JSX</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="svelte">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Svelte component"
disabled={isLoading}
on:click={() => convertSvgSvelteComponent(false)}
>
<SvelteIcon iconSize={18} />
<span>Copy JS</span>
</button>
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Svelte component"
disabled={isLoading}
on:click={() => convertSvgSvelteComponent(true)}
>
<SvelteIcon iconSize={18} />
<span>Copy TS</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="vue">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Vue component"
disabled={isLoading}
on:click={() => convertSvgVueComponent(false)}
>
<VueIcon iconSize={18} />
<span>Copy JS</span>
</button>
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Vue component"
disabled={isLoading}
on:click={() => convertSvgVueComponent(true)}
>
<VueIcon iconSize={18} />
<span>Copy TS</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="angular">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Standalone Component"
disabled={isLoading}
on:click={() => convertSvgAngularComponent()}
>
<AngularIcon iconSize={18} />
<span>Copy Standalone Component</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="web-component">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Web Component"
disabled={isLoading}
on:click={() => convertSvgWebComponent()}
>
<WebComponentIcon iconSize={18} />
<span>Copy Web Component</span>
</button>
</section>
</Tabs.Content>
<Tabs.Content value="astro">
<section class="flex flex-col space-y-2">
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Astro Component"
disabled={isLoading}
on:click={() => convertSvgAstroComponent()}
>
<AstroIcon iconSize={18} />
<span>Copy Astro Component</span>
</button>
</section>
</Tabs.Content>
</Tabs.Root>
<div
class="mt-1 flex w-full items-center text-center text-[12px] 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.
</p>
</div>
</Popover.Content>
</Popover.Root>
-298
View File
@@ -1,298 +0,0 @@
<script lang="ts">
import type { iSVG } from '@/types/svg';
import JSZip from 'jszip';
import download from 'downloadjs';
import { toast } from 'svelte-sonner';
import { DownloadIcon } from 'lucide-svelte';
import { getSource } from '@/templates/getSource';
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter
} from '@/ui/dialog';
import { buttonStyles } from '@/ui/styles';
import { cn } from '@/utils/cn';
import { getPrefixFromSvgUrl, prefixSvgIds } from '@/utils/prefixSvgIds';
// Props:
export let svgInfo: iSVG;
export let isDarkTheme: () => boolean;
// Shared:
let iconStroke = 1.8;
let iconSize = 16;
let mainDownloadStyles =
'flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40';
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';
// Functions:
const downloadSvg = async (url?: string) => {
let content = await getSource({
url: url
});
if (url) {
content = prefixSvgIds(content, getPrefixFromSvgUrl(url));
}
download(content || '', url?.split('/').pop() || '', 'image/svg+xml');
const category = Array.isArray(svgInfo.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
toast.success(`Downloading...`, {
description: `${svgInfo.title} - ${category}`
});
};
// Download all variants:
const downloadAllVariants = async ({
lightRoute,
darkRoute,
isWordmark
}: {
lightRoute: string;
darkRoute: string;
isWordmark?: boolean;
}) => {
const zip = new JSZip();
let lightSvg = await getSource({
url: lightRoute
});
let darkSvg = await getSource({
url: darkRoute
});
lightSvg = prefixSvgIds(
lightSvg,
svgInfo.title.toLowerCase() + (isWordmark ? '_wordmark_light' : '_light')
);
darkSvg = prefixSvgIds(
darkSvg,
svgInfo.title.toLowerCase() + (isWordmark ? '_wordmark_dark' : '_dark')
);
if (isWordmark) {
zip.file(`${svgInfo.title}_wordmark_light.svg`, lightSvg);
zip.file(`${svgInfo.title}_wordmark_dark.svg`, darkSvg);
} else {
zip.file(`${svgInfo.title}_light.svg`, lightSvg);
zip.file(`${svgInfo.title}_dark.svg`, darkSvg);
}
zip.generateAsync({ type: 'blob' }).then((content) => {
download(
content,
isWordmark ? `${svgInfo.title}_wordmark_light_dark.zip` : `${svgInfo.title}_light_dark.zip`,
'application/zip'
);
});
const category = Array.isArray(svgInfo.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
toast.success('Downloading light & dark variants...', {
description: isWordmark
? `${svgInfo.title} - Wordmark - ${category}`
: `${svgInfo.title} - ${category}`
});
};
</script>
{#if typeof svgInfo.route === 'string' && svgInfo.wordmark === undefined}
<button
title="Download Light & Dark variants"
class={mainDownloadStyles}
on:click={() => {
if (typeof svgInfo.route === 'string') {
downloadSvg(svgInfo.route);
return;
}
}}
>
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</button>
{:else}
<Dialog>
<DialogTrigger title="Download SVG" class={mainDownloadStyles}>
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
</DialogTrigger>
<DialogContent class="max-w-[630px]">
<DialogHeader>
<DialogTitle>Download {svgInfo.title} SVG</DialogTitle>
<DialogDescription>This logo has multiple options to download:</DialogDescription>
</DialogHeader>
<div
class={cn(
'flex h-full flex-col space-y-2 pb-0.5 pt-4',
'md:flex-row md:items-center md:justify-center md:space-x-2 md:space-y-0'
)}
>
{#if typeof svgInfo.route === 'string'}
<div class={cardDownloadStyles}>
<img
src={isDarkTheme() ? svgInfo.route : svgInfo.route}
alt={svgInfo.title}
class="my-4 h-8"
/>
<button
title="Download logo"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.route === 'string') {
downloadSvg(svgInfo.route);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Icon logo</p>
</button>
</div>
{:else}
<div class={cardDownloadStyles}>
<img
src={isDarkTheme() ? svgInfo.route.dark : svgInfo.route.light}
alt={svgInfo.title}
class="my-4 h-10"
/>
<button
title="Logo with light & dark variants"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.route !== 'string') {
downloadAllVariants({
lightRoute: svgInfo.route.light,
darkRoute: svgInfo.route.dark
});
}
}}
>
<DownloadIcon size={iconSize} />
<p>Light & dark variants</p>
</button>
<button
title="Download light variant"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.route !== 'string') {
downloadSvg(svgInfo.route.light);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
Only light variant
</button>
<button
title="Download dark variant"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.route !== 'string') {
downloadSvg(svgInfo.route.dark);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
Only dark variant
</button>
</div>
{/if}
{#if typeof svgInfo.wordmark === 'string' && svgInfo.wordmark !== undefined}
<div class={cardDownloadStyles}>
<img
src={isDarkTheme() ? svgInfo.wordmark : svgInfo.wordmark}
alt={svgInfo.title}
class="my-4 h-8"
/>
<button
title="Download Wordmark logo"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.wordmark === 'string') {
downloadSvg(svgInfo.wordmark);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
<p>Wordmark logo</p>
</button>
</div>
{/if}
{#if typeof svgInfo.wordmark !== 'string' && svgInfo.wordmark !== undefined}
<div class={cardDownloadStyles}>
<img
src={isDarkTheme() ? svgInfo.wordmark.dark : svgInfo.wordmark.light}
alt={svgInfo.title}
class="my-4 h-10"
/>
<button
title="Download Wordmark light variant"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.wordmark !== 'string') {
downloadAllVariants({
lightRoute: svgInfo.wordmark?.light || '',
darkRoute: svgInfo.wordmark?.dark || '',
isWordmark: true
});
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
Light & dark variants
</button>
<button
title="Download Wordmark light variant"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.wordmark !== 'string') {
downloadSvg(svgInfo.wordmark?.light);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
Wordmark light variant
</button>
<button
title="Download Wordmark dark variant"
class={buttonStyles}
on:click={() => {
if (typeof svgInfo.wordmark !== 'string') {
downloadSvg(svgInfo.wordmark?.dark);
return;
}
}}
>
<DownloadIcon class="mr-2" size={iconSize} />
Wordmark dark variant
</button>
</div>
{/if}
</div>
<DialogFooter class="mt-3 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.
</p>
</DialogFooter>
</DialogContent>
</Dialog>
{/if}
-34
View File
@@ -1,34 +0,0 @@
<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 select-none',
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>
-5
View File
@@ -1,5 +0,0 @@
<div
class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5"
>
<slot />
</div>
-54
View File
@@ -1,54 +0,0 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import * as ContextMenu from '@/ui/context-menu';
import { ArrowUpRightIcon, CopyIcon, StarsIcon } from 'lucide-svelte';
import Logo from './icons/logo.svelte';
import { clipboard } from '@/utils/clipboard';
import GithubIcon from './icons/githubIcon.svelte';
import { getSource } from '@/templates/getSource';
const logoUrl = '/library/svgl.svg';
const copyToClipboard = async () => {
const content = await getSource({
url: logoUrl
});
await clipboard(content);
toast.success('Copied to clipboard', {
description: `Svgl - Library`
});
};
const openUrl = (url: string) => {
window.open(url, '_blank');
};
</script>
<ContextMenu.Root>
<ContextMenu.Trigger class="flex items-center space-x-2">
<a href="/" aria-label="Go to the SVGL v4.1 home page">
<div class="flex items-center space-x-2 opacity-100 transition-opacity hover:opacity-80">
<svelte:component this={Logo} />
<span class="hidden text-xl font-medium tracking-tight md:block">svgl</span>
</div>
</a>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item on:click={() => copyToClipboard()}>
<CopyIcon size={16} strokeWidth={1.5} />
<span>Copy as SVG</span>
</ContextMenu.Item>
<ContextMenu.Item on:click={() => openUrl('https://github.com/pheralb/svgl')}>
<GithubIcon iconSize={16} />
<span>Repository</span>
<ArrowUpRightIcon class="text-neutral-400 dark:text-neutral-500" size={14} strokeWidth={2} />
</ContextMenu.Item>
<ContextMenu.Item disabled class="font-mono">
<StarsIcon size={16} strokeWidth={1.5} />
<span>v4.6.1</span>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
-48
View File
@@ -1,48 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize || 16}
height={iconSize || 16}
viewBox="0 0 242 256"
><g clip-path="url(#a)"
><mask
id="b"
width="242"
height="256"
x="0"
y="0"
maskUnits="userSpaceOnUse"
style="mask-type:luminance"><path fill="#fff" d="M0 0h242v256H0V0Z" /></mask
><g mask="url(#b)"
><path
fill="url(#c)"
d="m241 43-9 136L149 0l92 43Zm-58 176-62 36-63-36 12-31h101l12 31ZM121 68l32 80H88l33-80ZM9 179 0 43 92 0 9 179Z"
/><path
fill="url(#d)"
d="m241 43-9 136L149 0l92 43Zm-58 176-62 36-63-36 12-31h101l12 31ZM121 68l32 80H88l33-80ZM9 179 0 43 92 0 9 179Z"
/></g
></g
><defs
><linearGradient id="c" x1="53.2" x2="245" y1="231.9" y2="140.7" gradientUnits="userSpaceOnUse"
><stop stop-color="#E40035" /><stop offset=".2" stop-color="#F60A48" /><stop
offset=".4"
stop-color="#F20755"
/><stop offset=".5" stop-color="#DC087D" /><stop offset=".7" stop-color="#9717E7" /><stop
offset="1"
stop-color="#6C00F5"
/></linearGradient
>
<linearGradient id="d" x1="44.5" x2="170" y1="30.7" y2="174" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF31D9" /><stop
offset="1"
stop-color="#FF5BE1"
stop-opacity="0"
/></linearGradient
><clipPath id="a"><path fill="#fff" d="M0 0h242v256H0z" /></clipPath></defs
></svg
>
-20
View File
@@ -1,20 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg
viewBox="0 0 256 366"
xmlns="http://www.w3.org/2000/svg"
width={iconSize || 16}
height={iconSize || 16}
preserveAspectRatio="xMidYMid"
><path
fill="currentColor"
d="M182.022 9.147c2.982 3.702 4.502 8.697 7.543 18.687L256 246.074a276.467 276.467 0 0 0-79.426-26.891L133.318 73.008a5.63 5.63 0 0 0-10.802.017L79.784 219.11A276.453 276.453 0 0 0 0 246.04L66.76 27.783c3.051-9.972 4.577-14.959 7.559-18.654a24.541 24.541 0 0 1 9.946-7.358C88.67 0 93.885 0 104.314 0h47.683c10.443 0 15.664 0 20.074 1.774a24.545 24.545 0 0 1 9.95 7.373Z"
/><path
fill="#FF5D01"
d="M189.972 256.46c-10.952 9.364-32.812 15.751-57.992 15.751-30.904 0-56.807-9.621-63.68-22.56-2.458 7.415-3.009 15.903-3.009 21.324 0 0-1.619 26.623 16.898 45.14 0-9.615 7.795-17.41 17.41-17.41 16.48 0 16.46 14.378 16.446 26.043l-.001 1.041c0 17.705 10.82 32.883 26.21 39.28a35.685 35.685 0 0 1-3.588-15.647c0-16.886 9.913-23.173 21.435-30.48 9.167-5.814 19.353-12.274 26.372-25.232a47.588 47.588 0 0 0 5.742-22.735c0-5.06-.786-9.938-2.243-14.516Z"
/></svg
>
-18
View File
@@ -1,18 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg
width={iconSize || 16}
height={iconSize || 16}
fill="none"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
viewBox="0 0 256 256"
><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="currentColor"
/>
</svg>
-60
View File
@@ -1,60 +0,0 @@
<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
>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-22
View File
@@ -1,22 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
export let className: IconProps['className'];
</script>
<svg
width={iconSize || 28}
height={iconSize || 28}
class={className}
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7 18.079V21L0 14L1.46 12.54L7 18.081V18.079ZM9.921 21H7L14 28L15.46 26.54L9.921 21ZM26.535 15.462L27.996 14L13.996 0L12.538 1.466L18.077 7.004H14.73L10.864 3.146L9.404 4.606L11.809 7.01H10.129V17.876H20.994V16.196L23.399 18.6L24.859 17.14L20.994 13.274V9.927L26.535 15.462ZM7.73 6.276L6.265 7.738L7.833 9.304L9.294 7.844L7.73 6.276ZM20.162 18.708L18.702 20.17L20.268 21.738L21.73 20.276L20.162 18.708ZM4.596 9.41L3.134 10.872L7 14.738V11.815L4.596 9.41ZM16.192 21.006H13.268L17.134 24.872L18.596 23.41L16.192 21.006Z"
fill="#FF6363"
/>
</svg>
File diff suppressed because one or more lines are too long
-19
View File
@@ -1,19 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize || 16}
height={iconSize || 16}
viewBox="0 0 256 308"
><path
fill="#FF3E00"
d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.2 82.2 0 0 0-37.135 55.056a86.57 86.57 0 0 0 8.536 55.576a82.4 82.4 0 0 0-12.296 30.719a87.6 87.6 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.18 82.18 0 0 0 37.135-55.057a86.6 86.6 0 0 0-8.53-55.577a82.4 82.4 0 0 0 12.29-30.718a87.57 87.57 0 0 0-14.963-66.244"
/><path
fill="#FFF"
d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.7 52.7 0 0 1-9.003-39.85a50 50 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.5 92.5 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.07 16.07 0 0 0 2.89 10.656a17.14 17.14 0 0 0 18.397 6.828a15.8 15.8 0 0 0 4.403-1.935l71.67-45.672a14.92 14.92 0 0 0 6.734-9.977a15.92 15.92 0 0 0-2.713-12.011a17.16 17.16 0 0 0-18.404-6.832a15.8 15.8 0 0 0-4.396 1.933l-27.35 17.434a52.3 52.3 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.68 52.68 0 0 1-9.004-39.849a49.43 49.43 0 0 1 22.34-33.114l71.664-45.677a52.2 52.2 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.7 52.7 0 0 1 9.004 39.85a51 51 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.4 92.4 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.1 16.1 0 0 0-2.89-10.656a17.14 17.14 0 0 0-18.398-6.828a15.8 15.8 0 0 0-4.402 1.935l-71.67 45.674a14.9 14.9 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.16 17.16 0 0 0 18.404 6.832a15.8 15.8 0 0 0 4.402-1.935l27.345-17.427a52.2 52.2 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.68 52.68 0 0 1 9.003 39.848a49.45 49.45 0 0 1-22.34 33.12l-71.664 45.673a52.2 52.2 0 0 1-14.563 6.398"
/></svg
>
-16
View File
@@ -1,16 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={iconSize || 16}
height={iconSize || 16}
viewBox="0 0 256 221"
><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0z" /><path
fill="#41B883"
d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0z"
/><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0z" /></svg
>
@@ -1,18 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let iconSize: IconProps['size'];
</script>
<svg viewBox="0 0 128 128" width={iconSize || 16} height={iconSize || 16}>
<path
fill="var(--bgcolor, #fff)"
d="m31 12-1 1L2 63l29 51h67l15-26v-2l14-23-15-24v-2L98 12H31zm21 30h21l13 21-13 21H52L40 63z"
/>
<path fill="#166da5" d="m122 63-12 21-18-21 18-21z" />
<path fill="#8fdb69" d="M108 88 89 65 78 84l17 26z" />
<path fill="#166da5" d="M108 38 89 61 78 42l17-26z" />
<path d="M63 110 35 63l28-47H33L6 63l27 47z" />
<path fill="#287bbe" d="m50 38 13-22h32l13 22zm28 4h32l11 19H89z" />
<path fill="#ddf021" d="m50 88 13 22h32l13-22zm28-4h32l11-19H89z" />
</svg>
-19
View File
@@ -1,19 +0,0 @@
<script lang="ts">
import type { IconProps } from '@/types/icon';
export let size: IconProps['size'];
export let color: IconProps['color'];
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={size || 16}
height={size || 16}
name="Twitter"
fill="none"
viewBox="0 0 1200 1227"
><path
fill={color || '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>
-105
View File
@@ -1,105 +0,0 @@
<script lang="ts">
export let currentPath: string;
import { cn } from '@/utils/cn';
import Theme from './theme.svelte';
import { ArrowUpRight, CloudyIcon } from 'lucide-svelte';
import XIcon from './icons/xIcon.svelte';
import GithubIcon from './icons/githubIcon.svelte';
import HeaderLogoLink from './headerLogoLink.svelte';
const externalLinks = [
{
name: 'API',
url: '/api',
icon: CloudyIcon,
external: false,
label: "Go to the SVGL's API section"
},
{
name: 'Extensions',
url: 'https://github.com/pheralb/svgl?tab=readme-ov-file#-extensions',
icon: ArrowUpRight,
external: true,
label: "Go to the SVGL's extensions section"
},
{
name: 'Submit logo',
url: 'https://github.com/pheralb/svgl#-getting-started',
icon: ArrowUpRight,
external: true,
label: "Submit logo and go to the SVGL's getting started section"
}
];
</script>
<nav
class={cn(
'bg-white dark:bg-neutral-900',
'w-full border-b border-neutral-200 px-5 py-4 dark:border-neutral-800',
'sticky top-0 z-50',
'bg-white/90 backdrop-blur-md dark:bg-neutral-900/90'
)}
>
<div class="mx-auto flex items-center justify-between">
<HeaderLogoLink />
<div class="flex items-center space-x-0 md:space-x-7">
<div
class="flex items-center divide-x divide-dashed divide-neutral-300 dark:divide-neutral-700 md:space-x-4"
>
{#each externalLinks as link}
<a
href={link.url}
target={link.external ? '_blank' : ''}
aria-label={link.label ?? link.name}
class={cn(
'group flex items-center pl-2 text-[15px] opacity-80 transition-opacity hover:opacity-100 md:pl-3',
currentPath === link.url &&
'underline decoration-neutral-500 decoration-dotted underline-offset-8'
)}
>
{#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 hidden transition-transform duration-300 group-hover:-translate-y-[1px] group-hover:translate-x-[1px] 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 opacity-80 transition-opacity hover:opacity-100"
title="Twitter"
>
<XIcon size={16} color="currentColor" />
</a>
<a
href="https://github.com/pheralb/svgl"
target="_blank"
class="flex items-center space-x-1 opacity-80 transition-opacity hover:opacity-100"
title="GitHub"
>
<GithubIcon iconSize={19} />
</a>
<Theme />
</div>
</div>
</div>
</nav>
-29
View File
@@ -1,29 +0,0 @@
<script lang="ts">
import { buttonStyles } from '@/ui/styles';
export let notFoundTerm: string;
import { PackageOpen, ArrowUpRight } from 'lucide-svelte';
</script>
<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">Couldn't find the Icon</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={buttonStyles}
>
<span>Submit logo</span>
<ArrowUpRight size={16} />
</a>
<a
href="https://github.com/pheralb/svgl/issues/new?assignees=pheralb&labels=request&projects=&template=request-svg-.md&title=%5BRequest%5D%3A"
target="_blank"
class={buttonStyles}
>
<span>Request Icon</span>
<ArrowUpRight size={16} />
</a>
</div>
</div>
-77
View File
@@ -1,77 +0,0 @@
<script lang="ts">
import { page } from '$app/stores';
import { inputStyles } from '@/ui/styles';
import { Command, SearchIcon } from 'lucide-svelte';
export let searchTerm: string;
export let placeholder: string = 'Search...';
export let clearSearch: () => void;
import { X } from 'lucide-svelte';
let inputElement;
function focusInput(node: HTMLElement) {
const handleKeydown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
event.preventDefault();
node.focus();
}
};
window.addEventListener('keydown', handleKeydown);
return {
destroy() {
window.removeEventListener('keydown', handleKeydown);
}
};
}
let searchParams = {} as { [key: string]: string };
$: {
if ($page) {
searchParams = Object.fromEntries($page.url.searchParams);
if (!searchParams?.search) {
clearSearch();
}
}
}
</script>
<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={inputStyles}
bind:value={searchTerm}
on:input
use:focusInput
bind:this={inputElement}
/>
{#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>
{:else}
<div class="absolute inset-y-0 right-0 flex items-center pr-4 text-neutral-500">
<div class="flex h-full items-center pointer-events-none gap-x-1 font-mono">
<Command size={16} />
<span>K</span>
</div>
</div>
{/if}
</div>
</div>
-239
View File
@@ -1,239 +0,0 @@
<script lang="ts">
import type { iSVG } from '@/types/svg';
// Utils:
import { cn } from '@/utils/cn';
// Icons:
import {
LinkIcon,
ChevronsRight,
Baseline,
Sparkles,
EllipsisIcon,
TagIcon,
XIcon,
PaletteIcon
} from 'lucide-svelte';
// Components & styles:
import DownloadSvg from './downloadSvg.svelte';
import CopySvg from './copySvg.svelte';
import { badgeStyles, buttonStyles } from '@/ui/styles';
import * as Popover from '@/ui/popover';
// Figma
import { onMount } from 'svelte';
import { insertSVG as figmaInsertSVG } from '@/figma/insert-svg';
import { getSource } from '@/templates/getSource';
// Props:
export let svgInfo: iSVG;
export let searchTerm: string;
let isInFigma = false;
onMount(() => {
const searchParams = new URLSearchParams(window.location.search);
isInFigma = searchParams.get('figma') === '1';
});
// Wordmark SVG:
let wordmarkSvg = false;
$: {
if (searchTerm) {
wordmarkSvg = false;
}
}
const insertSVG = async (url?: string) => {
const content = (await getSource({
url
})) as string;
figmaInsertSVG(content);
};
// Icon Stroke & Size:
let iconStroke = 1.8;
let iconSize = 16;
// Max Categories:
let maxVisibleCategories = 1;
let moreTagsOptions = false;
// Global Styles:
const globalImageStyles = 'mb-4 mt-2 h-10 select-none pointer-events-none';
const btnStyles =
'flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40';
</script>
<div
class="group flex flex-col items-center justify-center rounded-md border border-neutral-200 p-4 transition-colors duration-100 hover:bg-neutral-100/80 dark:border-neutral-800 dark:hover:bg-neutral-800/20"
>
<!-- Image -->
{#if wordmarkSvg == true && svgInfo.wordmark !== undefined}
<img
class={cn('hidden dark:block', globalImageStyles)}
src={typeof svgInfo.wordmark !== 'string'
? svgInfo.wordmark?.dark || ''
: svgInfo.wordmark || ''}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
<img
class={cn('block dark:hidden', globalImageStyles)}
src={typeof svgInfo.wordmark !== 'string'
? svgInfo.wordmark?.light || ''
: svgInfo.wordmark || ''}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
{:else}
<img
class={cn('hidden dark:block', globalImageStyles)}
src={typeof svgInfo.route !== 'string' ? svgInfo.route.dark : svgInfo.route}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
<img
class={cn('block dark:hidden', globalImageStyles)}
src={typeof svgInfo.route !== 'string' ? svgInfo.route.light : svgInfo.route}
alt={svgInfo.title}
title={svgInfo.title}
loading="lazy"
/>
{/if}
<!-- Title -->
<div class="mb-3 flex flex-col items-center justify-center space-y-1">
<p class="select-all truncate text-balance text-center text-[15px] font-medium">
{svgInfo.title}
</p>
<div class="flex items-center justify-center space-x-1">
{#if Array.isArray(svgInfo.category)}
{#each svgInfo.category.slice(0, maxVisibleCategories) as c, index}
<a
href={`/directory/${c.toLowerCase()}`}
class={badgeStyles}
title={`This icon is part of the ${svgInfo.category} category`}>{c}</a
>
{/each}
{#if svgInfo.category.length > maxVisibleCategories}
<Popover.Root
open={moreTagsOptions}
onOpenChange={(isOpen) => (moreTagsOptions = isOpen)}
>
<Popover.Trigger class={badgeStyles} title="More Tags">
{#if moreTagsOptions}
<XIcon size={15} strokeWidth={1.5} />
{:else}
<EllipsisIcon size={15} strokeWidth={1.5} />
{/if}
</Popover.Trigger>
<Popover.Content class="flex flex-col space-y-2">
<p class="font-medium">More tags:</p>
{#each svgInfo.category.slice(maxVisibleCategories) as c}
<a
href={`/directory/${c.toLowerCase()}`}
class={cn(buttonStyles, 'w-full rounded-md')}
>
<TagIcon size={15} strokeWidth={1.5} />
<span>{c}</span>
</a>
{/each}
</Popover.Content>
</Popover.Root>
{/if}
{:else}
<a href={`/directory/${svgInfo.category.toLowerCase()}`} class={badgeStyles}>
{svgInfo.category}
</a>
{/if}
</div>
</div>
<!-- Actions -->
<div class="flex items-center space-x-1">
{#if isInFigma}
<button
title="Insert to figma"
on:click={() => {
const svgHasTheme = typeof svgInfo.route !== 'string';
if (!svgHasTheme) {
insertSVG(
typeof svgInfo.route === 'string'
? svgInfo.route
: "Something went wrong. Couldn't copy the SVG."
);
return;
}
const dark = document.documentElement.classList.contains('dark');
insertSVG(
typeof svgInfo.route !== 'string'
? dark
? svgInfo.route.dark
: svgInfo.route.light
: svgInfo.route
);
}}
class={btnStyles}
>
<ChevronsRight size={iconSize} strokeWidth={iconStroke} />
</button>
{/if}
{#if wordmarkSvg && svgInfo.wordmark !== undefined}
<CopySvg {iconSize} {iconStroke} {svgInfo} isInFigma={false} isWordmarkSvg={true} />
{:else}
<CopySvg {iconSize} {iconStroke} {svgInfo} isInFigma={false} isWordmarkSvg={false} />
{/if}
<DownloadSvg
{svgInfo}
isDarkTheme={() => {
const dark = document.documentElement.classList.contains('dark');
return dark;
}}
/>
<a
href={svgInfo.url}
title="Website"
target="_blank"
rel="noopener noreferrer"
class={btnStyles}
>
<LinkIcon size={iconSize} strokeWidth={iconStroke} />
</a>
{#if svgInfo.wordmark !== undefined}
<button
title={wordmarkSvg ? 'Show logo SVG' : 'Show wordmark SVG'}
on:click={() => {
wordmarkSvg = !wordmarkSvg;
}}
class={btnStyles}
>
{#if wordmarkSvg}
<Sparkles size={iconSize} strokeWidth={iconStroke} />
{:else}
<Baseline size={iconSize} strokeWidth={iconStroke} />
{/if}
</button>
{/if}
{#if svgInfo.brandUrl !== undefined}
<a
href={svgInfo.brandUrl}
title="Brand Assets"
target="_blank"
rel="noopener noreferrer"
class={btnStyles}
>
<PaletteIcon size={iconSize} strokeWidth={iconStroke} />
</a>
{/if}
</div>
</div>
-14
View File
@@ -1,14 +0,0 @@
<script lang="ts">
import { toggleMode, mode } from 'mode-watcher';
// Icons:
import { MoonIcon, SunIcon } from 'lucide-svelte';
</script>
<button on:click={toggleMode} aria-label="Toggle dark mode" class="opacity-80 hover:opacity-100">
{#if $mode === 'light'}
<SunIcon size={20} strokeWidth={1.5} />
{:else}
<MoonIcon size={20} strokeWidth={1.5} />
{/if}
</button>
-10
View File
@@ -1,10 +0,0 @@
<script lang="ts">
import { fly } from 'svelte/transition';
export let pathname: string = '';
</script>
{#key pathname}
<div in:fly={{ x: 0, y: 23, duration: 450 }}>
<slot />
</div>
{/key}
-45
View File
@@ -1,45 +0,0 @@
<script lang="ts">
import { AlertTriangleIcon, Check } from 'lucide-svelte';
import { browser } from '$app/environment';
import { buttonStyles } from '@/ui/styles';
import { cn } from '@/utils/cn';
let warning = false;
let warningName = 'svgl_warn_message';
const initialValue = browser ? window.localStorage.getItem(warningName) : true;
</script>
{#if !warning && !initialValue}
<div
class="flex w-full flex-col items-center justify-between space-x-0 space-y-2 border-b border-neutral-200 bg-neutral-100/60 px-3 py-2 text-neutral-700 dark:border-neutral-800 dark:bg-neutral-800/40 dark:text-neutral-300 md:flex-row md:space-x-2 md:space-y-0"
>
<div class="flex items-center space-x-2">
<AlertTriangleIcon
size={18}
strokeWidth={2}
class="mr-1 flex-shrink-0 animate-pulse text-yellow-600 dark:text-yellow-500"
/>
<p>
All SVGs include links to the respective products or companies that own them. <strong
>Please contact the owner directly if you need to use their logo.</strong
>
If you are the owner of an SVG and would like it removed,
<a
target="_blank"
class="underline decoration-neutral-500 decoration-dotted underline-offset-4"
href="https://github.com/pheralb/svgl/issues/new">create an issue</a
> on GitHub.
</p>
</div>
<button
class={cn(buttonStyles, 'h-10 text-sm')}
on:click={() => {
localStorage.setItem(warningName, 'true');
warning = true;
}}
>
<Check size={14} strokeWidth={2} />
<span>Accept</span>
</button>
</div>
{/if}