{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "citation",
  "title": "Citation",
  "description": "An inline source marker for AI answers, shown as a numbered chip or a site pill, that opens a preview card with the title, excerpt and link on hover or focus.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "hover-card"
  ],
  "files": [
    {
      "path": "registry/tealmac/citation.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight, ExternalLink } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from \"@/components/ui/hover-card\"\n\ntype CitationSource = {\n  /** Page title. */\n  title: string\n  /** Link to the page. */\n  url: string\n  /** Short excerpt shown in the preview card. */\n  snippet?: string\n  /** Shown instead of the host name, for example a publisher. */\n  site?: string\n  /** Icon image URL. When omitted a colored monogram is drawn, so nothing is fetched from a third party. */\n  favicon?: string\n  /** Publication date as text, for example \"Mar 4, 2026\". */\n  date?: string\n}\n\nconst TONES = [\n  \"bg-chart-1\",\n  \"bg-chart-3\",\n  \"bg-chart-4\",\n  \"bg-chart-2\",\n  \"bg-chart-5\",\n]\n\nfunction hostOf(url: string) {\n  try {\n    return new URL(url).hostname.replace(/^www\\./, \"\")\n  } catch {\n    return url\n  }\n}\n\nfunction hashOf(text: string) {\n  let h = 0\n  for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) >>> 0\n  return h\n}\n\ntype SourceFaviconProps = React.ComponentProps<\"span\"> & {\n  source: Pick<CitationSource, \"url\" | \"site\" | \"favicon\">\n}\n\n/** A small rounded icon for a source: its favicon when given, otherwise the first letter of its site on a stable color. */\nfunction SourceFavicon({ source, className, ...props }: SourceFaviconProps) {\n  const name = source.site ?? hostOf(source.url)\n  return (\n    <span\n      data-slot=\"source-favicon\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"text-background flex size-5 shrink-0 items-center justify-center overflow-hidden rounded-[6px] border border-black/5 text-[11px] font-semibold uppercase dark:border-white/10\",\n        !source.favicon && TONES[hashOf(name) % TONES.length],\n        className\n      )}\n      {...props}\n    >\n      {source.favicon ? (\n        <img src={source.favicon} alt=\"\" className=\"size-full object-cover\" />\n      ) : (\n        name.charAt(0)\n      )}\n    </span>\n  )\n}\n\ntype CitationProps = Omit<React.ComponentProps<\"a\">, \"href\" | \"children\"> & {\n  /** The source, or several sources backing one claim. With several, the card gets a pager. */\n  sources: CitationSource | CitationSource[]\n  /** Number shown in the marker. Ignored by the \"pill\" style. */\n  index?: number\n  /** \"number\" is a small superscript-style chip like [1]. \"pill\" shows the site name with a +N count. */\n  variant?: \"number\" | \"pill\"\n}\n\nfunction Citation({\n  sources,\n  index = 1,\n  variant = \"number\",\n  className,\n  ...props\n}: CitationProps) {\n  const list = Array.isArray(sources) ? sources : [sources]\n  const [page, setPage] = React.useState(0)\n  const active = list[Math.min(page, list.length - 1)]\n  if (!active) return null\n  const first = list[0]!\n  const host = first.site ?? hostOf(first.url)\n  const extra = list.length - 1\n  const many = list.length > 1\n\n  return (\n    <HoverCard openDelay={120} closeDelay={150}>\n      <HoverCardTrigger\n        data-slot=\"citation\"\n        href={first.url}\n        target=\"_blank\"\n        rel=\"noreferrer noopener\"\n        aria-label={\n          variant === \"pill\"\n            ? `Source: ${host}${extra ? ` and ${extra} more` : \"\"}`\n            : `Source ${index}: ${first.title}${extra ? ` and ${extra} more` : \"\"}`\n        }\n        className={cn(\n          \"text-foreground focus-visible:ring-ring/50 mx-0.5 inline-flex items-center justify-center align-baseline font-medium whitespace-nowrap no-underline transition-colors duration-150 outline-none focus-visible:ring-[3px]\",\n          variant === \"number\"\n            ? \"bg-muted hover:bg-foreground hover:text-background h-[1.25em] min-w-[1.35em] -translate-y-[0.15em] rounded-md px-1 text-[0.72em] tabular-nums\"\n            : \"bg-background hover:bg-accent h-6 gap-1 rounded-full border px-2 text-xs shadow-xs\",\n          className\n        )}\n        {...props}\n      >\n        {variant === \"number\" ? (\n          index\n        ) : (\n          <>\n            <span className=\"max-w-32 truncate\">{host}</span>\n            {extra > 0 && (\n              <span className=\"text-muted-foreground tabular-nums\">\n                +{extra}\n              </span>\n            )}\n          </>\n        )}\n      </HoverCardTrigger>\n      <HoverCardContent\n        data-slot=\"citation-card\"\n        className=\"grid w-[min(21rem,calc(100vw-1.5rem))] gap-2.5 p-3.5\"\n      >\n        {many && (\n          <div className=\"text-muted-foreground flex items-center justify-between text-xs\">\n            <span className=\"tabular-nums\" aria-live=\"polite\">\n              {`Source ${page + 1} of ${list.length}`}\n            </span>\n            <span className=\"flex items-center gap-0.5\">\n              <button\n                type=\"button\"\n                aria-label={\"Previous source\"}\n                disabled={page === 0}\n                onClick={() => setPage((p) => Math.max(0, p - 1))}\n                className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-6 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] disabled:opacity-40 disabled:hover:bg-transparent\"\n              >\n                <ChevronLeft\n                  aria-hidden=\"true\"\n                  className=\"size-4 rtl:rotate-180\"\n                />\n              </button>\n              <button\n                type=\"button\"\n                aria-label={\"Next source\"}\n                disabled={page === list.length - 1}\n                onClick={() => setPage((p) => Math.min(list.length - 1, p + 1))}\n                className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-6 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] disabled:opacity-40 disabled:hover:bg-transparent\"\n              >\n                <ChevronRight\n                  aria-hidden=\"true\"\n                  className=\"size-4 rtl:rotate-180\"\n                />\n              </button>\n            </span>\n          </div>\n        )}\n        <div className=\"text-muted-foreground flex items-center gap-2 text-xs\">\n          <SourceFavicon source={active} />\n          <span className=\"truncate\">{active.site ?? hostOf(active.url)}</span>\n          {active.date && (\n            <span className=\"shrink-0 before:me-2 before:content-['·']\">\n              {active.date}\n            </span>\n          )}\n        </div>\n        <p className=\"text-foreground line-clamp-2 text-sm leading-5 font-medium\">\n          {active.title}\n        </p>\n        {active.snippet && (\n          <p className=\"text-muted-foreground line-clamp-3 text-[13px] leading-5\">\n            {active.snippet}\n          </p>\n        )}\n        <a\n          href={active.url}\n          target=\"_blank\"\n          rel=\"noreferrer noopener\"\n          className=\"text-foreground focus-visible:ring-ring/50 inline-flex w-fit items-center gap-1 rounded-sm text-xs font-medium underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]\"\n        >\n          {\"Open source\"}\n          <ExternalLink aria-hidden=\"true\" className=\"size-3\" />\n        </a>\n      </HoverCardContent>\n    </HoverCard>\n  )\n}\n\nexport {\n  Citation,\n  SourceFavicon,\n  hostOf,\n  type CitationProps,\n  type CitationSource,\n  type SourceFaviconProps,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}