diff --git a/packages/web/src/components/CopyableId.tsx b/packages/web/src/components/CopyableId.tsx new file mode 100644 index 00000000..a19b95de --- /dev/null +++ b/packages/web/src/components/CopyableId.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { Copy, Check } from 'lucide-react'; +import { shortId, copyText } from '../lib/clipboard'; + +interface CopyableIdProps { + id: string; + /** Characters of the id to show before truncating (default 8). */ + length?: number; + label?: string; + testId?: string; +} + +/** + * Click-to-copy id chip (#23): shows a short id + copy icon; clicking copies the + * FULL id and flashes a transient "Copied!" confirmation. Reusable anywhere an + * id is shown (inspector, modals, lists). + */ +export function CopyableId({ id, length = 8, label = 'ID', testId = 'copyable-id' }: CopyableIdProps) { + const [copied, setCopied] = useState(false); + if (!id) return null; + const onCopy = async () => { + const ok = await copyText(id); + if (ok) { + setCopied(true); + setTimeout(() => setCopied(false), 1400); + } + }; + return ( + + ); +} diff --git a/packages/web/src/components/NodeInspector.tsx b/packages/web/src/components/NodeInspector.tsx index 3b3e7fcd..62dab7e2 100644 --- a/packages/web/src/components/NodeInspector.tsx +++ b/packages/web/src/components/NodeInspector.tsx @@ -4,6 +4,7 @@ import { useGraph } from '../contexts/GraphContext'; import { getTypeConfig, getStatusConfig } from '../constants/workItemConstants'; import type { WorkItemType } from '../constants/workItemConstants'; import { NodeSubgraphPreview } from './NodeSubgraphPreview'; +import { CopyableId } from './CopyableId'; // Heavy (markdown + Prism) — lazy so it's out of the main bundle until a node's // contents are first opened. @@ -50,6 +51,7 @@ export function NodeInspector({ node, onClose, compact = false, rootTestId = 'no
{typeCfg.label}
{node.title}
+