Skip to content

Commit b890a7c

Browse files
committed
Adds copy to other titles
1 parent df184c5 commit b890a7c

File tree

4 files changed

+135
-42
lines changed

4 files changed

+135
-42
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: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ interface CopyTextProps {
1111
children: string;
1212
className?: string;
1313
showButton?: boolean;
14+
alwaysShowButton?: boolean;
1415
}
1516

1617
export const CopyText = ({
1718
children,
1819
className,
1920
showButton = true,
21+
alwaysShowButton = false,
2022
}: CopyTextProps) => {
2123
const [isCopied, setIsCopied] = useState(false);
2224
const [isHovered, setIsHovered] = useState(false);
@@ -35,7 +37,6 @@ export const CopyText = ({
3537
);
3638

3739
const handleAnimationEnd = useCallback(() => {
38-
console.log("Animation ended, reverting to copy icon");
3940
setIsCopied(false);
4041
}, []);
4142

@@ -45,7 +46,7 @@ export const CopyText = ({
4546
return (
4647
<>
4748
<style>{`
48-
@keyframes revert-copied {
49+
@keyframes revert-check {
4950
0%, 80% {
5051
opacity: 1;
5152
transform: rotate(0deg) scale(1);
@@ -74,45 +75,61 @@ export const CopyText = ({
7475
{children}
7576
</Text>
7677

77-
<Button
78-
variant="ghost"
79-
size="icon"
80-
className={cn(
81-
"h-4 w-4 shrink-0 transition-opacity duration-200",
82-
isCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100",
83-
)}
84-
onClick={handleButtonClick}
85-
>
86-
<span className="relative h-3 w-3">
87-
{isCopied ? (
88-
<span
89-
key="check"
90-
className="absolute inset-0 animate-[revert-copied_1.5s_ease-in-out_forwards]"
91-
onAnimationEnd={handleAnimationEnd}
92-
>
93-
<Icon
94-
name="Check"
95-
size="sm"
96-
className="text-emerald-400"
97-
/>
98-
</span>
99-
) : (
100-
<Icon
101-
key="copy"
102-
name="Copy"
103-
size="sm"
104-
className={cn(
105-
"absolute inset-0 text-muted-foreground transition-all duration-200",
106-
isHovered
107-
? "rotate-0 scale-100 opacity-100"
108-
: "rotate-90 scale-0 opacity-0",
109-
)}
110-
/>
78+
{showButton && (
79+
<Button
80+
variant="ghost"
81+
size="icon"
82+
className={cn(
83+
"h-6 w-6 shrink-0 transition-opacity duration-200",
84+
alwaysShowButton || isCopied
85+
? "opacity-100"
86+
: "opacity-0 group-hover:opacity-100",
11187
)}
112-
</span>
113-
</Button>
88+
onClick={handleButtonClick}
89+
>
90+
<CopyIcon
91+
isCopied={isCopied}
92+
alwaysShow={alwaysShowButton || isHovered}
93+
onAnimationEnd={handleAnimationEnd}
94+
/>
95+
</Button>
96+
)}
11497
</InlineStack>
11598
</div>
11699
</>
117100
);
118101
};
102+
103+
interface CopyIconProps {
104+
isCopied: boolean;
105+
alwaysShow: boolean;
106+
onAnimationEnd: () => void;
107+
}
108+
109+
const CopyIcon = ({ isCopied, alwaysShow, onAnimationEnd }: CopyIconProps) => (
110+
<span className="relative h-3.5 w-3.5">
111+
{isCopied ? (
112+
<span
113+
className="absolute inset-0 animate-[revert-check_1.5s_ease-in-out_forwards]"
114+
onAnimationEnd={onAnimationEnd}
115+
>
116+
<Icon
117+
name="Check"
118+
size="sm"
119+
className="text-emerald-400"
120+
/>
121+
</span>
122+
) : (
123+
<Icon
124+
name="Copy"
125+
size="sm"
126+
className={cn(
127+
"absolute inset-0 text-muted-foreground transition-all duration-200",
128+
alwaysShow
129+
? "rotate-0 scale-100 opacity-100"
130+
: "rotate-90 scale-0 opacity-0",
131+
)}
132+
/>
133+
)}
134+
</span>
135+
);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { ReactNode, RefObject } from "react";
2+
3+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4+
import { BlockStack, InlineStack } from "@/components/ui/layout";
5+
import { Text } from "@/components/ui/typography";
6+
import { cn } from "@/lib/utils";
7+
import type { TaskNodeDimensions } from "@/types/taskNode";
8+
9+
interface TaskNodeCardContentProps {
10+
name: string;
11+
taskId?: string;
12+
dimensions: TaskNodeDimensions;
13+
selected?: boolean;
14+
highlighted?: boolean;
15+
isSubgraphNode?: boolean;
16+
nodeRef?: RefObject<HTMLDivElement | null>;
17+
onDoubleClick?: () => void;
18+
headerActions?: ReactNode;
19+
headerIcons?: ReactNode;
20+
children: ReactNode;
21+
}
22+
23+
export const TaskNodeCardContent = ({
24+
name,
25+
taskId,
26+
dimensions,
27+
selected = false,
28+
highlighted = false,
29+
isSubgraphNode = false,
30+
nodeRef,
31+
onDoubleClick,
32+
headerActions,
33+
headerIcons,
34+
children,
35+
}: TaskNodeCardContentProps) => {
36+
return (
37+
<Card
38+
className={cn(
39+
"rounded-2xl border-gray-200 border-2 wrap-break-word p-0 drop-shadow-none gap-2",
40+
selected ? "border-gray-500" : "hover:border-slate-200",
41+
highlighted && "border-orange-500!",
42+
isSubgraphNode && "cursor-pointer",
43+
)}
44+
style={{
45+
width: dimensions.w + "px",
46+
height: "auto",
47+
}}
48+
ref={nodeRef}
49+
onDoubleClick={onDoubleClick}
50+
>
51+
<CardHeader className="border-b border-slate-200 px-2 py-2.5 flex flex-row justify-between items-start">
52+
<BlockStack>
53+
<InlineStack gap="2" blockAlign="center" wrap="nowrap">
54+
{headerIcons}
55+
<CardTitle className="wrap-break-word text-left text-xs text-slate-900">
56+
{name}
57+
</CardTitle>
58+
</InlineStack>
59+
{taskId &&
60+
taskId !== name &&
61+
!taskId.match(new RegExp(`^${name}\\s*\\d+$`)) && (
62+
<Text size="xs" tone="subdued" className="font-light">
63+
{taskId}
64+
</Text>
65+
)}
66+
</BlockStack>
67+
{headerActions}
68+
</CardHeader>
69+
<CardContent className="p-2 flex flex-col gap-2">
70+
{children}
71+
</CardContent>
72+
</Card>
73+
);
74+
};

0 commit comments

Comments
 (0)