Skip to content

feat(CopyButton): add HoverTooltip for copy feedback#7458

Merged
kernelwhisperer merged 11 commits into
developfrom
feat/storybook-3
May 14, 2026
Merged

feat(CopyButton): add HoverTooltip for copy feedback#7458
kernelwhisperer merged 11 commits into
developfrom
feat/storybook-3

Conversation

@kernelwhisperer
Copy link
Copy Markdown
Contributor

@kernelwhisperer kernelwhisperer commented May 6, 2026

Summary

Fixes (partially) https://www.notion.so/cownation/copy-and-copied-UI-is-a-bit-different-in-comparison-with-other-places-3028da5f04ca80398470f5892dac18a4

  • Adds a Tooltip to the copy button when it's icon only 0ca75c9
  • Replace the HoverTooltip component with the one from BaseUI 75f92f6
  • Instead of replacing the HoverTooltip, I decided to add a new one called NewTooltip and deprecate the old one, this allows us to incrementally adopt and simplify the props interface of the Tooltip component.
  • Only the icon-only version of the CopyButton uses the new tooltip.
  • Fixes a typescript error that surfaced only after a new pnpm install 🤷 fa563de 5e9857a

Before: Try it out live

Screencast.From.2026-05-07.12-45-50.mp4

After: Try it out live

Screencast.From.2026-05-07.14-00-02.mp4

Bundle size

As questioned by Leandro, what about bundle size cost?

Before & After
Screenshot From 2026-05-12 14-19-19 Screenshot From 2026-05-12 14-50-13

Rendered: 3.43KB -> 4.00KB - 16% increase
Gzip: 1.06KB -> 1.35KB - 27% increase

But given our app size is 5940KB (or 5.9MB) Gzipped, that's only a 0.0001% increase in total bundle size.

Looks like treeshaking works beautifully, just beware to keep imports like this:

import { Tooltip } from '@base-ui/react/tooltip' // good
import { Tooltip } from '@base-ui/react' // bad

To Test

Note: The arrow is quite broken for small tooltips but that's an existing problem and I will not spend more time on it:
image

  1. Open the Affiliate page
  2. Find the CopyButton which only has an icon
  3. Confirm it shows a tooltip that behaves normally

Background

  • These are the TS errors that surfaced
Screenshot From 2026-05-12 14-23-01 image

Summary by CodeRabbit

  • New Features

    • Introduced NewTooltip component as a modern replacement for existing tooltip implementation
    • Created Storybook stories for UI components including copy and tooltip utilities
    • Integrated internationalization support into Storybook
  • Chores

    • Added Storybook Vite configuration support
    • Updated dependencies: added @storybook/react-vite and @base-ui/react
    • Updated gitignore rules for TypeScript build artifacts
  • Refactor

    • Migrated copy-to-clipboard functionality to modernized CopyButton component across affiliate modules
    • Enhanced type safety with improved type assertions for ENS resolution

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview May 14, 2026 11:28am
explorer-dev Ready Ready Preview May 14, 2026 11:28am
storybook Ready Ready Preview May 14, 2026 11:28am
swap-dev Ready Ready Preview May 14, 2026 11:28am
widget-configurator Ready Ready Preview May 14, 2026 11:28am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored May 14, 2026 11:28am
sdk-tools Ignored Ignored Preview May 14, 2026 11:28am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@kernelwhisperer has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 27 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 52131ac8-8787-4ed4-973d-f7a0fa028991

📥 Commits

Reviewing files that changed from the base of the PR and between 95c29f8 and 7a65902.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .gitignore
  • apps/cowswap-frontend/package.json
  • apps/cowswap-frontend/src/common/pure/AddressInputPanel/hooks/useAddressResolution.ts
  • apps/cowswap-frontend/src/common/pure/ReceiveAmount/index.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.stories.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/index.tsx
  • apps/cowswap-frontend/src/locales/en-US.po
  • apps/cowswap-frontend/src/modules/affiliate/containers/AffiliatePartnerCodeInfo.tsx
  • apps/cowswap-frontend/src/modules/affiliate/containers/AffiliateTraderCodeInfo.tsx
  • apps/cowswap-frontend/src/modules/affiliate/pure/AffiliatePartner/AffiliatePartnerCodeForm.tsx
  • apps/cowswap-frontend/src/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts
  • apps/explorer/package.json
  • apps/storybook/src/main.ts
  • apps/storybook/src/preview.tsx
  • libs/ui/package.json
  • libs/ui/src/index.ts
  • libs/ui/src/pure/CopyButton/CopyButton.stories.tsx
  • libs/ui/src/pure/CopyButton/index.tsx
  • libs/ui/src/pure/Tooltip/Tooltip.stories.tsx
  • libs/ui/src/pure/Tooltip/Tooltip.tsx
  • libs/ui/src/pure/Tooltip/index.tsx

Walkthrough

This PR introduces a new NewTooltip component built on @base-ui/react/tooltip, enhances CopyButton with iconOnly mode and tooltip fallback, improves the legacy CopyHelper with type safety, migrates affiliate copy UI to the new CopyButton, and configures Storybook with comprehensive story discovery and i18n support.

Changes

UI Component Refactoring and Storybook Setup

Layer / File(s) Summary
NewTooltip component and Tooltip library update
libs/ui/src/pure/Tooltip/Tooltip.tsx, libs/ui/src/pure/Tooltip/index.tsx
NewTooltip is implemented on @base-ui/react/tooltip with placement parsing, styled wrappers, and arrow customization. HoverTooltip is deprecated in favor of the new component.
Tooltip stories covering HoverTooltip, HelpTooltip, NewTooltip, and comparison demo
libs/ui/src/pure/Tooltip/Tooltip.stories.tsx
Comprehensive Storybook stories for controlled tooltips, hover tooltips, help and info tooltips, new tooltip with all placements, and a side-by-side comparison of old vs. new implementations.
UI library dependencies and barrel exports
libs/ui/package.json, libs/ui/src/index.ts
Adds @base-ui/react and @types/ms.macro dependencies; updates barrel export to re-export from ./pure/Tooltip/Tooltip.
CopyButton iconOnly prop and NewTooltip integration
libs/ui/src/pure/CopyButton/index.tsx
Replaces showCopiedLabel prop with iconOnly; when enabled, wraps button in NewTooltip with dynamic content switching between copied and idle states.
CopyButton Storybook stories with Default and IconOnly variants
libs/ui/src/pure/CopyButton/CopyButton.stories.tsx
Stories demonstrating default CopyButton with text label and icon-only mode with aria-label for accessibility.
CopyHelper type safety, stories, and import source update
apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.tsx, apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.stories.tsx, apps/cowswap-frontend/src/legacy/components/Copy/index.tsx, apps/cowswap-frontend/src/locales/en-US.po
Adds explicit ReactNode return type to CopyHelper, creates Storybook stories, updates Copy/index.tsx to import from CopyHelper instead of CopyMod, and updates locale reference.
Migrate AffiliatePartnerCodeInfo, AffiliateTraderCodeInfo, and AffiliatePartnerCodeForm to CopyButton
apps/cowswap-frontend/src/modules/affiliate/containers/AffiliatePartnerCodeInfo.tsx, apps/cowswap-frontend/src/modules/affiliate/containers/AffiliateTraderCodeInfo.tsx, apps/cowswap-frontend/src/modules/affiliate/pure/AffiliatePartner/AffiliatePartnerCodeForm.tsx
Replaces legacy CopyHelper usage with CopyButton from @cowprotocol/ui across three affiliate UI modules, using iconOnly and value props with appropriate icon sizing.
Storybook configuration with Vite aliases, story sources, and i18n
apps/cowswap-frontend/package.json, apps/explorer/package.json, apps/storybook/src/main.ts, apps/storybook/src/preview.tsx
Adds @storybook/react-vite dependency to both applications; configures Storybook to add @/ alias for apps/cow-fi and include stories from cowswap-frontend; initializes Lingui i18n and wraps decorator with I18nProvider.
ENS type narrowing, tooltip placement prop, and gitignore pattern
apps/cowswap-frontend/src/common/pure/AddressInputPanel/hooks/useAddressResolution.ts, apps/cowswap-frontend/src/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts, apps/cowswap-frontend/src/common/pure/ReceiveAmount/index.tsx, .gitignore
Improves ENS handling with explicit 0x${string} type casting and optional chaining fallback; adds placement="right" to ReceiveAmount's question helper; broadens gitignore pattern to match all *tsbuildinfo files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cowprotocol/cowswap#7457: Introduces and standardizes copy-to-clipboard via the shared CopyButton component, aligning with this PR's affiliate module migrations and legacy Copy refactoring; also includes the same .gitignore tsbuildinfo pattern update.

Suggested reviewers

  • shoom3301
  • elena-zh

Poem

🐰 A tooltip hops into view,
CopyButton's fresh, polished and new,
Old helpers give way to clean UI,
Storybook's stories now multiply,
Affiliate code flows smooth, it's true!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(CopyButton): add HoverTooltip for copy feedback' accurately describes the main change: adding tooltip functionality to the CopyButton component for improved copy feedback UX.
Description check ✅ Passed The PR description comprehensively covers the summary, testing steps, and background context. However, it lacks the 'To Test' section's expected checkbox format and detailed step-by-step testing instructions that align with the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/storybook-3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 6, 2026

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7a65902
Status:🚫  Build failed.

View logs

@Danziger
Copy link
Copy Markdown
Contributor

Danziger commented May 7, 2026

@kernelwhisperer If the only reason to add BaseUI is to get the better tooltip, I suggest we don't. While I haven't been prioritizing it due to other more important tasks, the Dropdown refactor PR adds @popperjs, which would allow us to easily implement that same tooltip behaviour without adding a UI library as a dependency.

@kernelwhisperer kernelwhisperer requested review from a team May 13, 2026 07:54
@kernelwhisperer kernelwhisperer marked this pull request as ready for review May 13, 2026 07:55
Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

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

Hey @kernelwhisperer , thank you!
However, in my PC (Win11+Chrome browser ) the icon does not show a tooltip/copied message

Image Image

Also, as for the linked task, we have other places where we use 'copy' button

Image Image

The UI is still different for these buttons...

Comment thread libs/ui/src/pure/Tooltip/Tooltip.tsx Outdated
@kernelwhisperer
Copy link
Copy Markdown
Contributor Author

kernelwhisperer commented May 14, 2026

@kernelwhisperer If the only reason to add BaseUI is to get the better tooltip, I suggest we don't. While I haven't been prioritizing it due to other more important tasks, the Dropdown refactor PR adds @popperjs, which would allow us to easily implement that same tooltip behaviour without adding a UI library as a dependency.

@Danziger

Tree shaking works really great with this library, if you're reaching for a banana you're not getting the whole forest.

It might make sense to leverage https://base-ui.com/react/components/menu for your PR, instead of creating a primitive yourself. I think this Tooltip problem is a great example on why we should avoid that.

@kernelwhisperer
Copy link
Copy Markdown
Contributor Author

@elena-zh my bad, it should work now, but only for Affiliate. I will follow up with another PR (very shortly) to replace it everywhere in the app (I will try cow-fi & explorer too).

Let's keep this one as is, to make it easier to review. 🙏

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/ui/src/pure/CopyButton/index.tsx (1)

49-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Icon-only mode can render unnamed buttons and bypass i18n fallback.

When iconOnly is used with no idleContent, the control becomes icon-only and relies on callers to pass aria-label. That creates an accessibility gap (already visible in current affiliate call sites). Also, "Copy to clipboard" is hardcoded instead of localized.

💡 Proposed fix
+import { t } from '@lingui/core/macro'
 import { Trans } from '@lingui/react/macro'

 export function CopyButton(props: CopyButtonProps): ReactNode {
   const {
@@
     onCopy,
     onClick,
     disabled,
+    'aria-label': ariaLabel,
     type,
     ...rest
   } = props
@@
-  const content = isCopied ? iconOnly ? null : <span>{copiedLabel}</span> : idleContent
+  const content = isCopied ? (iconOnly ? null : <span>{copiedLabel}</span>) : idleContent
+  const tooltipContent = isCopied ? copiedLabel : <Trans>Copy to clipboard</Trans>
+  const fallbackAriaLabel = isCopied ? t`Copied` : t`Copy to clipboard`
   const button = (
     <StyledCopyButton
       type={type ?? 'button'}
       onClick={handleClick}
       disabled={disabled}
+      aria-label={ariaLabel ?? fallbackAriaLabel}
       $isCopied={isCopied}
       $color={color}
       {...rest}
@@
   return !idleContent && iconOnly ? (
-    <NewTooltip content={isCopied ? copiedLabel : 'Copy to clipboard'} placement="top">
+    <NewTooltip content={tooltipContent} placement="top">
       {button}
     </NewTooltip>
   ) : (

Also applies to: 82-103

🤖 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 `@libs/ui/src/pure/CopyButton/index.tsx` around lines 49 - 64, The component
currently allows iconOnly buttons with no idleLabel/aria-label and uses a
hardcoded English string; update the CopyButton logic so that when iconOnly is
true it derives an accessible label from idleLabel or, if missing, a localized
fallback (use the existing i18n mechanism that backs copiedLabel, e.g., <Trans>
or t) and ensure that the rendered button receives aria-label set to that
fallback; adjust places that compute the rendered children/idle content
(references: props.iconOnly, props.idleLabel, props.copiedLabel, children) so
the fallback label is used for both visible idleLabel and aria-label when
necessary, and remove any hardcoded "Copy to clipboard" literal.
🧹 Nitpick comments (1)
apps/cowswap-frontend/src/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts (1)

88-88: ⚡ Quick win

Type assertion bypasses receiver address validation.

The cast as \0x${string}` | undefinedforcesorder?.receiver(which can bestring | null | undefined` per the TypeScript errors shown in PR screenshots) to be treated as a hex address. This assertion:

  1. Assumes receiver is always 0x-prefixed when present (might not be validated upstream)
  2. Converts null to undefined implicitly

The optional chaining ?.name correctly guards against useENS returning undefined/null. Consider whether upstream types should be narrowed (e.g., making receiver a 0x${string} type at the source) or adding a runtime check before calling useENS.

🤖 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/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts`
at line 88, The line setting receiverEnsName bypasses validation by casting
order?.receiver to `0x${string}`; instead, validate the receiver at runtime
before calling useENS (or tighten upstream types). Change the logic around
receiverEnsName/useENS to first check order?.receiver is a non-null string and a
valid hex address (e.g., using an address validator like ethers.utils.isAddress
or a simple /^0x[0-9a-fA-F]{40}$/.test) and only pass the validated address into
useENS; if invalid or null, pass undefined. Ensure you update references to
receiverEnsName and keep the optional chaining on the useENS result as-is.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.stories.tsx`:
- Around line 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).

In `@libs/ui/src/pure/Tooltip/Tooltip.tsx`:
- Line 101: The destructuring of placement is unsafe: replace the single-line
destructure using placement?.split('-') with a guarded approach that defaults
when placement is undefined—e.g., compute parts = placement?.split('-') ?? []
(or provide a default placement like "top-center"), then destructure into side
and align with safe defaults (ensure side and align variables get fallback
values such as side = 'top' and align = 'center'); update the line referencing
placement (the const [side, align = 'center'] = placement?.split('-') as [Side,
Align?]) to use this guarded/defaulted logic so runtime errors are avoided.

---

Outside diff comments:
In `@libs/ui/src/pure/CopyButton/index.tsx`:
- Around line 49-64: The component currently allows iconOnly buttons with no
idleLabel/aria-label and uses a hardcoded English string; update the CopyButton
logic so that when iconOnly is true it derives an accessible label from
idleLabel or, if missing, a localized fallback (use the existing i18n mechanism
that backs copiedLabel, e.g., <Trans> or t) and ensure that the rendered button
receives aria-label set to that fallback; adjust places that compute the
rendered children/idle content (references: props.iconOnly, props.idleLabel,
props.copiedLabel, children) so the fallback label is used for both visible
idleLabel and aria-label when necessary, and remove any hardcoded "Copy to
clipboard" literal.

---

Nitpick comments:
In
`@apps/cowswap-frontend/src/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts`:
- Line 88: The line setting receiverEnsName bypasses validation by casting
order?.receiver to `0x${string}`; instead, validate the receiver at runtime
before calling useENS (or tighten upstream types). Change the logic around
receiverEnsName/useENS to first check order?.receiver is a non-null string and a
valid hex address (e.g., using an address validator like ethers.utils.isAddress
or a simple /^0x[0-9a-fA-F]{40}$/.test) and only pass the validated address into
useENS; if invalid or null, pass undefined. Ensure you update references to
receiverEnsName and keep the optional chaining on the useENS result as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 24bd5122-a68b-4685-ae05-ca2a4b47c834

📥 Commits

Reviewing files that changed from the base of the PR and between d950f31 and 95c29f8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .gitignore
  • apps/cowswap-frontend/package.json
  • apps/cowswap-frontend/src/common/pure/AddressInputPanel/hooks/useAddressResolution.ts
  • apps/cowswap-frontend/src/common/pure/ReceiveAmount/index.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.stories.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/CopyHelper.tsx
  • apps/cowswap-frontend/src/legacy/components/Copy/index.tsx
  • apps/cowswap-frontend/src/locales/en-US.po
  • apps/cowswap-frontend/src/modules/affiliate/containers/AffiliatePartnerCodeInfo.tsx
  • apps/cowswap-frontend/src/modules/affiliate/containers/AffiliateTraderCodeInfo.tsx
  • apps/cowswap-frontend/src/modules/affiliate/pure/AffiliatePartner/AffiliatePartnerCodeForm.tsx
  • apps/cowswap-frontend/src/modules/orderProgressBar/hooks/useOrderProgressBarProps.ts
  • apps/explorer/package.json
  • apps/storybook/src/main.ts
  • apps/storybook/src/preview.tsx
  • libs/ui/package.json
  • libs/ui/src/index.ts
  • libs/ui/src/pure/CopyButton/CopyButton.stories.tsx
  • libs/ui/src/pure/CopyButton/index.tsx
  • libs/ui/src/pure/Tooltip/Tooltip.stories.tsx
  • libs/ui/src/pure/Tooltip/Tooltip.tsx
  • libs/ui/src/pure/Tooltip/index.tsx

Comment on lines +20 to +24
export const IconOnly: Story = {
args: {
children: undefined,
hideCopiedLabel: true,
},
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).


export function NewTooltip(props: NewTooltipProps): ReactNode {
const { content, children, placement } = props
const [side, align = 'center'] = placement?.split('-') as [Side, Align?]
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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the optional prop + unsafe destructuring pattern and find call sites that omit placement.
rg -n "placement\\?: TooltipPlacement|placement\\?\\.split\\('-'\\)" libs/ui/src/pure/Tooltip/Tooltip.tsx
rg -nP --type=tsx "<NewTooltip(?![^>]*\\bplacement=)"

Repository: cowprotocol/cowswap

Length of output: 222


🏁 Script executed:

# Find all usages of NewTooltip to see if placement is ever omitted
rg -n "NewTooltip\b" --type ts --type js -A 3 -B 1 | head -100

Repository: cowprotocol/cowswap

Length of output: 3473


🏁 Script executed:

# Also check the actual return type and see how it's actually destructured
cat -n libs/ui/src/pure/Tooltip/Tooltip.tsx | sed -n '90,110p'

Repository: cowprotocol/cowswap

Length of output: 907


Fix unsafe destructuring of optional placement prop.

Line 101 will throw at runtime when placement is undefined, since placement?.split('-') returns undefined and is destructured immediately. Although the interface declares placement? as optional, the destructuring assumes it's always defined.

Proposed fix
 export function NewTooltip(props: NewTooltipProps): ReactNode {
   const { content, children, placement } = props
-  const [side, align = 'center'] = placement?.split('-') as [Side, Align?]
+  const [side = 'top', align = 'center'] = (placement?.split('-') ?? []) as [Side?, Align?]
📝 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
const [side, align = 'center'] = placement?.split('-') as [Side, Align?]
const [side = 'top', align = 'center'] = (placement?.split('-') ?? []) as [Side?, Align?]
🤖 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 `@libs/ui/src/pure/Tooltip/Tooltip.tsx` at line 101, The destructuring of
placement is unsafe: replace the single-line destructure using
placement?.split('-') with a guarded approach that defaults when placement is
undefined—e.g., compute parts = placement?.split('-') ?? [] (or provide a
default placement like "top-center"), then destructure into side and align with
safe defaults (ensure side and align variables get fallback values such as side
= 'top' and align = 'center'); update the line referencing placement (the const
[side, align = 'center'] = placement?.split('-') as [Side, Align?]) to use this
guarded/defaulted logic so runtime errors are avoided.

Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

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

Hey @kernelwhisperer , thank you!
Approving with an agreement to replace the component in other places in a separate PR :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants