feat: add visual feedback to copy button in task actions (#11403)

* feat: add visual feedback to copy button in task actions

The copy button now shows a checkmark for 2 seconds after copying to provide visual feedback.
Fixes #11401

* fix: change Check icon import name in TaskActions and update test

- Fix Check icon import name from Check to CheckIcon for consistency in TaskActions.tsx
- Add test to verify check icon is shown when showCopyFeedback is true
- Mock useCopyToClipboard hook in TaskActions.spec.tsx to test copy button functionality

This change resolves the import inconsistency and adds a test to ensure the copy button correctly shows a check icon after successful copy.
This commit is contained in:
Sazid Al Bayazid 2026-02-20 12:55:22 +06:00 committed by GitHub
parent 5db2062d0c
commit 492006d53d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 3 deletions

View file

@ -9,7 +9,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
import { ShareButton } from "./ShareButton"
import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
import { CopyIcon, CheckIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
import { LucideIconButton } from "./LucideIconButton"
interface TaskActionsProps {
@ -20,7 +20,7 @@ interface TaskActionsProps {
export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
const { t } = useTranslation()
const { copyWithFeedback } = useCopyToClipboard()
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
const { debug } = useExtensionState()
return (
@ -33,7 +33,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
{item?.task && (
<LucideIconButton
icon={CopyIcon}
icon={showCopyFeedback ? CheckIcon : CopyIcon}
title={t("history:copyPrompt")}
onClick={(e) => copyWithFeedback(item.task, e)}
/>

View file

@ -3,6 +3,7 @@ import type { HistoryItem } from "@roo-code/types"
import { render, screen, fireEvent } from "@/utils/test-utils"
import { vscode } from "@/utils/vscode"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useCopyToClipboard } from "@/utils/clipboard"
import { TaskActions } from "../TaskActions"
@ -24,8 +25,14 @@ vi.mock("@/context/ExtensionStateContext", () => ({
useExtensionState: vi.fn(),
}))
// Mock the useCopyToClipboard hook
vi.mock("@/utils/clipboard", () => ({
useCopyToClipboard: vi.fn(),
}))
const mockPostMessage = vi.mocked(vscode.postMessage)
const mockUseExtensionState = vi.mocked(useExtensionState)
const mockUseCopyToClipboard = vi.mocked(useCopyToClipboard)
// Mock react-i18next
vi.mock("react-i18next", () => ({
@ -87,6 +94,10 @@ describe("TaskActions", () => {
organizationName: "Test Organization",
},
} as any)
mockUseCopyToClipboard.mockReturnValue({
copyWithFeedback: vi.fn(),
showCopyFeedback: false,
})
})
describe("Share Button Visibility", () => {
@ -353,6 +364,29 @@ describe("TaskActions", () => {
const deleteButton = screen.queryByLabelText("Delete Task (Shift + Click to skip confirmation)")
expect(deleteButton).not.toBeInTheDocument()
})
it("shows check icon when showCopyFeedback is true", () => {
// First render with showCopyFeedback: false (default)
const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
// Verify copy icon is shown initially
const copyButton = screen.getByLabelText("Copy")
expect(copyButton).toBeInTheDocument()
expect(copyButton.querySelector("svg.lucide-copy")).toBeInTheDocument()
expect(copyButton.querySelector("svg.lucide-check")).not.toBeInTheDocument()
// Mock showCopyFeedback: true to simulate successful copy
mockUseCopyToClipboard.mockReturnValue({
copyWithFeedback: vi.fn(),
showCopyFeedback: true,
})
rerender(<TaskActions item={mockItem} buttonsDisabled={false} />)
// Verify check icon is shown after successful copy
expect(copyButton.querySelector("svg.lucide-check")).toBeInTheDocument()
expect(copyButton.querySelector("svg.lucide-copy")).not.toBeInTheDocument()
})
})
describe("Button States", () => {