{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "website-link",
  "title": "Website Link",
  "description": "A compact inline link with an automatically derived favicon, protocol-aware fallback, and native anchor behavior.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/rivetui/components/website-link/website-link.tsx",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { ExternalLink, Globe2, Link2, Mail, Phone } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst websiteLinkVariants = cva(\n  \"group/website-link inline-flex max-w-full items-center border border-border bg-muted/20 align-middle font-medium leading-none text-foreground no-underline outline-none transition-[background-color,border-color,box-shadow] duration-100 hover:border-foreground/30 hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background active:bg-muted motion-reduce:transition-none\",\n  {\n    variants: {\n      size: {\n        sm: \"h-6 gap-1 px-1 text-xs\",\n        md: \"h-7 gap-1.5 px-1.5 text-sm\",\n        lg: \"h-9 gap-2 px-2.5 text-base\",\n      },\n    },\n    defaultVariants: {\n      size: \"md\",\n    },\n  },\n);\n\ntype WebsiteLinkProps = Omit<React.ComponentProps<\"a\">, \"children\" | \"href\" | \"title\"> &\n  VariantProps<typeof websiteLinkVariants> & {\n    href: string;\n    title?: string;\n  };\n\nfunction WebsiteLink({\n  href,\n  title,\n  size = \"md\",\n  className,\n  rel,\n  target,\n  ...props\n}: WebsiteLinkProps) {\n  const { faviconUrls, fallbackTitle, kind } = getLinkIdentity(href);\n  const FallbackIcon = getFallbackIcon(kind);\n\n  return (\n    <a\n      data-slot=\"website-link\"\n      data-kind={kind}\n      data-size={size}\n      href={href}\n      target={target}\n      rel={getLinkRel(rel, target)}\n      className={cn(websiteLinkVariants({ size, className }))}\n      {...props}\n    >\n      <span\n        data-slot=\"website-link-leading-icon\"\n        className=\"relative inline-flex size-4 shrink-0 items-center justify-center overflow-hidden text-muted-foreground group-data-[size=sm]/website-link:size-3.5 group-data-[size=lg]/website-link:size-5\"\n      >\n        <FallbackIcon\n          className=\"size-3 group-data-[size=sm]/website-link:size-2.5 group-data-[size=lg]/website-link:size-3.5\"\n          aria-hidden=\"true\"\n        />\n        <WebsiteLinkFavicon key={href} sources={faviconUrls} />\n      </span>\n      <span data-slot=\"website-link-title\" className=\"truncate\">\n        {title ?? fallbackTitle}\n      </span>\n      {kind === \"website\" ? (\n        <ExternalLink\n          data-slot=\"website-link-icon\"\n          className=\"size-3.5 shrink-0 text-muted-foreground transition-colors duration-100 group-hover/website-link:text-foreground group-data-[size=sm]/website-link:size-3 group-data-[size=lg]/website-link:size-4 motion-reduce:transition-none\"\n          aria-hidden=\"true\"\n        />\n      ) : null}\n      {target === \"_blank\" ? <span className=\"sr-only\"> (opens in a new tab)</span> : null}\n    </a>\n  );\n}\n\nfunction WebsiteLinkFavicon({ sources }: { sources: readonly string[] }) {\n  const [mounted, setMounted] = React.useState(false);\n  const [sourceIndex, setSourceIndex] = React.useState(0);\n  const [loaded, setLoaded] = React.useState(false);\n  const imageRef = React.useRef<HTMLImageElement>(null);\n  const source = sources[sourceIndex];\n\n  React.useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const advanceSource = React.useCallback(() => {\n    setLoaded(false);\n    setSourceIndex((currentIndex) =>\n      sources[currentIndex] === source ? currentIndex + 1 : currentIndex,\n    );\n  }, [source, sources]);\n\n  React.useEffect(() => {\n    const image = imageRef.current;\n\n    if (!mounted || !source || !image?.complete) {\n      return;\n    }\n\n    if (image.naturalWidth > 0) {\n      setLoaded(true);\n    } else {\n      advanceSource();\n    }\n  }, [advanceSource, mounted, source]);\n\n  if (!mounted || !source) {\n    return null;\n  }\n\n  return (\n    // biome-ignore lint/performance/noImgElement: Favicons are arbitrary external resources in this framework-agnostic registry component.\n    <img\n      ref={imageRef}\n      key={source}\n      src={source}\n      alt=\"\"\n      aria-hidden=\"true\"\n      decoding=\"async\"\n      referrerPolicy=\"no-referrer\"\n      data-loaded={loaded ? \"\" : undefined}\n      className=\"absolute inset-0 size-full bg-background object-cover opacity-0 data-loaded:opacity-100\"\n      onLoad={() => setLoaded(true)}\n      onError={advanceSource}\n    />\n  );\n}\n\nfunction getLinkRel(rel: string | undefined, target: React.HTMLAttributeAnchorTarget | undefined) {\n  if (target !== \"_blank\") {\n    return rel;\n  }\n\n  const values = new Set(rel?.split(/\\s+/).filter(Boolean));\n  values.add(\"noopener\");\n  values.add(\"noreferrer\");\n\n  return Array.from(values).join(\" \");\n}\n\ntype WebsiteLinkKind = \"email\" | \"link\" | \"phone\" | \"website\";\n\nfunction getFallbackIcon(kind: WebsiteLinkKind) {\n  if (kind === \"email\") {\n    return Mail;\n  }\n\n  if (kind === \"phone\") {\n    return Phone;\n  }\n\n  if (kind === \"website\") {\n    return Globe2;\n  }\n\n  return Link2;\n}\n\nfunction getLinkIdentity(href: string): {\n  fallbackTitle: string;\n  faviconUrls: readonly string[];\n  kind: WebsiteLinkKind;\n} {\n  try {\n    const url = new URL(href);\n\n    if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n      return {\n        faviconUrls: [\"/favicon.ico\", \"/favicon.svg\", \"/icon.svg\", \"/apple-touch-icon.png\"].map(\n          (path) => new URL(path, url.origin).toString(),\n        ),\n        fallbackTitle: url.hostname.replace(/^www\\./, \"\"),\n        kind: \"website\",\n      };\n    }\n\n    if (url.protocol === \"mailto:\") {\n      return {\n        faviconUrls: [],\n        fallbackTitle: decodeURIComponent(url.pathname),\n        kind: \"email\",\n      };\n    }\n\n    if (url.protocol === \"tel:\") {\n      return {\n        faviconUrls: [],\n        fallbackTitle: decodeURIComponent(url.pathname),\n        kind: \"phone\",\n      };\n    }\n\n    return {\n      faviconUrls: [],\n      fallbackTitle: href,\n      kind: \"link\",\n    };\n  } catch {\n    return { faviconUrls: [], fallbackTitle: href, kind: \"link\" };\n  }\n}\n\nexport type { WebsiteLinkProps };\nexport { WebsiteLink, websiteLinkVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}