Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ CLAUDE.md
# Github
/.npmrc

tsconfig.tsbuildinfo
*tsbuildinfo
1 change: 1 addition & 0 deletions apps/cowswap-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"workbox-strategies": "^6.6.1"
},
"devDependencies": {
"@storybook/react-vite": "^10.3.1",
"@testing-library/react": "16.3.0",
"@types/ms": "^2.1.0",
"@types/ms.macro": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export function useAddressResolution(
targetChainId: TargetChainId | undefined,
): { address: string | null; loading: boolean; name: string | null } {
const strategy = getAddressValidationStrategy(targetChainId)
const { address: ensAddress, loading: ensLoading, name } = useENS(strategy.supportsENS ? value : undefined)
const {
address: ensAddress,
loading: ensLoading,
name,
} = useENS((strategy.supportsENS ? value : undefined) as `0x${string}` | undefined)

return useMemo(() => {
if (!strategy.supportsENS) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function ReceiveAmount(props: ReceiveAmountProps): ReactNode {
<div>
<span>{!isSell ? t`From (incl. fees)` : t`Receive (incl. fees)`}</span>
<styledEl.QuestionHelperWrapped
placement="right"
text={
bridgeEstimatedAmounts ? (
<BridgeReceiveAmountInfo bridgeEstimatedAmounts={bridgeEstimatedAmounts} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import CopyHelper from './CopyHelper'

import type { Meta, StoryObj } from '@storybook/react-vite'

const meta = {
title: 'Cowswap/CopyHelper',
component: CopyHelper,
args: {
toCopy: '0x1234567890abcdef1234567890abcdef12345678',
children: 'Copy address',
},
} satisfies Meta<typeof CopyHelper>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {}

export const IconOnly: Story = {
args: {
children: undefined,
hideCopiedLabel: true,
},
Comment on lines +20 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an accessible label for the icon-only story.

IconOnly removes visible text, so the showcased usage should provide an accessible name for assistive tech.

Suggested patch
 export const IconOnly: Story = {
   args: {
     children: undefined,
     hideCopiedLabel: true,
+    'aria-label': 'Copy address',
   },
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const IconOnly: Story = {
args: {
children: undefined,
hideCopiedLabel: true,
},
export const IconOnly: Story = {
args: {
children: undefined,
hideCopiedLabel: true,
'aria-label': 'Copy address',
},
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.stories.tsx`
around lines 20 - 24, The IconOnly story removes visible text so it needs an
accessible name; update the IconOnly Story args to include an explicit
accessible label (e.g., add a prop such as ariaLabel or label to the args
alongside children: undefined and hideCopiedLabel: true) so assistive
technologies have a name for the icon; ensure the prop name matches the
CopyHelper/Copy component API (use ariaLabel if the component accepts aria
props, otherwise use the component's label prop).

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { MouseEvent } from 'react'
import React, { MouseEvent, ReactNode } from 'react'

import { useCopyClipboard } from '@cowprotocol/common-hooks'
import { UI, LinkStyledButton } from '@cowprotocol/ui'
Expand Down Expand Up @@ -63,15 +63,11 @@ interface CopyHelperProps
hideCopiedLabel?: boolean
}

// TODO: Add proper return type annotation
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default function CopyHelper(props: CopyHelperProps) {
export default function CopyHelper(props: CopyHelperProps): ReactNode {
const { toCopy, children, clickableLink, copyIconWidth, hideCopiedLabel = false, ...rest } = props
const [isCopied, setCopied] = useCopyClipboard()

// TODO: Add proper return type annotation
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
const handleClick = (event: MouseEvent<HTMLButtonElement>): void => {
event.stopPropagation()
setCopied(toCopy)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/legacy/components/Copy/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Copy, { CopyIcon, TransactionStatusText } from './CopyMod'
import Copy, { CopyIcon, TransactionStatusText } from './CopyHelper'

export { CopyIcon, TransactionStatusText }

Expand Down
2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/locales/en-US.po
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,7 @@ msgstr "Sending {nativeTokenSymbol}"
msgid "Tokens"
msgstr "Tokens"

#: apps/cowswap-frontend/src/legacy/components/Copy/CopyMod.tsx
#: apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.tsx
msgid "Copied"
msgstr "Copied"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import LockedIcon from '@cowprotocol/assets/images/icon-locked-2.svg'
import ICON_QR_CODE from '@cowprotocol/assets/images/icon-qr-code-v2.svg'
import ICON_SOCIAL_X from '@cowprotocol/assets/images/icon-social-x.svg'
import { formatShortDate } from '@cowprotocol/common-utils'
import { CopyButton } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'

import { Trans } from '@lingui/react/macro'
import SVG from 'react-inlinesvg'

import CopyHelper from 'legacy/components/Copy'

import { useModalState } from 'common/hooks/useModalState'

import { AffiliatePartnerQrModal } from './AffiliatePartnerQrModal'
Expand Down Expand Up @@ -64,7 +63,7 @@ export function AffiliatePartnerCodeInfo(): ReactNode {
<LinkedCard>
<LinkedCodeRow>
<LinkedCopy>
<CopyHelper toCopy={refCode} iconSize={16} hideCopiedLabel />
<CopyButton value={refCode} iconSize={16} iconOnly />
<LinkedCodeText>{refCode}</LinkedCodeText>
</LinkedCopy>
<LinkedBadge>
Expand All @@ -74,7 +73,7 @@ export function AffiliatePartnerCodeInfo(): ReactNode {
</LinkedCodeRow>
<LinkedLinkRow>
<LinkedCopy>
<CopyHelper toCopy={referralLink} iconSize={16} hideCopiedLabel />
<CopyButton value={referralLink} iconSize={16} iconOnly />
<LinkedLinkText>
{referralLink
.split(/(ref=)/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import CheckIcon from '@cowprotocol/assets/cow-swap/order-check.svg'
import LockedIcon from '@cowprotocol/assets/images/icon-locked-2.svg'
import { useTimeAgo } from '@cowprotocol/common-hooks'
import { formatDateWithTimezone, formatShortDate } from '@cowprotocol/common-utils'
import { ButtonPrimary, HelpTooltip } from '@cowprotocol/ui'
import { ButtonPrimary, CopyButton, HelpTooltip } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'

import { Trans } from '@lingui/react/macro'
import ms from 'ms.macro'
import SVG from 'react-inlinesvg'

import CopyHelper from 'legacy/components/Copy'

import { useAffiliateTraderInfo } from '../hooks/useAffiliateTraderInfo'
import { useAffiliateTraderStats } from '../hooks/useAffiliateTraderStats'
import { useIsRefCodeExpired } from '../hooks/useIsRefCodeExpired'
Expand Down Expand Up @@ -63,7 +61,7 @@ export function AffiliateTraderCodeInfo(): ReactNode {
<LinkedCard $isExpired={isExpired}>
<LinkedCodeRow $isExpired={isExpired}>
<LinkedCopy>
<CopyHelper toCopy={savedCode} iconSize={16} hideCopiedLabel />
<CopyButton value={savedCode} iconSize={16} iconOnly />
<LinkedCodeText>{savedCode}</LinkedCodeText>
</LinkedCopy>
{isExpired ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { KeyboardEvent, ReactNode, useCallback, useId } from 'react'

import { HelpTooltip, UI } from '@cowprotocol/ui'
import { CopyButton, HelpTooltip, UI } from '@cowprotocol/ui'

import { t } from '@lingui/core/macro'
import { Trans } from '@lingui/react/macro'
import { RotateCw } from 'react-feather'
import styled from 'styled-components/macro'

import CopyHelper from 'legacy/components/Copy'

import { REF_CODE_MIN_LENGTH } from 'modules/affiliate'

import { AffiliatePartnerCodeErrorMessage } from './AffiliatePartnerCodeErrorMessage'
Expand Down Expand Up @@ -158,10 +156,10 @@ function ReferralLinkPreview({ previewLink, isCopyDisabled }: ReferralLinkPrevie
<ConnectedPreviewCard>
<LinkedLinkRow>
<LinkedCopy>
<CopyHelper
toCopy={previewLink}
<CopyButton
value={previewLink}
iconSize={16}
hideCopiedLabel
iconOnly
disabled={isCopyDisabled}
aria-label={isCopyDisabled ? t`Enter a code to copy the referral link` : t`Copy referral link`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function useOrderProgressBarProps(
)

const surplusData = useGetSurplusData(order)
const receiverEnsName = useENS(order?.receiver).name || undefined
const receiverEnsName = useENS(order?.receiver as `0x${string}` | undefined)?.name || undefined

const props = useMemo(() => {
// Add supplementary stuff
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"yaml": "^2.8.1"
},
"devDependencies": {
"@storybook/react-vite": "^10.3.1",
"@testing-library/react": "16.3.0",
"@types/cytoscape": "^3.21.9",
"@types/cytoscape-popper": "^2.0.0",
Expand Down
11 changes: 9 additions & 2 deletions apps/storybook/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,17 @@ function getStorybookProcessEnv(configType: 'DEVELOPMENT' | 'PRODUCTION'): Recor
}
}

const workspaceAliases = getWorkspaceAliases()
const workspaceAliases = [
...getWorkspaceAliases(),
{ find: /^@\/(.*)$/, replacement: path.resolve(process.cwd(), 'apps/cow-fi/$1') },
]

const config: StorybookConfig = {
stories: ['../../../libs/ui/src/**/*.stories.@(ts|tsx)'],
stories: [
'../../../libs/ui/src/**/*.stories.@(ts|tsx)',
'../../../apps/cowswap-frontend/src/**/*.stories.@(ts|tsx)',
// '../../../apps/explorer/src/components/common/CopyButton/CopyButton.stories.@(ts|tsx)',
],
addons: [getAbsolutePath('@storybook/addon-a11y'), getAbsolutePath('@storybook/addon-docs')],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
Expand Down
17 changes: 13 additions & 4 deletions apps/storybook/src/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { DEFAULT_LOCALE } from '@cowprotocol/common-const'

import { i18n } from '@lingui/core'
import { I18nProvider } from '@lingui/react'
import { createGlobalStyle, ThemeProvider } from 'styled-components/macro'

import { Color } from '../../../libs/ui/src/colors'
Expand All @@ -8,6 +12,9 @@ import { ThemeColorVars } from '../../../libs/ui/src/theme/ThemeColorVars'

import type { Preview } from '@storybook/react-vite'

i18n.load(DEFAULT_LOCALE, {})
i18n.activate(DEFAULT_LOCALE)

type ThemeMode = 'dark' | 'light'

const checkerboardBackground = `repeating-conic-gradient(
Expand Down Expand Up @@ -57,10 +64,12 @@ const preview: Preview = {
const theme = baseTheme(themeMode)

return (
<ThemeProvider theme={theme}>
<GlobalStyle $themeMode={themeMode} />
<Story />
</ThemeProvider>
<I18nProvider i18n={i18n}>
<ThemeProvider theme={theme}>
<GlobalStyle $themeMode={themeMode} />
<Story />
</ThemeProvider>
</I18nProvider>
)
},
],
Expand Down
7 changes: 5 additions & 2 deletions libs/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,26 @@
}
},
"dependencies": {
"@cowprotocol/cow-sdk": "9.0.2",
"@base-ui/react": "^1.4.1",
"@cowprotocol/analytics": "workspace:*",
"@cowprotocol/assets": "workspace:*",
"@cowprotocol/common-const": "workspace:*",
"@cowprotocol/common-hooks": "workspace:*",
"@cowprotocol/common-utils": "workspace:*",
"@cowprotocol/core": "workspace:*",
"@cowprotocol/cow-sdk": "9.0.2",
"@cowprotocol/currency": "workspace:*",
"@cowprotocol/types": "workspace:*",
"@cowprotocol/ui-utils": "workspace:*",
"@lingui/core": "^5.4.1",
"@lingui/react": "^5.4.1",
"@popperjs/core": "^2.4.4",
"@reach/menu-button": "^0.18.0",
"@reach/portal": "^0.18.0",
"@cowprotocol/currency": "workspace:*",
"bignumber.js": "^9.1.2",
"color2k": "^2.0.2",
"jotai": "2.16.2",
"ms.macro": "^2.0.0",
"next": "15.2.8",
"polished": "^4.0.5",
"react": "19.1.2",
Expand All @@ -56,6 +58,7 @@
"devDependencies": {
"@storybook/addon-docs": "^10.3.1",
"@storybook/react-vite": "^10.3.1",
"@types/ms.macro": "^2.0.0",
"@types/react": "19.1.3",
"@types/rebass": "^4.0.10",
"@types/styled-components": "5.1.34"
Expand Down
1 change: 1 addition & 0 deletions libs/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export * from './pure/TokenAmount'
export * from './pure/TokenName'
export * from './pure/TokenSymbol'
export * from './pure/Tooltip'
export * from './pure/Tooltip/Tooltip'
export * from './pure/TruncatedText'
export * from './styles'
export * from './theme'
Expand Down
27 changes: 27 additions & 0 deletions libs/ui/src/pure/CopyButton/CopyButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { CopyButton } from './index'

import type { Meta, StoryObj } from '@storybook/react-vite'

const meta = {
title: 'UI/CopyButton',
component: CopyButton,
args: {
value: '0x1234567890abcdef1234567890abcdef12345678',
children: 'Copy address',
copiedLabel: 'Copied',
},
} satisfies Meta<typeof CopyButton>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {}

export const IconOnly: Story = {
args: {
children: undefined,
iconOnly: true,
'aria-label': 'Copy address',
},
}
18 changes: 13 additions & 5 deletions libs/ui/src/pure/CopyButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import styled from 'styled-components/macro'

import { UI } from '../../enum'
import { LinkStyledButton } from '../LinkStyledButton'
import { NewTooltip } from '../Tooltip/Tooltip'

export interface CopyButtonState {
isCopied: boolean
Expand All @@ -21,7 +22,7 @@ export interface CopyButtonProps
children?: CopyButtonChildren
copiedLabel?: ReactNode
idleLabel?: ReactNode
showCopiedLabel?: boolean
iconOnly?: boolean
iconSize?: number
iconPosition?: 'left' | 'right'
color?: string
Expand Down Expand Up @@ -50,7 +51,7 @@ export function CopyButton(props: CopyButtonProps): ReactNode {
children,
copiedLabel = <Trans>Copied</Trans>,
idleLabel,
showCopiedLabel = true,
iconOnly = false,
iconSize = 16,
iconPosition = 'left',
color,
Expand Down Expand Up @@ -78,9 +79,8 @@ export function CopyButton(props: CopyButtonProps): ReactNode {
const renderedChildren = typeof children === 'function' ? children({ isCopied }) : children
const idleContent = renderedChildren ?? idleLabel
const icon = isCopied ? <Check size={iconSize} /> : <Copy size={iconSize} />
const content = isCopied ? showCopiedLabel ? <span>{copiedLabel}</span> : null : idleContent

return (
const content = isCopied ? iconOnly ? null : <span>{copiedLabel}</span> : idleContent
const button = (
<StyledCopyButton
type={type ?? 'button'}
onClick={handleClick}
Expand All @@ -93,4 +93,12 @@ export function CopyButton(props: CopyButtonProps): ReactNode {
{iconPosition === 'left' ? content : icon}
</StyledCopyButton>
)

return !idleContent && iconOnly ? (
<NewTooltip content={isCopied ? copiedLabel : 'Copy to clipboard'} placement="top">
{button}
</NewTooltip>
) : (
button
)
}
Loading
Loading