55 <span class =" text" >{{ text }}</span >
66 </slot >
77 </span >
8- <DuplicateIcon v-if =" text.length" class =" ff-icon" @click =" copyPath" @click.prevent.stop />
9- <span ref =" copied" class =" ff-copied" :class =" { 'ff-copied-left': promptPosition === 'left'}" >Copied!</span >
8+ <button v-if =" text.length" class =" ff-icon-button" @click =" copyPath" >
9+ <DuplicateIcon v-if =" !copied" class =" ff-icon" />
10+ <CheckIcon v-else class =" ff-icon ff-icon-check" />
11+ </button >
1012 </span >
1113</template >
1214
1315<script >
14- import { DuplicateIcon } from ' @heroicons/vue/outline'
16+ import { CheckIcon , DuplicateIcon } from ' @heroicons/vue/outline'
1517
1618import Alert from ' ../services/alerts.js'
1719
1820export default {
1921 name: ' TextCopier' ,
20- components: { DuplicateIcon },
22+ components: { DuplicateIcon, CheckIcon },
2123 props: {
2224 text: {
2325 required: true ,
@@ -46,20 +48,42 @@ export default {
4648 }
4749 },
4850 emits: [' copied' ],
51+ data () {
52+ return {
53+ copied: false
54+ }
55+ },
4956 methods: {
50- copyPath () {
51- navigator .clipboard .writeText (this .text )
57+ async copyPath () {
58+ try {
59+ // Try modern clipboard API first
60+ if (navigator .clipboard && navigator .clipboard .writeText ) {
61+ await navigator .clipboard .writeText (this .text )
62+ } else {
63+ // Fallback for non-secure contexts (HTTP, not HTTPS)
64+ const textArea = document .createElement (' textarea' )
65+ textArea .value = this .text
66+ textArea .style .position = ' fixed'
67+ textArea .style .left = ' -999999px'
68+ textArea .style .top = ' -999999px'
69+ document .body .appendChild (textArea)
70+ textArea .focus ()
71+ textArea .select ()
72+ document .execCommand (' copy' )
73+ document .body .removeChild (textArea)
74+ }
5275
53- if (this .confirmationType === ' alert' ) {
54- Alert .emit (' Copied to Clipboard' , ' confirmation' )
55- } else {
56- // show "Copied" notification
57- this .$refs .copied .style .display = ' inline'
58- // hide after 500ms
59- setTimeout (() => {
60- this .$refs .copied .style .display = ' none'
61- }, 500 )
62- this .$emit (' copied' )
76+ if (this .confirmationType === ' alert' ) {
77+ Alert .emit (' Copied to Clipboard' , ' confirmation' )
78+ } else {
79+ this .copied = true
80+ setTimeout (() => { this .copied = false }, 2000 )
81+ this .$emit (' copied' )
82+ }
83+ } catch (err) {
84+ console .error (' Failed to copy to clipboard:' , err)
85+ // Don't show alert to avoid inject() error
86+ // Just keep the icon in copy state (don't show checkmark)
6387 }
6488 }
6589 }
@@ -75,6 +99,35 @@ export default {
7599 & :hover {
76100 cursor : pointer ;
77101 }
102+ .ff-icon-button {
103+ display : inline-flex ;
104+ align-items : center ;
105+ justify-content : center ;
106+ padding : 4px ;
107+ border : none ;
108+ background : transparent ;
109+ border-radius : 4px ;
110+ cursor : pointer ;
111+ transition : all 0.2s ease ;
112+ color : $ff-grey-600 ;
113+
114+ & :hover {
115+ color : $ff-indigo-600 ;
116+ background-color : $ff-indigo-50 ;
117+ }
118+
119+ & :active {
120+ background-color : $ff-indigo-100 ;
121+ }
122+
123+ .ff-icon {
124+ pointer-events : none ;
125+ }
126+
127+ .ff-icon-check {
128+ color : $ff-green-600 ;
129+ }
130+ }
78131 .ff-copied {
79132 background-color : black ;
80133 color : white ;
0 commit comments