Skip to content

Commit 76f0c28

Browse files
authored
Adds copy to other titles (#1477)
## Description Added copy functionality to pipeline names in the UI. The `CopyText` component now has an `alwaysShowButton` prop that keeps the copy button visible at all times rather than only on hover. This enhancement has been implemented in both the Pipeline Details and Run Details views, allowing users to easily copy pipeline names. ## Type of Change - [x] Improvement - [x] New feature ## Checklist - [x] I have tested this does not break current pipelines / runs functionality - [x] I have tested the changes on staging ## Screenshots (if applicable) Before:![Screenshot 2025-12-05 at 1.47.50 PM.png](https://app.graphite.com/user-attachments/assets/661bc833-86fe-4cc8-ab13-1d71e0316b46.png) ## After ![Screenshot 2025-12-05 at 1.47.25 PM.png](https://app.graphite.com/user-attachments/assets/0a1cb2ac-96ff-4d4d-a686-937a16dbbd86.png) Bef ## Test Instructions 1. Navigate to a pipeline in the editor view 2. Verify the copy button is always visible next to the pipeline name 3. Click the copy button and confirm the pipeline name is copied to clipboard 4. Navigate to a pipeline run view 5. Verify the same copy functionality works for the pipeline name in this view ## Additional Comments This change improves user experience by making it more obvious that pipeline names can be copied, rather than requiring users to discover this functionality by hovering.
1 parent 75d6880 commit 76f0c28

File tree

3 files changed

+52
-33
lines changed

3 files changed

+52
-33
lines changed

src/components/Editor/PipelineDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
33

44
import { PipelineValidationList } from "@/components/Editor/components/PipelineValidationList/PipelineValidationList";
55
import { useValidationIssueNavigation } from "@/components/Editor/hooks/useValidationIssueNavigation";
6+
import { CopyText } from "@/components/shared/CopyText/CopyText";
67
import { Button } from "@/components/ui/button";
78
import { Icon } from "@/components/ui/icon";
89
import { InlineStack } from "@/components/ui/layout";
@@ -134,9 +135,9 @@ const PipelineDetails = () => {
134135
{/* Header */}
135136
<div className="flex items-center gap-2 max-w-[90%]">
136137
<Network className="w-6 h-6 text-secondary-foreground rotate-270 min-w-6 min-h-6" />
137-
<h2 className="text-lg font-semibold" data-testid="pipeline-name">
138+
<CopyText className="text-lg font-semibold" alwaysShowButton>
138139
{componentSpec.name ?? "Unnamed Pipeline"}
139-
</h2>
140+
</CopyText>
140141
<RenamePipeline />
141142
</div>
142143

src/components/PipelineRun/RunDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Frown, Videotape } from "lucide-react";
22

3+
import { CopyText } from "@/components/shared/CopyText/CopyText";
34
import { Spinner } from "@/components/ui/spinner";
45
import { useCheckComponentSpecFromPath } from "@/hooks/useCheckComponentSpecFromPath";
56
import { useUserDetails } from "@/hooks/useUserDetails";
@@ -84,9 +85,9 @@ export const RunDetails = () => {
8485
<div className="p-2 flex flex-col gap-6 h-full">
8586
<div className="flex items-center gap-2 max-w-[90%]">
8687
<Videotape className="w-6 h-6 text-gray-500" />
87-
<h2 className="text-lg font-semibold">
88+
<CopyText className="text-lg font-semibold" alwaysShowButton>
8889
{componentSpec.name ?? "Unnamed Pipeline"}
89-
</h2>
90+
</CopyText>
9091
<StatusIcon status={runStatus} tooltip />
9192
</div>
9293

src/components/shared/CopyText/CopyText.tsx

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ import { copyToClipboard } from "@/utils/string";
1010
interface CopyTextProps {
1111
children: string;
1212
className?: string;
13+
alwaysShowButton?: boolean;
1314
}
1415

15-
export const CopyText = ({ children, className }: CopyTextProps) => {
16+
export const CopyText = ({
17+
children,
18+
className,
19+
alwaysShowButton = false,
20+
}: CopyTextProps) => {
1621
const [isCopied, setIsCopied] = useState(false);
1722
const [isHovered, setIsHovered] = useState(false);
1823

@@ -34,7 +39,6 @@ export const CopyText = ({ children, className }: CopyTextProps) => {
3439
}, []);
3540

3641
return (
37-
3842
<div
3943
className="group cursor-pointer"
4044
onClick={handleCopy}
@@ -54,40 +58,53 @@ export const CopyText = ({ children, className }: CopyTextProps) => {
5458
</Text>
5559

5660
<Button
57-
variant={null}
61+
variant="ghost"
5862
size="icon"
5963
className={cn(
60-
"h-4 w-4 shrink-0 transition-opacity duration-200",
61-
isCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100",
64+
"h-6 w-6 shrink-0 transition-opacity duration-200",
65+
alwaysShowButton || isCopied
66+
? "opacity-100"
67+
: "opacity-0 group-hover:opacity-100",
6268
)}
6369
onClick={handleButtonClick}
6470
>
65-
<span className="relative h-3 w-3">
66-
{isCopied ? (
67-
<span
68-
key="check"
69-
className="absolute inset-0 animate-revert-copied"
70-
onAnimationEnd={handleAnimationEnd}
71-
>
72-
<Icon name="Check" size="sm" className="text-emerald-400" />
73-
</span>
74-
) : (
75-
<Icon
76-
key="copy"
77-
name="Copy"
78-
size="sm"
79-
className={cn(
80-
"absolute inset-0 text-muted-foreground transition-all duration-200",
81-
isHovered
82-
? "rotate-0 scale-100 opacity-100"
83-
: "rotate-90 scale-0 opacity-0",
84-
)}
85-
/>
86-
)}
87-
</span>
71+
<CopyIcon
72+
isCopied={isCopied}
73+
alwaysShow={alwaysShowButton || isHovered}
74+
onAnimationEnd={handleAnimationEnd}
75+
/>
8876
</Button>
8977
</InlineStack>
9078
</div>
91-
9279
);
9380
};
81+
82+
interface CopyIconProps {
83+
isCopied: boolean;
84+
alwaysShow: boolean;
85+
onAnimationEnd: () => void;
86+
}
87+
//
88+
const CopyIcon = ({ isCopied, alwaysShow, onAnimationEnd }: CopyIconProps) => (
89+
<span className="relative h-3.5 w-3.5">
90+
{isCopied ? (
91+
<span
92+
className="absolute inset-0 animate-revert-copied"
93+
onAnimationEnd={onAnimationEnd}
94+
>
95+
<Icon name="Check" size="sm" className="text-emerald-400" />
96+
</span>
97+
) : (
98+
<Icon
99+
name="Copy"
100+
size="sm"
101+
className={cn(
102+
"absolute inset-0 text-muted-foreground transition-all duration-200",
103+
alwaysShow
104+
? "rotate-0 scale-100 opacity-100"
105+
: "rotate-90 scale-0 opacity-0",
106+
)}
107+
/>
108+
)}
109+
</span>
110+
);

0 commit comments

Comments
 (0)