mirror of
https://github.com/pheralb/svgl.git
synced 2024-12-05 05:42:35 +08:00
Merge pull request #428 from selemondev/feat/svg-component-copy
feat: add copy Svg as Vue or Svelte component
This commit is contained in:
commit
9324c532b4
@ -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>
|
||||
|
17
src/components/icons/svelteIcon.svelte
Normal file
17
src/components/icons/svelteIcon.svelte
Normal 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
|
||||
>
|
14
src/components/icons/vueIcon.svelte
Normal file
14
src/components/icons/vueIcon.svelte
Normal 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
|
||||
>
|
21
src/utils/componentTemplate.ts
Normal file
21
src/utils/componentTemplate.ts
Normal 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}
|
||||
`;
|
||||
}
|
||||
};
|
26
src/utils/parseSvgContent.ts
Normal file
26
src/utils/parseSvgContent.ts
Normal 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
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user