Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/content/panel/components/ComponentDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { captureElementToClipboard } from '@/lib/screenshot';
import type { RuntimeMessage } from '@/lib/types';
import { getPanelHost } from '../../shadow-host';
import { useEnvHost, useStore } from '../store';
import { CopyableUri } from './CopyableUri';
import { Icon } from './Icon';
import { Breadcrumb } from './Breadcrumb';
import { AnnotationEditor } from './AnnotationEditor';
Expand Down Expand Up @@ -58,7 +59,15 @@ export function ComponentDetails() {
<h4 className="cs-section-title">Component</h4>
<Breadcrumb />
<p className="cs-name">{selected.displayName}</p>
{selected.instance && <p className="cs-instance">{selected.instance}</p>}
{/* Copy yields the *full* URI even though the displayed text is the
shorter instance id β€” most consumers (Clay tools, fetch URLs, etc.)
want the URI, not just the suffix. The full URI surfaces in the
tooltip on hover so it's still discoverable. */}
<CopyableUri
uri={selected.uri}
displayText={selected.instance ?? selected.uri}
label="Component URI"
/>
<div className="cs-link-row">
<button
className="cs-link cs-link-primary"
Expand Down
53 changes: 53 additions & 0 deletions src/content/panel/components/CopyableUri.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { copyToClipboard } from '@/lib/clipboard';
import { useStore } from '../store';
import { Icon } from './Icon';

interface CopyableUriProps {
/** The string actually written to the clipboard. */
readonly uri: string;
/**
* Optional shorter text to display in the row. Defaults to `uri`. Useful
* for component rows where the visible label is the instance ID but the
* value worth copying is the full URI.
*/
readonly displayText?: string;
/** Used in the toast + accessible label, e.g. "Page URI" or "Component URI". */
readonly label: string;
}

/**
* A URI rendered in muted monospace with a copy-icon button on the right
* of the same row. Hover (and keyboard focus) brightens the button so the
* affordance is discoverable but not visually noisy at rest.
*/
export function CopyableUri({ uri, displayText, label }: CopyableUriProps) {
const pushToast = useStore((s) => s.pushToast);

const onCopy = async () => {
const ok = await copyToClipboard(uri);
pushToast(ok ? `${label} copied` : 'Copy failed', ok ? 'success' : 'error');
};

// When the displayed text differs from the value being copied (component
// rows show the instance id, copy yields the full URI), surface the full
// URI in the tooltip so it's still discoverable.
const tooltip =
displayText && displayText !== uri
? `Copy ${label} to clipboard\n${uri}`
: `Copy ${label} to clipboard`;

return (
<div className="cs-uri-row">
<p className="cs-instance cs-uri-row-text">{displayText ?? uri}</p>
<button
type="button"
className="cs-icon-btn cs-uri-row-copy"
onClick={onCopy}
title={tooltip}
aria-label={`Copy ${label.toLowerCase()}`}
>
<Icon name="copy" size={12} />
</button>
</div>
);
}
3 changes: 2 additions & 1 deletion src/content/panel/components/PageInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { buildEditorUrl, buildUrl, unpublishedUri } from '@/lib/clay-uri';
import { findMappingForHost, rewriteUrlToEnv } from '@/lib/site-host';
import { SITE_ENV_LABELS, SITE_ENV_ORDER, type RuntimeMessage } from '@/lib/types';
import { useEnvHost, useStore } from '../store';
import { CopyableUri } from './CopyableUri';
import { Icon } from './Icon';
import { ExportMenu } from './ExportMenu';

Expand Down Expand Up @@ -31,7 +32,7 @@ export function PageInfo() {
<section className="cs-section">
<h4 className="cs-section-title">Page</h4>
<p className="cs-name">{page.pageInstance ?? 'Unknown page'}</p>
<p className="cs-instance">{page.pageUri}</p>
<CopyableUri uri={page.pageUri} label="Page URI" />
<div className="cs-link-row">
<button
className="cs-link cs-link-primary"
Expand Down
30 changes: 30 additions & 0 deletions src/content/panel/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@
word-break: break-all;
}

/* URI row: a single horizontal line with the URI on the left and a small
copy-icon button anchored on the right. The button stays muted at rest
so it doesn't compete with the URI text, then brightens on row-hover or
keyboard focus. */
.cs-uri-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin: 0;
}

.cs-uri-row-text {
flex: 1;
min-width: 0;
margin: 0;
}

.cs-uri-row-copy {
flex-shrink: 0;
opacity: 0.55;
transition: opacity 0.12s ease;
margin-top: -2px;
}

.cs-uri-row:hover .cs-uri-row-copy,
.cs-uri-row-copy:hover,
.cs-uri-row-copy:focus-visible {
opacity: 1;
}

.cs-link-row {
display: flex;
flex-wrap: wrap;
Expand Down
Loading