Skip to content

Commit 117ba40

Browse files
refactor: copy to clipboard button
1 parent ef1f52a commit 117ba40

19 files changed

Lines changed: 181 additions & 298 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,5 @@ CLAUDE.md
8080

8181
# Github
8282
/.npmrc
83+
84+
tsconfig.tsbuildinfo
Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
1-
import { useEffect, useState } from 'react'
1+
import React from 'react'
2+
3+
import { useCopyClipboard } from '@cowprotocol/common-hooks'
24

35
import { CopyIcon, CopyMessage, CopyWrapper } from '@/components/TokenDetails/index.styles'
46

57
export const CopyToClipboard = ({ text }: { text: string }) => {
6-
const [copied, setCopied] = useState(false)
7-
8-
const copyToClipboard = async () => {
9-
await navigator.clipboard.writeText(text)
10-
setCopied(true)
11-
}
12-
13-
useEffect(() => {
14-
let timer = 0
15-
16-
if (copied) {
17-
timer = window.setTimeout(() => {
18-
setCopied(false)
19-
}, 3000)
20-
}
21-
22-
return () => {
23-
window.clearTimeout(timer)
24-
}
25-
}, [copied])
8+
const [copied, copyToClipboard] = useCopyClipboard(3000)
269

2710
return (
2811
<CopyWrapper>
2912
{copied && <CopyMessage>Copied!</CopyMessage>}
30-
<CopyIcon src="/images/icons/click-to-copy.svg" alt="Copy contract address" onClick={copyToClipboard} />
13+
<CopyIcon
14+
src="/images/icons/click-to-copy.svg"
15+
alt="Copy contract address"
16+
onClick={() => copyToClipboard(text)}
17+
/>
3118
</CopyWrapper>
3219
)
3320
}

apps/cow-fi/components/ShareBlock.tsx

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
'use client'
22

3-
import { useCallback, useEffect, useRef, useState } from 'react'
3+
import { useCallback, useEffect, useState } from 'react'
44
import type { ReactNode } from 'react'
55

6+
import { useCopyClipboard } from '@cowprotocol/common-hooks'
67
import { Font, UI } from '@cowprotocol/ui'
78

89
import SVG from 'react-inlinesvg'
910
import styled from 'styled-components/macro'
1011

11-
import { buildShareHref, copyUrl, openShare, type ShareTarget } from './ShareBlock.utils'
12+
import { buildShareHref, openShare, type ShareTarget } from './ShareBlock.utils'
1213

1314
const ICON_PATH = '/images/icons/share'
1415

@@ -170,16 +171,15 @@ interface ShareState {
170171
webShareSupported: boolean
171172
copied: boolean
172173
handleShare: (target: ShareTarget) => void
173-
handleCopy: () => Promise<void>
174+
handleCopy: () => void
174175
handleWebShare: () => Promise<void>
175176
}
176177

177178
function useShareBlockState({ url, title, onShare }: ShareBlockProps): ShareState {
178179
const [shareUrl, setShareUrl] = useState(url)
179180
const [shareTitle, setShareTitle] = useState(title)
180181
const [webShareSupported, setWebShareSupported] = useState(false)
181-
const [copied, setCopied] = useState(false)
182-
const copyTimeoutRef = useRef<number | null>(null)
182+
const [copied, setCopied] = useCopyClipboard(1500)
183183

184184
useEffect(() => {
185185
const canonicalLink = document.querySelector<HTMLLinkElement>('link[rel="canonical"]')
@@ -195,14 +195,6 @@ function useShareBlockState({ url, title, onShare }: ShareBlockProps): ShareStat
195195
setWebShareSupported(Boolean(navigator.share))
196196
}, [title, url])
197197

198-
useEffect(() => {
199-
return () => {
200-
if (copyTimeoutRef.current !== null) {
201-
window.clearTimeout(copyTimeoutRef.current)
202-
}
203-
}
204-
}, [])
205-
206198
const handleShare = useCallback(
207199
(target: ShareTarget) => {
208200
if (!shareUrl) return
@@ -218,16 +210,11 @@ function useShareBlockState({ url, title, onShare }: ShareBlockProps): ShareStat
218210
[shareUrl, shareTitle, title, onShare],
219211
)
220212

221-
const handleCopy = useCallback(async () => {
213+
const handleCopy = useCallback(() => {
222214
if (!shareUrl) return
223-
await copyUrl(shareUrl)
224-
setCopied(true)
225-
if (copyTimeoutRef.current !== null) {
226-
window.clearTimeout(copyTimeoutRef.current)
227-
}
228-
copyTimeoutRef.current = window.setTimeout(() => setCopied(false), 1500)
215+
setCopied(shareUrl)
229216
onShare?.()
230-
}, [shareUrl, onShare])
217+
}, [shareUrl, setCopied, onShare])
231218

232219
const handleWebShare = useCallback(async () => {
233220
if (!shareUrl || !navigator.share) return

apps/cow-fi/components/ShareBlock.utils.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,3 @@ export function openShare(href: string): void {
3434
popup.opener = null
3535
}
3636
}
37-
38-
export async function copyUrl(url: string): Promise<void> {
39-
try {
40-
await navigator.clipboard.writeText(url)
41-
return
42-
} catch {}
43-
44-
const ta = document.createElement('textarea')
45-
ta.value = url
46-
ta.style.position = 'fixed'
47-
ta.style.left = '-9999px'
48-
document.body.appendChild(ta)
49-
ta.focus()
50-
ta.select()
51-
document.execCommand('copy')
52-
document.body.removeChild(ta)
53-
}

apps/cow-fi/hooks/useWebShare.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

apps/cowswap-frontend/src/legacy/components/Copy/CopyMod.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import { Trans } from '@lingui/react/macro'
77
import { CheckCircle, Copy } from 'react-feather'
88
import styled, { DefaultTheme, StyledComponentProps } from 'styled-components/macro'
99

10-
import { TransactionStatusText } from 'legacy/components/Copy/index'
11-
12-
// MOD imports
1310
export const CopyIcon = styled(LinkStyledButton)<{ copyIconWidth?: string }>`
1411
--iconSize: var(${UI.ICON_SIZE_NORMAL});
1512
color: inherit;
@@ -33,6 +30,16 @@ export const CopyIcon = styled(LinkStyledButton)<{ copyIconWidth?: string }>`
3330
}
3431
`
3532

33+
export const TransactionStatusText = styled.span<{ isCopied?: boolean }>`
34+
${({ theme }) => theme.flexRowNoWrap};
35+
color: ${({ isCopied }) => (isCopied ? `var(${UI.COLOR_SUCCESS})` : 'inherit')};
36+
align-items: center;
37+
38+
${CopyIcon} {
39+
color: currentColor;
40+
}
41+
`
42+
3643
const CheckCircleIconWrapper = styled(CheckCircle)`
3744
margin: 0 4px 0 0;
3845
`
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
import { UI } from '@cowprotocol/ui'
1+
import Copy, { CopyIcon, TransactionStatusText } from './CopyMod'
22

3-
import styled from 'styled-components/macro'
4-
5-
import Copy, { CopyIcon } from './CopyMod'
6-
7-
export const TransactionStatusText = styled.span<{ isCopied?: boolean }>`
8-
${({ theme }) => theme.flexRowNoWrap};
9-
color: ${({ isCopied }) => (isCopied ? `var(${UI.COLOR_SUCCESS})` : 'inherit')};
10-
align-items: center;
11-
12-
${CopyIcon} {
13-
color: currentColor;
14-
}
15-
`
16-
17-
export { CopyIcon }
3+
export { CopyIcon, TransactionStatusText }
184

195
export default Copy

apps/cowswap-frontend/src/modules/account/containers/CopyHelper/index.tsx

Lines changed: 0 additions & 72 deletions
This file was deleted.

apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/RowQuoteId.container.tsx

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { type MouseEvent, type ReactNode, useCallback } from 'react'
1+
import { type ReactNode } from 'react'
22

3-
import { useCopyClipboard } from '@cowprotocol/common-hooks'
43
import { HoverTooltip, RowFixed } from '@cowprotocol/ui'
54

65
import { Trans, useLingui } from '@lingui/react/macro'
7-
import { Check, Copy } from 'react-feather'
86

97
import { QuoteIdTooltipContent } from './QuoteIdTooltipContent'
108
import { QuoteVerificationBadge } from './QuoteVerificationIndicator'
@@ -24,31 +22,17 @@ interface QuoteIdValueProps {
2422

2523
export function QuoteIdValue({ quoteId }: QuoteIdValueProps): ReactNode {
2624
const { t } = useLingui()
27-
const [isCopied, setCopied] = useCopyClipboard(1500)
28-
29-
const handleCopy = useCallback(
30-
(event: MouseEvent<HTMLButtonElement>) => {
31-
event.stopPropagation()
32-
setCopied(quoteId)
33-
},
34-
[quoteId, setCopied],
35-
)
36-
3725
return (
3826
<styledEl.QuoteIdValueWrapper>
3927
<styledEl.QuoteIdReference>{formatQuoteIdReference(quoteId)}</styledEl.QuoteIdReference>
40-
<styledEl.CopyQuoteIdButton onClick={handleCopy} aria-label={t`Copy full quote ID`}>
41-
{isCopied ? (
42-
<>
43-
<Check size={14} />
44-
<styledEl.CopiedLabel>
45-
<Trans>Copied!</Trans>
46-
</styledEl.CopiedLabel>
47-
</>
48-
) : (
49-
<Copy size={14} />
50-
)}
51-
</styledEl.CopyQuoteIdButton>
28+
<styledEl.CopyQuoteIdButton
29+
value={quoteId}
30+
timeoutMs={1500}
31+
iconSize={14}
32+
copiedLabel={<Trans>Copied!</Trans>}
33+
aria-label={t`Copy full quote ID`}
34+
onClick={(event) => event.stopPropagation()}
35+
/>
5236
</styledEl.QuoteIdValueWrapper>
5337
)
5438
}

apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/RowQuoteId.styled.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LinkStyledButton, UI } from '@cowprotocol/ui'
1+
import { CopyButton } from '@cowprotocol/ui'
22

33
import styled from 'styled-components/macro'
44

@@ -20,7 +20,7 @@ export const QuoteIdTransactionText = styled(TransactionText)`
2020
align-items: center;
2121
`
2222

23-
export const CopyQuoteIdButton = styled(LinkStyledButton)`
23+
export const CopyQuoteIdButton = styled(CopyButton)`
2424
display: inline-flex;
2525
align-items: center;
2626
justify-content: center;
@@ -34,10 +34,9 @@ export const CopyQuoteIdButton = styled(LinkStyledButton)`
3434
text-decoration: none;
3535
opacity: 1;
3636
}
37-
`
3837
39-
export const CopiedLabel = styled.span`
40-
color: var(${UI.COLOR_SUCCESS});
41-
font-size: 11px;
42-
line-height: 1;
38+
> span {
39+
font-size: 11px;
40+
line-height: 1;
41+
}
4342
`

0 commit comments

Comments
 (0)