Skip to content

Commit be6f694

Browse files
committed
Adds a copy button and truncation to title
1 parent c424d9a commit be6f694

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

src/components/layout/AppMenu.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Link } from "@tanstack/react-router";
33
import logo from "/public/Tangle_white.png";
44
import { isAuthorizationRequired } from "@/components/shared/Authentication/helpers";
55
import { TopBarAuthentication } from "@/components/shared/Authentication/TopBarAuthentication";
6+
import { CopyText } from "@/components/shared/CopyText/CopyText";
67
import ImportPipeline from "@/components/shared/ImportPipeline";
78
import { InlineStack } from "@/components/ui/layout";
89
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
@@ -35,7 +36,12 @@ const AppMenu = () => {
3536
className="h-8 filter cursor-pointer shrink-0"
3637
/>
3738
</Link>
38-
<span className="text-white text-md font-bold ml-22">{title}</span>
39+
40+
{title && (
41+
<CopyText className="text-white text-md font-bold truncate max-w-lg ml-22">
42+
{title}
43+
</CopyText>
44+
)}
3945
</InlineStack>
4046

4147
<InlineStack blockAlign="center">
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useCallback, useEffect, useRef, useState } from "react";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { Icon } from "@/components/ui/icon";
5+
import { InlineStack } from "@/components/ui/layout";
6+
import { Text } from "@/components/ui/typography";
7+
import { cn } from "@/lib/utils";
8+
import { copyToClipboard } from "@/utils/string";
9+
10+
const ICON_DURATION_MS = 500;
11+
12+
interface CopyTextProps {
13+
children: string;
14+
className?: string;
15+
showButton?: boolean;
16+
}
17+
18+
export const CopyText = ({
19+
children,
20+
className,
21+
showButton = true,
22+
}: CopyTextProps) => {
23+
const [isCopied, setIsCopied] = useState(false);
24+
const [isHovered, setIsHovered] = useState(false);
25+
const timerRef = useRef<NodeJS.Timeout | null>(null);
26+
27+
useEffect(() => {
28+
return () => {
29+
if (timerRef.current) clearTimeout(timerRef.current);
30+
};
31+
}, []);
32+
33+
const handleCopy = useCallback(() => {
34+
copyToClipboard(children);
35+
36+
if (timerRef.current) clearTimeout(timerRef.current);
37+
setIsCopied(true);
38+
timerRef.current = setTimeout(() => setIsCopied(false), ICON_DURATION_MS);
39+
}, [children]);
40+
41+
const handleButtonClick = useCallback(
42+
(e: React.MouseEvent) => {
43+
e.stopPropagation();
44+
handleCopy();
45+
},
46+
[handleCopy],
47+
);
48+
49+
if (!showButton && !isCopied)
50+
return <Text className={className}>{children}</Text>;
51+
52+
return (
53+
<div
54+
className="group cursor-pointer"
55+
onClick={handleCopy}
56+
onMouseEnter={() => setIsHovered(true)}
57+
onMouseLeave={() => setIsHovered(false)}
58+
title={children}
59+
>
60+
<InlineStack gap="1" blockAlign="center" wrap="nowrap">
61+
<Text
62+
className={cn(
63+
"transition-all duration-150",
64+
className,
65+
isCopied && "scale-[1.01] text-emerald-400!",
66+
)}
67+
>
68+
{children}
69+
</Text>
70+
71+
<Button
72+
variant="ghost"
73+
size="icon"
74+
className={cn(
75+
"h-4 w-4 shrink-0 transition-opacity duration-200",
76+
isCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100",
77+
)}
78+
onClick={handleButtonClick}
79+
>
80+
<span className="relative h-3 w-3">
81+
<Icon
82+
name="Check"
83+
size="sm"
84+
className={cn(
85+
"absolute inset-0 text-emerald-400 transition-all duration-200",
86+
isCopied
87+
? "rotate-0 scale-100 opacity-100"
88+
: "-rotate-90 scale-0 opacity-0",
89+
)}
90+
/>
91+
<Icon
92+
name="Copy"
93+
size="sm"
94+
className={cn(
95+
"absolute inset-0 text-muted-foreground transition-all duration-200",
96+
isHovered && !isCopied
97+
? "rotate-0 scale-100 opacity-100"
98+
: "rotate-90 scale-0 opacity-0",
99+
)}
100+
/>
101+
</span>
102+
</Button>
103+
</InlineStack>
104+
</div>
105+
);
106+
};

0 commit comments

Comments
 (0)