import { useState, type CSSProperties, type ReactNode } from 'react'; import type { Citation } from './types.js'; export interface CitationChipProps { citation: Citation; /** Override the chip body — useful for icons. Defaults to `[n]`. */ children?: ReactNode; /** Suppress the popover preview (e.g. when used inline in dense lists). */ noPreview?: boolean; className?: string; style?: CSSProperties; } /** * Inline citation marker. Renders a small `[n]` chip; on hover or focus * a preview pops up with the title, snippet, and an outbound link. * * The preview is a CSS-only popover (no portal, no JS layout) so it * stays cheap to embed dozens of times in a single message body. */ export function CitationChip({ citation, children, noPreview = false, className, style, }: CitationChipProps) { const [hover, setHover] = useState(false); const [focus, setFocus] = useState(false); const open = !noPreview && (hover || focus); const Wrapper = citation.url ? 'a' : 'span'; const wrapperProps = citation.url ? { href: citation.url, target: '_blank', rel: 'noopener noreferrer', } : {}; return ( setHover(true)} onMouseLeave={() => setHover(false)} > setFocus(true)} onBlur={() => setFocus(false)} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minWidth: 18, height: 18, padding: '0 4px', marginInline: 2, fontSize: 11, fontWeight: 700, lineHeight: 1, color: 'var(--bl-accent, #6366f1)', background: 'var(--bl-accent-muted, rgba(99,102,241,0.12))', borderRadius: 'var(--bl-radius-pill, 999px)', textDecoration: 'none', cursor: citation.url ? 'pointer' : 'help', verticalAlign: 'super', }} > {children ?? citation.index} {open && ( {citation.source && (
{citation.source}
)}
{citation.title}
{citation.snippet && (
{citation.snippet}
)} {citation.url && (
{truncateUrl(citation.url)}
)}
)}
); } function truncateUrl(url: string, max = 56): string { return url.length <= max ? url : `${url.slice(0, max - 1)}…`; }