Merge branch 'next' into main

This commit is contained in:
Pablo Hdez
2024-01-25 16:00:23 +00:00
committed by GitHub
33 changed files with 923 additions and 614 deletions
+244
View File
@@ -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}
+3 -3
View File
@@ -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>
+4 -3
View File
@@ -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} />
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.">
+1 -5
View File
@@ -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';
+9 -9
View File
@@ -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}
+7 -1
View File
@@ -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
View File
@@ -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;
}
+38
View File
@@ -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>
+13
View File
@@ -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>
+16
View File
@@ -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>
+13
View File
@@ -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>
+24
View File
@@ -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}
/>
+8
View File
@@ -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>
+16
View File
@@ -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>
+34
View File
@@ -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
};
+8
View File
@@ -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';