mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
Merge branch 'next' into main
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
<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 { getSvgContent } from '@/utils/getSvgContent';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription
|
||||
} from '@/ui/dialog';
|
||||
import { buttonStyles } from '@/ui/styles';
|
||||
|
||||
// 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 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 = (url?: string) => {
|
||||
download(url || '');
|
||||
|
||||
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();
|
||||
|
||||
const lightSvg = await getSvgContent(lightRoute, false);
|
||||
const darkSvg = await getSvgContent(darkRoute, false);
|
||||
|
||||
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'}
|
||||
<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}</DialogTitle>
|
||||
<DialogDescription>This logo has multiple options to download.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div
|
||||
class="flex w-full flex-col md:flex-row items-center justify-center space-y-2 md:space-y-0 md:space-x-2 mt-5"
|
||||
>
|
||||
<div class={cardDownloadStyles}>
|
||||
<img
|
||||
src={isDarkTheme() ? svgInfo.route.dark : svgInfo.route.light}
|
||||
alt={svgInfo.title}
|
||||
class="h-8 my-4"
|
||||
/>
|
||||
<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 typeof svgInfo.wordmark === 'string' && svgInfo.wordmark !== undefined}
|
||||
<div class={cardDownloadStyles}>
|
||||
<img
|
||||
src={isDarkTheme() ? svgInfo.wordmark : svgInfo.wordmark}
|
||||
alt={svgInfo.title}
|
||||
class="h-8 my-4"
|
||||
/>
|
||||
<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="h-8 my-4"
|
||||
/>
|
||||
<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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/if}
|
||||
@@ -50,14 +50,14 @@
|
||||
'backdrop-blur-md opacity-95'
|
||||
)}
|
||||
>
|
||||
<!-- Se le puso un aria-label al href="/" -->
|
||||
<!-- Se le puso un aria-label al href="/" -->
|
||||
<div class="flex items-center justify-between mx-auto">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/" aria-label="Go to the SVGL v4.0 home page">
|
||||
<a href="/" aria-label="Go to the SVGL v4.1 home page">
|
||||
<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-400 hidden md:block font-mono">v4.0</p>
|
||||
<p class="text-neutral-400 hidden md:block font-mono">v4.1</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { buttonStyles } from '@/ui/styles';
|
||||
export let notFoundTerm: string;
|
||||
import { PackageOpen, ArrowUpRight } from 'lucide-svelte';
|
||||
</script>
|
||||
@@ -11,15 +12,15 @@
|
||||
<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"
|
||||
class={buttonStyles}
|
||||
>
|
||||
<span>Submit logo</span>
|
||||
<ArrowUpRight size={16} />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl/issues/new"
|
||||
href="https://github.com/pheralb/svgl/issues/new?assignees=pheralb&labels=request&projects=&template=request-svg-.md&title=%5BRequest%5D%3A"
|
||||
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"
|
||||
class={buttonStyles}
|
||||
>
|
||||
<span>Request SVG</span>
|
||||
<ArrowUpRight size={16} />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
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 'phosphor-svelte/lib/X';
|
||||
import { X } from 'lucide-svelte';
|
||||
|
||||
let inputElement;
|
||||
|
||||
@@ -36,7 +37,7 @@
|
||||
type="text"
|
||||
{placeholder}
|
||||
autocomplete="off"
|
||||
class="w-full border-b border-neutral-300 bg-white p-3 px-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"
|
||||
class={inputStyles}
|
||||
bind:value={searchTerm}
|
||||
on:input
|
||||
use:focusInput
|
||||
|
||||
+33
-137
@@ -1,28 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { iSVG } from '../types/svg';
|
||||
|
||||
import download from 'downloadjs';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import jszip from 'jszip';
|
||||
|
||||
// Utils:
|
||||
import { cn } from '@/utils/cn';
|
||||
import { MIMETYPE, getSvgContent } from '@/utils/getSvgContent';
|
||||
import { flyAndScale } from '@/utils/flyAndScale';
|
||||
|
||||
// Icons:
|
||||
import {
|
||||
CopyIcon,
|
||||
DownloadIcon,
|
||||
LinkIcon,
|
||||
PackageIcon,
|
||||
PaintBucket,
|
||||
ChevronsRight,
|
||||
Baseline
|
||||
} from 'lucide-svelte';
|
||||
import { CopyIcon, LinkIcon, ChevronsRight, Baseline } from 'lucide-svelte';
|
||||
|
||||
// Main Card:
|
||||
// Components & styles:
|
||||
import CardSpotlight from './cardSpotlight.svelte';
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import DownloadSvg from './downloadSvg.svelte';
|
||||
import { badgeStyles } from '@/ui/styles';
|
||||
|
||||
// Figma
|
||||
import { onMount } from 'svelte';
|
||||
@@ -41,51 +31,6 @@
|
||||
// Wordmark SVG:
|
||||
let wordmarkSvg = false;
|
||||
|
||||
// Download SVG:
|
||||
const downloadSvg = (url?: string) => {
|
||||
download(url || '');
|
||||
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);
|
||||
|
||||
if (svgInfo.wordmark) {
|
||||
if (typeof svgInfo.wordmark === 'string') {
|
||||
downloadSvg(svgInfo.wordmark);
|
||||
return;
|
||||
}
|
||||
|
||||
const lightWordmarkSvg = await getSvgContent(svgInfo.wordmark.light, false);
|
||||
const darkWordmarkSvg = await getSvgContent(svgInfo.wordmark.dark, false);
|
||||
|
||||
zip.file(`${svgInfo.title}.wordmark.svg`, lightWordmarkSvg);
|
||||
zip.file(`${svgInfo.title}.wordmark.dark.svg`, darkWordmarkSvg);
|
||||
}
|
||||
|
||||
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:
|
||||
const copyToClipboard = async (url?: string) => {
|
||||
const data = {
|
||||
@@ -105,22 +50,26 @@
|
||||
await navigator.clipboard.writeText(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} - ${svgInfo.category}`
|
||||
description: `${svgInfo.title} - ${category}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (wordmarkSvg) {
|
||||
toast.success('Copied wordmark SVG to clipboard!', {
|
||||
description: `${svgInfo.title} - ${svgInfo.category}`
|
||||
description: `${svgInfo.title} - ${category}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Copied to clipboard!', {
|
||||
description: `${svgInfo.title} - ${svgInfo.category}`
|
||||
description: `${svgInfo.title} - ${category}`
|
||||
});
|
||||
};
|
||||
|
||||
@@ -158,14 +107,14 @@
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
class="hidden dark:block mb-4 mt-2 h-10"
|
||||
class={cn('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"
|
||||
class={cn('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}
|
||||
@@ -173,15 +122,21 @@
|
||||
/>
|
||||
{/if}
|
||||
<!-- Title -->
|
||||
<!-- Aqui se modifico el text-neutral a 400 -->
|
||||
<div class="mb-3 flex flex-col items-center justify-center">
|
||||
<div class="mb-3 flex flex-col space-y-1 items-center justify-center">
|
||||
<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-400 hover:underline font-mono">{svgInfo.category}</a
|
||||
>
|
||||
<div class="flex items-center space-x-1 justify-center">
|
||||
{#if Array.isArray(svgInfo.category)}
|
||||
{#each svgInfo.category.sort() as c, index}
|
||||
<a href={`/directory/${c.toLowerCase()}`} class={badgeStyles}>{c} </a>
|
||||
{/each}
|
||||
{:else}
|
||||
<a href={`/directory/${svgInfo.category.toLowerCase()}`} class={badgeStyles}>
|
||||
{svgInfo.category}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center space-x-1">
|
||||
@@ -276,73 +231,14 @@
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if typeof svgInfo.route !== 'string'}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
title="Download SVG"
|
||||
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
|
||||
title="Download Light & Dark variants"
|
||||
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium cursor-pointer 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
|
||||
title="Download only {document.documentElement.classList.contains('dark')
|
||||
? 'dark'
|
||||
: 'light'} variant"
|
||||
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
|
||||
on:click={() => {
|
||||
const svgHasTheme = typeof svgInfo.route !== 'string';
|
||||
<DownloadSvg
|
||||
{svgInfo}
|
||||
isDarkTheme={() => {
|
||||
const dark = document.documentElement.classList.contains('dark');
|
||||
return dark;
|
||||
}}
|
||||
/>
|
||||
|
||||
if (!svgHasTheme) {
|
||||
downloadSvg(
|
||||
typeof svgInfo.route === 'string'
|
||||
? svgInfo.route
|
||||
: "Something went wrong. Couldn't copy the SVG."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
+24
-21
@@ -1160,7 +1160,7 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
{
|
||||
title: 'WordPress',
|
||||
category: 'Software',
|
||||
category: ['Software', 'CMS'],
|
||||
route: '/library/wordpress.svg',
|
||||
url: 'https://wordpress.org/'
|
||||
},
|
||||
@@ -2028,16 +2028,7 @@ export const svgs: iSVG[] = [
|
||||
url: 'https://www.webflow.com'
|
||||
},
|
||||
{
|
||||
title: 'bigcommerce',
|
||||
category: 'CMS',
|
||||
route: {
|
||||
light: '/library/bigcommerce-light.svg',
|
||||
dark: '/library/bigcommerce-dark.svg'
|
||||
},
|
||||
url: 'https://www.bigcommerce.co.uk'
|
||||
},
|
||||
{
|
||||
title: 'sanity',
|
||||
title: 'Sanity',
|
||||
category: 'CMS',
|
||||
route: '/library/sanity.svg',
|
||||
url: 'https://www.sanity.io'
|
||||
@@ -2049,57 +2040,60 @@ export const svgs: iSVG[] = [
|
||||
url: 'https://www.sky.com'
|
||||
},
|
||||
{
|
||||
title: 'airbnb',
|
||||
title: 'Airbnb',
|
||||
category: 'Software',
|
||||
route: '/library/airbnb.svg',
|
||||
wordmark: '/library/airbnb-wordmark.svg',
|
||||
url: 'https://www.airbnb.com'
|
||||
},
|
||||
{
|
||||
title: 'uber',
|
||||
title: 'Uber',
|
||||
category: 'Software',
|
||||
route: '/library/uber.svg',
|
||||
route: {
|
||||
light: '/library/uber_light.svg',
|
||||
dark: '/library/uber_dark.svg'
|
||||
},
|
||||
url: 'https://www.uber.com'
|
||||
},
|
||||
{
|
||||
title: 'gmail',
|
||||
title: 'Gmail',
|
||||
category: 'Software',
|
||||
route: '/library/gmail.svg',
|
||||
url: 'https://www.gmail.com'
|
||||
},
|
||||
{
|
||||
title: 'outlook',
|
||||
title: 'Outlook',
|
||||
category: 'Software',
|
||||
route: '/library/outlook.svg',
|
||||
url: 'https://www.outlook.com'
|
||||
},
|
||||
{
|
||||
title: 'slack',
|
||||
title: 'Slack',
|
||||
category: 'Software',
|
||||
route: '/library/slack.svg',
|
||||
wordmark: '/library/slack-wordmark.svg',
|
||||
url: 'https://www.slack.com'
|
||||
},
|
||||
{
|
||||
title: 'snapchat',
|
||||
title: 'Snapchat',
|
||||
category: 'Software',
|
||||
route: '/library/snapchat.svg',
|
||||
url: 'https://www.snapchat.com'
|
||||
},
|
||||
{
|
||||
title: 'ebay',
|
||||
title: 'Ebay',
|
||||
category: 'Software',
|
||||
route: '/library/ebay.svg',
|
||||
url: 'https://www.ebay.com'
|
||||
},
|
||||
{
|
||||
title: 'ibm',
|
||||
title: 'IBM',
|
||||
category: 'Software',
|
||||
route: '/library/ibm.svg',
|
||||
url: 'https://www.ibm.com'
|
||||
},
|
||||
{
|
||||
title: 'trustpilot',
|
||||
title: 'TrustPilot',
|
||||
category: 'Software',
|
||||
route: '/library/trustpilot.svg',
|
||||
url: 'https://www.trustpilot.com'
|
||||
@@ -2383,5 +2377,14 @@ export const svgs: iSVG[] = [
|
||||
dark: '/library/replicate-wordmark_dark.svg'
|
||||
},
|
||||
url: 'https://replicate.com/'
|
||||
},
|
||||
{
|
||||
title: 'Markdown',
|
||||
category: 'Language',
|
||||
route: {
|
||||
light: '/library/markdown-light.svg',
|
||||
dark: '/library/markdown-dark.svg'
|
||||
},
|
||||
url: 'https://www.markdownguide.org/'
|
||||
}
|
||||
];
|
||||
|
||||
+19
-18
@@ -27,32 +27,33 @@ 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 {
|
||||
export interface Category {
|
||||
category: string;
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
- For SVGs:
|
||||
|
||||
```ts
|
||||
type ThemeOptions = {
|
||||
light: string;
|
||||
dark: string;
|
||||
};
|
||||
|
||||
export interface iSVG {
|
||||
id: number;
|
||||
title: string;
|
||||
category: string | string[];
|
||||
route: string | ThemeOptions;
|
||||
wordmark?: string | ThemeOptions;
|
||||
url: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
<Endpoint title="Get all SVGs" method="GET" description="Returns all the SVGs in the repository.">
|
||||
|
||||
@@ -8,13 +8,9 @@
|
||||
// Get categories:
|
||||
import { svgs } from '@/data/svgs';
|
||||
const categories = svgs
|
||||
.map((svg) => svg.category)
|
||||
.flatMap((svg) => Array.isArray(svg.category) ? svg.category : [svg.category])
|
||||
.filter((category, index, array) => array.indexOf(category) === index);
|
||||
|
||||
// Icons:
|
||||
import Heart from 'phosphor-svelte/lib/Heart';
|
||||
import { ArrowUpRight } from 'lucide-svelte';
|
||||
|
||||
// Toaster:
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
// Icons:
|
||||
import { ArrowDown, ArrowDownUpIcon, ArrowUpDownIcon } from 'lucide-svelte';
|
||||
import { buttonStyles } from '@/ui/styles';
|
||||
|
||||
let sorted: boolean = false;
|
||||
let isFirstLoad: boolean = true;
|
||||
@@ -117,15 +118,14 @@
|
||||
</Grid>
|
||||
{#if filteredSvgs.length > 30 && !showAll}
|
||||
<div class="flex items-center justify-center mt-4">
|
||||
<button
|
||||
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"
|
||||
on:click={() => (showAll = true)}
|
||||
>
|
||||
<ArrowDown size={16} strokeWidth={2} />
|
||||
<span>Load All SVGs</span>
|
||||
<span class="opacity-70">
|
||||
({filteredSvgs.length - 30} more)
|
||||
</span>
|
||||
<button class={buttonStyles} on:click={() => (showAll = true)}>
|
||||
<div class="flex items-center space-x-2 relative">
|
||||
<ArrowDown size={16} strokeWidth={2} />
|
||||
<span>Load All SVGs</span>
|
||||
<span class="opacity-70">
|
||||
({filteredSvgs.length - 30} more)
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -13,7 +13,13 @@ export const load = (async ({ params }) => {
|
||||
}
|
||||
|
||||
// Filter out the svg with the matching slug:
|
||||
const svgsByCategory = svgs.filter((svg: iSVG) => svg.category.toLowerCase() === slug);
|
||||
const svgsByCategory = svgs.filter((svg: iSVG) => {
|
||||
if (Array.isArray(svg.category)) {
|
||||
return svg.category.some(categoryItem => categoryItem.toLowerCase() === slug);
|
||||
} else {
|
||||
return svg.category.toLowerCase() === slug;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
category: slug as string,
|
||||
|
||||
+11
-13
@@ -1,20 +1,18 @@
|
||||
import type { tCategory } from './categories';
|
||||
|
||||
type CategoryPair = [tCategory, tCategory];
|
||||
type CategoryTriple = [tCategory, tCategory, tCategory];
|
||||
|
||||
type ThemeOptions = {
|
||||
dark: string;
|
||||
light: string;
|
||||
};
|
||||
|
||||
export interface iSVG {
|
||||
id?: number;
|
||||
title: string;
|
||||
category: tCategory;
|
||||
route:
|
||||
| string // for backwards compat of when theme support was not added
|
||||
| {
|
||||
dark: string;
|
||||
light: string;
|
||||
};
|
||||
wordmark?:
|
||||
| string // for backwards compat of when theme support was not added
|
||||
| {
|
||||
dark: string;
|
||||
light: string;
|
||||
};
|
||||
category: tCategory | CategoryPair | CategoryTriple;
|
||||
route: string | ThemeOptions;
|
||||
wordmark?: string | ThemeOptions;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import * as Dialog from '@/ui/dialog';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
import { X } from 'lucide-svelte';
|
||||
import { flyAndScale } from '@/utils/flyAndScale';
|
||||
|
||||
type $$Props = DialogPrimitive.ContentProps;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let transition: $$Props['transition'] = flyAndScale;
|
||||
export let transitionConfig: $$Props['transitionConfig'] = {
|
||||
duration: 200
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border border-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 bg-white p-6 shadow-lg sm:rounded-lg md:w-full',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<DialogPrimitive.Close
|
||||
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type $$Props = DialogPrimitive.DescriptionProps;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description class={cn('text-sm opacity-70 mb-2', className)} {...$$restProps}>
|
||||
<slot />
|
||||
</DialogPrimitive.Description>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...$$restProps}>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
type $$Props = DialogPrimitive.OverlayProps;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let transition: $$Props['transition'] = fade;
|
||||
export let transitionConfig: $$Props['transitionConfig'] = {
|
||||
duration: 150
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
'fixed inset-0 z-50 bg-neutral-100/80 dark:bg-neutral-900/80 backdrop-blur-sm',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
/>
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
type $$Props = DialogPrimitive.PortalProps;
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Portal {...$$restProps}>
|
||||
<slot />
|
||||
</DialogPrimitive.Portal>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type $$Props = DialogPrimitive.TitleProps;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
class={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</DialogPrimitive.Title>
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
const Root = DialogPrimitive.Root;
|
||||
const Trigger = DialogPrimitive.Trigger;
|
||||
|
||||
import Title from './dialog-title.svelte';
|
||||
import Portal from './dialog-portal.svelte';
|
||||
import Footer from './dialog-footer.svelte';
|
||||
import Header from './dialog-header.svelte';
|
||||
import Overlay from './dialog-overlay.svelte';
|
||||
import Content from './dialog-content.svelte';
|
||||
import Description from './dialog-description.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
//
|
||||
Root as Dialog,
|
||||
Title as DialogTitle,
|
||||
Portal as DialogPortal,
|
||||
Footer as DialogFooter,
|
||||
Header as DialogHeader,
|
||||
Trigger as DialogTrigger,
|
||||
Overlay as DialogOverlay,
|
||||
Content as DialogContent,
|
||||
Description as DialogDescription
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export const buttonStyles =
|
||||
'flex items-center space-x-2 relative h-10 overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800 bg-transparent px-4 text-neutral-950 dark:text-white hover:bg-neutral-200/50 dark:hover:bg-neutral-800/50 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:focus:ring-neutral-700 transition-colors duration-100';
|
||||
|
||||
export const inputStyles =
|
||||
'w-full border-b border-neutral-300 bg-white p-3 px-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';
|
||||
|
||||
export const badgeStyles =
|
||||
'inline-flex items-center px-2.5 py-0.5 rounded-full font-medium bg-neutral-100 dark:bg-neutral-800/50 text-neutral-500 dark:text-neutral-400 text-xs font-mono hover:underline';
|
||||
Reference in New Issue
Block a user