/ src / lib / components / copy-link-button / copy-link-button.svelte
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>