Skip to content

Commit ebc7dc3

Browse files
authored
Merge pull request #61 from contextforge-org/44-copied-feedback-on-copy-button
fix: consistent Copied feedback on every copy button
2 parents 2fa9864 + 3d70cac commit ebc7dc3

50 files changed

Lines changed: 643 additions & 414 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AuthProvider } from "./auth/AuthContext";
22
import { ThemeProvider } from "./hooks/useTheme";
33
import { Toaster } from "@/components/ui/sonner";
4+
import { TooltipProvider } from "@/components/ui/tooltip";
45
import { RouterProvider, Route, Redirect, AuthGuard, useRouter } from "./router";
56
import { AppShell } from "./components/layout/AppShell";
67
import { Login } from "./pages/Login";
@@ -97,8 +98,10 @@ export function App() {
9798
<RouterProvider>
9899
<ThemeProvider>
99100
<AuthProvider>
100-
<Routes />
101-
<Toaster />
101+
<TooltipProvider>
102+
<Routes />
103+
<Toaster />
104+
</TooltipProvider>
102105
</AuthProvider>
103106
</ThemeProvider>
104107
</RouterProvider>

src/components/gateways/VirtualServerDetailsPanel.tsx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useIntl } from "react-intl";
44
import {
55
Activity,
66
Box,
7-
Copy,
87
EllipsisVertical,
98
Loader2,
109
MessageSquareCode,
@@ -20,13 +19,13 @@ import {
2019
import { MCPIcon } from "@/components/icons/MCPIcon";
2120
import { Badge } from "@/components/ui/badge";
2221
import { Button } from "@/components/ui/button";
22+
import { CopyButton } from "@/components/ui/copy-button";
2323
import { InlineTagAdd } from "@/components/ui/inline-tag-add";
2424
import { CopyValue } from "@/components/ui/copy-value";
2525
import { Input } from "@/components/ui/input";
2626
import { cn } from "@/lib/utils";
2727
import type { MCPServer, VirtualServer } from "@/types/server";
2828
import type { ComponentFilter } from "@/components/gateways/types";
29-
import { copyToClipboard } from "@/lib/clipboard";
3029
import {
3130
buildComponentItems,
3231
formatServerDateTime,
@@ -586,32 +585,28 @@ export function VirtualServerDetailsPanel({
586585
<span className="min-w-0 truncate text-muted-foreground">{title}</span>
587586
<span className="flex min-w-0 items-center gap-2 font-mono text-[13px] text-muted-foreground">
588587
<span className="truncate">{identifier}</span>
589-
<Button
590-
type="button"
591-
variant="ghost"
592-
size="icon-xs"
593-
aria-label={`Copy ${title}`}
588+
<CopyButton
589+
value={identifier}
590+
label={intl.formatMessage(
591+
{ id: "common.copyValue" },
592+
{ label: title },
593+
)}
594594
className="size-5 text-muted-foreground"
595-
onClick={() => copyToClipboard(identifier)}
596-
>
597-
<Copy className="size-3.5" />
598-
</Button>
595+
/>
599596
</span>
600597
</>
601598
) : (
602599
<>
603600
<span className="flex min-w-0 items-center gap-2 font-mono text-[13px] text-muted-foreground">
604601
<span className="truncate">{identifier}</span>
605-
<Button
606-
type="button"
607-
variant="ghost"
608-
size="icon-xs"
609-
aria-label={`Copy ${identifier}`}
602+
<CopyButton
603+
value={identifier}
604+
label={intl.formatMessage(
605+
{ id: "common.copyValue" },
606+
{ label: identifier },
607+
)}
610608
className="size-5 text-muted-foreground"
611-
onClick={() => copyToClipboard(identifier)}
612-
>
613-
<Copy className="size-3.5" />
614-
</Button>
609+
/>
615610
</span>
616611
<span aria-hidden="true" />
617612
</>

src/components/prompts/PromptDefinitionTable.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Copy, MoreHorizontal } from "lucide-react";
1+
import { MoreHorizontal } from "lucide-react";
22
import { useIntl } from "react-intl";
33

44
import type { PromptRead } from "@/generated/types";
55
import { Button } from "@/components/ui/button";
6+
import { CopyButton } from "@/components/ui/copy-button";
67
import {
78
DropdownMenu,
89
DropdownMenuContent,
@@ -17,7 +18,6 @@ import {
1718
TableHeader,
1819
TableRow,
1920
} from "@/components/ui/table";
20-
import { copyToClipboard } from "@/lib/clipboard";
2121
import { truncateMiddle } from "@/components/gateways/utils";
2222

2323
export interface PromptDefinitionTableProps {
@@ -88,22 +88,15 @@ export function PromptDefinitionTable({
8888
<span className="min-w-0 truncate font-mono text-xs text-muted-foreground">
8989
{truncateMiddle(prompt.id, 40)}
9090
</span>
91-
<Button
92-
type="button"
93-
variant="ghost"
94-
size="icon-xs"
95-
aria-label={intl.formatMessage(
91+
<CopyButton
92+
value={prompt.id}
93+
label={intl.formatMessage(
9694
{ id: "prompts.details.table.copyPromptId" },
9795
{ name: prompt.name },
9896
)}
97+
iconClassName="size-3"
9998
className="ml-4 size-4 shrink-0 text-muted-foreground hover:text-foreground"
100-
onClick={(e) => {
101-
e.stopPropagation();
102-
copyToClipboard(prompt.id);
103-
}}
104-
>
105-
<Copy className="size-3" />
106-
</Button>
99+
/>
107100
</div>
108101
</TableCell>
109102

src/components/prompts/PromptPreviewResult.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ export function PromptPreviewResult({ preview }: PromptPreviewResultProps) {
6060
<CodeBlock
6161
code={JSON.stringify({ messages: result.rendered.messages ?? [] }, null, 2)}
6262
language="json"
63-
copyLabel="JSON"
64-
copiedLabel={intl.formatMessage({ id: "prompts.details.code.copySuccess" })}
63+
copyLabel={intl.formatMessage({ id: "common.copyValue" }, { label: "JSON" })}
6564
/>
6665
)}
6766

src/components/prompts/PromptSnippetTabs.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ export function PromptSnippetTabs({
7272
[promptName, args],
7373
);
7474

75-
const copiedLabel = intl.formatMessage({ id: "prompts.details.code.copySuccess" });
76-
7775
return (
7876
<Tabs value={value} onValueChange={onValueChange}>
7977
<div className="mb-2 flex items-center justify-between gap-4">
@@ -96,7 +94,6 @@ export function PromptSnippetTabs({
9694
{ id: "prompts.details.code.copyAriaLabel" },
9795
{ language: snippet.language },
9896
)}
99-
copiedLabel={copiedLabel}
10097
/>
10198
</TabsContent>
10299
))}

src/components/resources/ResourcePreviewResult.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,11 @@ function ResourceContentPreview({
159159
}
160160

161161
if (kind === "code") {
162-
const copiedLabel = intl.formatMessage({ id: "resources.details.code.copySuccess" });
163162
return (
164163
<CodeBlock
165164
code={content.text ?? ""}
166165
language={codeLanguageFor(mimeType)}
167166
copyLabel={mimeType || "content"}
168-
copiedLabel={copiedLabel}
169167
/>
170168
);
171169
}

src/components/resources/ResourceTryItTab.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ function ResourcePreviewPane({ resource }: { resource: NonNullable<ResourceRead>
122122
[preview],
123123
);
124124

125-
const copiedLabel = intl.formatMessage({ id: "resources.details.code.copySuccess" });
126125
const rendered = useMemo(
127126
() => RESOURCE_SNIPPETS.map((spec) => ({ ...spec, text: spec.build({ uri: resolvedUri }) })),
128127
[resolvedUri],
@@ -154,7 +153,6 @@ function ResourcePreviewPane({ resource }: { resource: NonNullable<ResourceRead>
154153
{ id: "resources.details.code.copyAriaLabel" },
155154
{ language: snippet.language },
156155
)}
157-
copiedLabel={copiedLabel}
158156
/>
159157
</TabsContent>
160158
))}

src/components/resources/ResourcesTable.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("ResourcesTable", () => {
3636
beforeEach(() => {
3737
vi.clearAllMocks();
3838
mockOnSelectResource.mockClear();
39-
vi.spyOn(clipboardUtils, "copyToClipboard").mockImplementation(() => {});
39+
vi.spyOn(clipboardUtils, "copyToClipboard").mockResolvedValue(true);
4040
});
4141

4242
it("renders table with correct headers", () => {

src/components/resources/ResourcesTable.tsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useIntl } from "react-intl";
2-
import { Copy, MoreHorizontal } from "lucide-react";
2+
import { MoreHorizontal } from "lucide-react";
33
import { Button } from "@/components/ui/button";
4+
import { CopyButton } from "@/components/ui/copy-button";
45
import {
56
DropdownMenu,
67
DropdownMenuContent,
@@ -16,7 +17,6 @@ import {
1617
TableRow,
1718
} from "@/components/ui/table";
1819
import type { ResourceRead } from "@/generated/types";
19-
import { copyToClipboard } from "@/lib/clipboard";
2020
import { truncateMiddle } from "@/components/gateways/utils";
2121

2222
export function ResourcesTable({
@@ -78,22 +78,15 @@ export function ResourcesTable({
7878
<span className="min-w-0 truncate font-mono text-xs text-muted-foreground">
7979
{truncateMiddle(resource.uriTemplate || resource.uri, 28)}
8080
</span>
81-
<Button
82-
type="button"
83-
variant="ghost"
84-
size="icon-xs"
85-
aria-label={intl.formatMessage(
81+
<CopyButton
82+
value={resource.uriTemplate || resource.uri}
83+
label={intl.formatMessage(
8684
{ id: "resources.table.copyUri" },
8785
{ uri: resource.uriTemplate || resource.uri },
8886
)}
87+
iconClassName="size-3"
8988
className="ml-4 size-4 shrink-0 text-muted-foreground hover:text-foreground"
90-
onClick={(e) => {
91-
e.stopPropagation();
92-
copyToClipboard(resource.uriTemplate || resource.uri);
93-
}}
94-
>
95-
<Copy className="size-3" />
96-
</Button>
89+
/>
9790
</div>
9891
</TableCell>
9992

@@ -102,19 +95,12 @@ export function ResourcesTable({
10295
<span className="min-w-0 truncate font-mono text-xs text-muted-foreground">
10396
{truncateMiddle(resource.id, 18)}
10497
</span>
105-
<Button
106-
type="button"
107-
variant="ghost"
108-
size="icon-xs"
109-
aria-label={intl.formatMessage({ id: "resources.table.copyResourceId" })}
98+
<CopyButton
99+
value={resource.id}
100+
label={intl.formatMessage({ id: "resources.table.copyResourceId" })}
101+
iconClassName="size-3"
110102
className="ml-4 size-4 shrink-0 text-muted-foreground hover:text-foreground"
111-
onClick={(e) => {
112-
e.stopPropagation();
113-
copyToClipboard(resource.id);
114-
}}
115-
>
116-
<Copy className="size-3" />
117-
</Button>
103+
/>
118104
</div>
119105
</TableCell>
120106

src/components/servers/MCPServerDetailsPanel.tsx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useIntl } from "react-intl";
44
import {
55
Activity,
66
Box,
7-
Copy,
87
Globe,
98
Loader2,
109
MessageSquareCode,
@@ -20,12 +19,12 @@ import {
2019
} from "@/components/common/VisibilityInfoPopover";
2120
import { Badge } from "@/components/ui/badge";
2221
import { Button } from "@/components/ui/button";
22+
import { CopyButton } from "@/components/ui/copy-button";
2323
import { InlineTagAdd } from "@/components/ui/inline-tag-add";
2424
import { CopyValue } from "@/components/ui/copy-value";
2525
import { Input } from "@/components/ui/input";
2626
import { cn } from "@/lib/utils";
2727
import type { MCPServer as BaseMCPServer, VirtualServerTag } from "@/types/server";
28-
import { copyToClipboard } from "@/lib/clipboard";
2928
import { useQuery } from "@/hooks/useQuery";
3029
import { TestConnectionPanel } from "./TestConnectionPanel";
3130
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -489,32 +488,28 @@ export function MCPServerDetailsPanel({
489488
</span>
490489
<span className="flex min-w-0 items-center gap-2 font-mono text-[13px] text-muted-foreground">
491490
<span className="truncate">{identifier}</span>
492-
<Button
493-
type="button"
494-
variant="ghost"
495-
size="icon-xs"
496-
aria-label={`Copy ${title}`}
491+
<CopyButton
492+
value={identifier}
493+
label={intl.formatMessage(
494+
{ id: "common.copyValue" },
495+
{ label: title },
496+
)}
497497
className="size-5 text-muted-foreground"
498-
onClick={() => copyToClipboard(identifier)}
499-
>
500-
<Copy className="size-3.5" />
501-
</Button>
498+
/>
502499
</span>
503500
</>
504501
) : (
505502
<>
506503
<span className="flex min-w-0 items-center gap-2 font-mono text-[13px] text-muted-foreground">
507504
<span className="truncate">{identifier}</span>
508-
<Button
509-
type="button"
510-
variant="ghost"
511-
size="icon-xs"
512-
aria-label={`Copy ${identifier}`}
505+
<CopyButton
506+
value={identifier}
507+
label={intl.formatMessage(
508+
{ id: "common.copyValue" },
509+
{ label: identifier },
510+
)}
513511
className="size-5 text-muted-foreground"
514-
onClick={() => copyToClipboard(identifier)}
515-
>
516-
<Copy className="size-3.5" />
517-
</Button>
512+
/>
518513
</span>
519514
<span aria-hidden="true" />
520515
</>

0 commit comments

Comments
 (0)