Merge pull request #428 from selemondev/feat/svg-component-copy

feat: add copy Svg as Vue or Svelte component
This commit is contained in:
Pablo Hdez 2024-10-29 18:07:36 +00:00 committed by GitHub
commit 9324c532b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 153 additions and 3 deletions

View File

@ -12,7 +12,12 @@
import { copyToClipboard as figmaCopyToClipboard } from '@/figma/copy-to-clipboard';
import { buttonStyles } from '@/ui/styles';
import { cn } from '@/utils/cn';
import { componentTemplate } from '@/utils/componentTemplate';
//Icons:
import ReactIcon from './icons/reactIcon.svelte';
import VueIcon from './icons/vueIcon.svelte';
import SvelteIcon from './icons/svelteIcon.svelte';
// Props:
export let iconSize = 24;
@ -67,6 +72,7 @@
optionsOpen = false;
const content = await getSvgContent(svgUrlToCopy);
if (isInFigma) {
figmaCopyToClipboard(content);
}
@ -125,6 +131,34 @@
isLoading = false;
};
// Copy as either Vue or Svelte component:
const copySvgComponent = async (ts: boolean, framework: 'Vue' | 'Svelte') => {
try {
const svgUrlToCopy = getSvgUrl();
optionsOpen = false;
const content = await getSvgContent(svgUrlToCopy);
const copyCode = componentTemplate(ts ? 'ts' : '', content, framework);
if (copyCode) {
await clipboard(copyCode);
}
const category = Array.isArray(svgInfo?.category)
? svgInfo.category.sort().join(' - ')
: svgInfo.category;
toast.success(`Copied as ${framework} ${ts ? 'TS' : 'JS'} component`, {
description: `${svgInfo?.title} - ${category}`
});
} catch (err) {
console.error(`Error copying ${framework} component:`, err);
toast.error(`Failed to copy ${framework} component`);
}
};
</script>
<Popover.Root open={optionsOpen} onOpenChange={(isOpen) => (optionsOpen = isOpen)}>
@ -142,7 +176,7 @@
</Popover.Trigger>
<Popover.Content class="flex flex-col space-y-2" sideOffset={3}>
<button
class={cn(buttonStyles, 'rounded-md w-full')}
class={cn(buttonStyles, 'w-full rounded-md')}
title={isWordmarkSvg ? 'Copy wordmark SVG to clipboard' : 'Copy SVG to clipboard'}
on:click={() => copyToClipboard()}
>
@ -150,7 +184,7 @@
<span>Copy SVG</span>
</button>
<button
class={cn(buttonStyles, 'rounded-md w-full')}
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as React component"
disabled={isLoading}
on:click={() => convertSvgReactComponent(true)}
@ -159,7 +193,7 @@
<span>Copy TSX</span>
</button>
<button
class={cn(buttonStyles, 'rounded-md w-full')}
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as React component"
disabled={isLoading}
on:click={() => convertSvgReactComponent(false)}
@ -167,5 +201,43 @@
<ReactIcon iconSize={18} color="#60a5fa" />
<span>Copy JSX</span>
</button>
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Vue component"
disabled={isLoading}
on:click={() => copySvgComponent(false, 'Vue')}
>
<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={() => copySvgComponent(true, 'Vue')}
>
<VueIcon iconSize={18} />
<span>Copy TS</span>
</button>
<button
class={cn(buttonStyles, 'w-full rounded-md')}
title="Copy as Svelte component"
disabled={isLoading}
on:click={() => copySvgComponent(false, 'Svelte')}
>
<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={() => copySvgComponent(true, 'Svelte')}
>
<SvelteIcon iconSize={18} />
<span>Copy TS</span>
</button>
</Popover.Content>
</Popover.Root>

View File

@ -0,0 +1,17 @@
<script lang="ts">
export let iconSize: number;
</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
>

View File

@ -0,0 +1,14 @@
<script lang="ts">
export let iconSize: number;
</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
>

View File

@ -0,0 +1,21 @@
import { parseSvgContent } from './parseSvgContent';
export const componentTemplate = (lang: string, content: string, framework: 'Vue' | 'Svelte') => {
const { templateContent, componentStyle } = parseSvgContent(content, framework);
if (framework === 'Vue') {
return `<script setup${lang ? ` lang="${lang}"` : ''}></script>
<template>
${templateContent}
</template>
${componentStyle}
`;
} else {
return `<script${lang ? ` lang="${lang}"` : ''}></script>
${templateContent}
${componentStyle}
`;
}
};

View File

@ -0,0 +1,26 @@
export const parseSvgContent = (content: string, framework: 'Vue' | 'Svelte') => {
if (content.includes('<?xml')) {
content = content.replace(/<\?xml[^>]*\?>/i, '');
}
// Regular expression to match <style> tags in the SVG content
const styleTagRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
// Extract styles and store them in an array
const styles = [];
let matched;
while ((matched = styleTagRegex.exec(content)) !== null) {
styles.push(matched[1]); // Add the style content (not including the style tag)
}
// Remove <style> tags from the SVG content
const templateContent = content.replace(styleTagRegex, '');
const componentStyle = styles.length
? `<style${framework === 'Vue' ? ' scoped' : ''}>\n${styles.join('\n')}\n</style>`
: '';
return {
componentStyle,
templateContent
};
};