copy-link-button.svelte
1 <script lang="ts"> 2 import Button from '$lib/components/button/button.svelte'; 3 import LinkIcon from '$lib/components/icons/Link.svelte'; 4 import CheckCircleIcon from '$lib/components/icons/CheckCircle.svelte'; 5 import CopyIcon from '$lib/components/icons/Copy.svelte'; 6 import { fade } from 'svelte/transition'; 7 import type { ComponentProps } from 'svelte'; 8 import { createEventDispatcher } from 'svelte'; 9 10 const dispatch = createEventDispatcher<{ 11 linkCopied: { 12 url: string; 13 }; 14 }>(); 15 16 interface Props { 17 url: string; 18 variant?: ComponentProps<typeof Button>['variant']; 19 success?: import('svelte').Snippet; 20 hover?: import('svelte').Snippet; 21 idle?: import('svelte').Snippet; 22 children?: import('svelte').Snippet; 23 } 24 25 let { url, variant = 'normal', success, hover, idle, children }: Props = $props(); 26 27 let hovering = $state(false); 28 let copySuccess = $state(false); 29 30 function copyShareLink() { 31 navigator.clipboard.writeText(url); 32 copySuccess = true; 33 setTimeout(() => (copySuccess = false), 1000); 34 dispatch('linkCopied', { url }); 35 } 36 </script> 37 38 <Button 39 onmouseenter={() => (hovering = true)} 40 onfocus={() => (hovering = true)} 41 onmouseleave={() => (hovering = false)} 42 onblur={() => (hovering = false)} 43 onclick={copyShareLink} 44 justify="left" 45 {variant} 46 > 47 <div class="button-inner"> 48 <div class="icon"> 49 {#if copySuccess} 50 <span transition:fade={{ duration: 200 }}> 51 {#if success}{@render success()}{:else} 52 <CheckCircleIcon style="fill: var(--color-positive)" /> 53 {/if} 54 </span> 55 {:else if hovering} 56 <span transition:fade={{ duration: 200 }}> 57 {#if hover}{@render hover()}{:else} 58 <CopyIcon style="fill: currentColor" /> 59 {/if} 60 </span> 61 {:else} 62 <span transition:fade={{ duration: 200 }}> 63 {#if idle}{@render idle()}{:else} 64 <LinkIcon style="fill: currentColor" /> 65 {/if} 66 </span> 67 {/if} 68 </div> 69 {#if children}{@render children()}{:else}Copy link{/if} 70 </div> 71 </Button> 72 73 <style> 74 .button-inner { 75 display: flex; 76 align-items: center; 77 gap: 0.125rem; 78 margin-left: -0.25rem; 79 } 80 81 .button-inner .icon { 82 height: 2rem; 83 width: 2rem; 84 display: inline-flex; 85 justify-content: center; 86 align-items: center; 87 position: relative; 88 border-radius: 1rem; 89 } 90 91 .button-inner .icon > * { 92 position: absolute; 93 } 94 </style>