CopyPostId

4 variants. Open one in isolation: use the link on each card.

CopiedFeedbackLeft

Open alone →
Copied!

CopiedFeedbackLeftWithGitHub

Open alone →
Copied! e
Copied!

WithGitHubEdit

Open alone →
e Copied!

src/ui/1_molecules/CopyPostId.astro
---
import Link from '../0_atoms/Link.astro'
/**
* Footer affordance for post detail pages: shows the muted post ID, copies it
* to the clipboard on click, and (optionally) renders a single-letter `e` link
* to the GitHub editor for the source file.
*
* The inline `style="opacity: 0; transition: …"` on `.copy-post-id-feedback` is
* a documented exception (functional, JS-driven; not a theme token). See
* `AGENTS.md → CSS / Styling Architecture`.
*/
interface Props {
postId: string
/** When set, an `e` link is rendered next to the post ID. */
githubEditHref?: string
/**
* Where the transient “Copied!” label sits in the row:
* `left` → `Copied!` · post ID · optional GitHub `e`;
* `right` (default) → post ID · optional GitHub `e` · `Copied!`.
*/
copiedFeedbackPosition?: 'left' | 'right'
class?: string
}
const {
postId,
githubEditHref,
copiedFeedbackPosition = 'right',
class: className,
} = Astro.props
---
<span class:list={['copy-post-id-root flex min-w-0 items-center gap-2', className]}>
{copiedFeedbackPosition === 'left' ? (
<span
class="meta copy-post-id-feedback shrink-0 text-xs"
style="opacity: 0; transition: opacity 0.2s ease;"
>Copied!</span>
) : null}
<span class="inline-flex min-w-0 items-center gap-0">
<button
type="button"
class="meta copy-post-id-btn min-w-0 cursor-pointer border-0 bg-transparent p-0 leading-none text-ink-faint transition-colors hover:text-ink-muted"
data-post-id={postId}
aria-label="Copy post ID to clipboard"
>
<span class="min-w-0 truncate">{postId}</span>
</button>
{githubEditHref ? (
<Link
href={githubEditHref}
external
variant="faint"
isMeta={true}
class="shrink-0 leading-none"
aria-label="Edit this post on GitHub"
title="Edit on GitHub"
>e</Link>
) : null}
</span>
{copiedFeedbackPosition === 'right' ? (
<span
class="meta copy-post-id-feedback shrink-0 text-xs"
style="opacity: 0; transition: opacity 0.2s ease;"
>Copied!</span>
) : null}
</span>
<script>
document.querySelectorAll<HTMLButtonElement>('.copy-post-id-btn').forEach(btn => {
const feedback = btn.closest('.copy-post-id-root')?.querySelector<HTMLElement>('.copy-post-id-feedback')
let copyTimer: ReturnType<typeof setTimeout> | null = null
btn.addEventListener('click', async () => {
const postId = btn.dataset.postId
if (!postId) return
try {
await navigator.clipboard.writeText(postId)
if (feedback) {
if (copyTimer) clearTimeout(copyTimer)
feedback.style.opacity = '1'
copyTimer = setTimeout(() => {
if (feedback) feedback.style.opacity = '0'
}, 1500)
}
} catch {
// clipboard unavailable
}
})
})
</script>