CopyLinkButton.vue (2755B)
1 <template> 2 <div class="copy-link-wrap"> 3 <button class="copy-link-btn" @click="copyLink" aria-label="Copy page link"> 4 <svg 5 class="icon link-icon" 6 :class="{ hidden: copied }" 7 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" 8 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" 9 > 10 <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/> 11 <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/> 12 </svg> 13 <svg 14 class="icon check-icon" 15 :class="{ visible: copied }" 16 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" 17 fill="none" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" 18 > 19 <path 20 ref="checkPath" 21 d="M4 12l5 5L20 6" 22 stroke-dasharray="24" 23 :stroke-dashoffset="copied ? 0 : 24" 24 /> 25 </svg> 26 </button> 27 </div> 28 </template> 29 30 <script setup> 31 import { ref } from 'vue' 32 33 const copied = ref(false) 34 let resetTimer = null 35 36 function copyLink() { 37 const url = window.location.href 38 39 navigator.clipboard.writeText(url).then(showCopied).catch(() => { 40 fallbackCopy(url) 41 }) 42 } 43 44 function fallbackCopy(text) { 45 const textarea = document.createElement('textarea') 46 textarea.value = text 47 textarea.style.position = 'fixed' 48 textarea.style.opacity = '0' 49 document.body.appendChild(textarea) 50 textarea.focus() 51 textarea.select() 52 53 try { 54 document.execCommand('copy') 55 showCopied() 56 } catch (err) { 57 console.error('Copy failed:', err) 58 } 59 60 document.body.removeChild(textarea) 61 } 62 63 function showCopied() { 64 clearTimeout(resetTimer) 65 copied.value = true 66 resetTimer = setTimeout(() => { 67 copied.value = false 68 }, 1400) 69 } 70 </script> 71 72 <style scoped> 73 .copy-link-wrap { 74 display: flex; 75 justify-content: flex-end; 76 margin-bottom: 12px; 77 } 78 79 .copy-link-btn { 80 position: relative; 81 width: 32px; 82 height: 32px; 83 border-radius: 6px; 84 border: 1px solid var(--vp-c-divider); 85 background: var(--vp-c-bg-soft); 86 display: flex; 87 align-items: center; 88 justify-content: center; 89 cursor: pointer; 90 transition: border-color 0.2s; 91 } 92 93 .copy-link-btn:hover { 94 border-color: var(--vp-c-brand); 95 } 96 97 .icon { 98 position: absolute; 99 transition: opacity 0.2s ease, transform 0.2s ease; 100 } 101 102 .link-icon { 103 color: var(--vp-c-text-2); 104 opacity: 1; 105 transform: scale(1); 106 } 107 108 .link-icon.hidden { 109 opacity: 0; 110 transform: scale(0.5); 111 } 112 113 .check-icon { 114 opacity: 0; 115 transform: scale(0.5); 116 } 117 118 .check-icon.visible { 119 opacity: 1; 120 transform: scale(1); 121 } 122 123 .check-icon path { 124 transition: stroke-dashoffset 0.35s ease; 125 } 126 </style>