agent-ecosystem/src/renderer/components/common/CopyablePath.tsx

68 lines
1.9 KiB
TypeScript

/**
* CopyablePath - Path display with copy-to-clipboard on hover.
* Click anywhere on the path row to copy the full absolute path.
* A small icon appears on hover as visual affordance.
*/
import React, { useCallback, useState } from 'react';
import { Check, Copy } from 'lucide-react';
interface CopyablePathProps {
/** Shortened path for display */
displayText: string;
/** Full absolute path for clipboard */
copyText: string;
/** CSS classes for the text span */
className?: string;
/** Inline style for the text span */
style?: React.CSSProperties;
}
export const CopyablePath = ({
displayText,
copyText,
className = '',
style,
}: Readonly<CopyablePathProps>): React.ReactElement => {
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(
async (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
try {
await navigator.clipboard.writeText(copyText);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
// Clipboard API may not be available in all contexts
}
},
[copyText]
);
return (
<div
role="button"
tabIndex={-1}
className="group/copypath flex min-w-0 cursor-pointer items-center gap-1"
title={copyText}
onClick={handleCopy}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') void handleCopy(e as unknown as React.MouseEvent);
}}
>
<span className={`min-w-0 truncate ${className}`} style={style}>
{displayText}
</span>
<span
className="flex shrink-0 items-center opacity-0 transition-opacity group-hover/copypath:opacity-60"
style={{ color: style?.color ?? 'var(--color-text-muted)' }}
aria-hidden="true"
>
{copied ? <Check size={11} /> : <Copy size={11} />}
</span>
</div>
);
};