diff --git a/src/components/layout/AppMenu.tsx b/src/components/layout/AppMenu.tsx index 954ae0447..41849dd95 100644 --- a/src/components/layout/AppMenu.tsx +++ b/src/components/layout/AppMenu.tsx @@ -1,6 +1,7 @@ import logo from "/public/Tangle_white.png"; import { isAuthorizationRequired } from "@/components/shared/Authentication/helpers"; import { TopBarAuthentication } from "@/components/shared/Authentication/TopBarAuthentication"; +import { CopyText } from "@/components/shared/CopyText/CopyText"; import ImportPipeline from "@/components/shared/ImportPipeline"; import { InlineStack } from "@/components/ui/layout"; import { Link } from "@/components/ui/link"; @@ -34,7 +35,12 @@ const AppMenu = () => { className="h-8 filter cursor-pointer shrink-0" /> - {title} + + {title && ( + + {title} + + )} diff --git a/src/components/shared/CopyText/CopyText.tsx b/src/components/shared/CopyText/CopyText.tsx new file mode 100644 index 000000000..a36c3285a --- /dev/null +++ b/src/components/shared/CopyText/CopyText.tsx @@ -0,0 +1,93 @@ +import { type MouseEvent, useCallback, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Icon } from "@/components/ui/icon"; +import { InlineStack } from "@/components/ui/layout"; +import { Text } from "@/components/ui/typography"; +import { cn } from "@/lib/utils"; +import { copyToClipboard } from "@/utils/string"; + +interface CopyTextProps { + children: string; + className?: string; +} + +export const CopyText = ({ children, className }: CopyTextProps) => { + const [isCopied, setIsCopied] = useState(false); + const [isHovered, setIsHovered] = useState(false); + + const handleCopy = useCallback(() => { + copyToClipboard(children); + setIsCopied(true); + }, [children]); + + const handleButtonClick = useCallback( + (e: MouseEvent) => { + e.stopPropagation(); + handleCopy(); + }, + [handleCopy], + ); + + const handleAnimationEnd = useCallback(() => { + setIsCopied(false); + }, []); + + return ( + +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + title={children} + > + + + {children} + + + + +
+ + ); +}; diff --git a/src/styles/global.css b/src/styles/global.css index b9ff3d49c..4dd7dec3d 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -144,6 +144,21 @@ code { --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + + /* Custom animations */ + --animate-revert-copied: revert-copied 0.5s ease-in-out forwards; + + @keyframes revert-copied { + 0%, + 80% { + opacity: 1; + transform: rotate(0deg) scale(1); + } + 100% { + opacity: 0; + transform: rotate(-90deg) scale(0); + } + } } @layer base {