{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "link-preview",
  "type": "registry:ui",
  "title": "LinkPreview",
  "description": "The `LinkPreview` component is an interactive UI element that displays a preview of a link when hovered over. It enhances user experience by providing additional context about the link.",
  "files": [
    {
      "path": "registry/sonaui/link-preview/link-preview.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useCallback, useState, useSyncExternalStore } from \"react\";\nimport { FaArrowUpRightFromSquare } from \"react-icons/fa6\";\nimport useMeasure from \"react-use-measure\";\nimport { motionTransition } from \"@/lib/sona-motion\";\nimport { cn } from \"@/lib/sona-utils\";\n\nfunction useMediaQuery(query: string) {\n  const getSnapshot = useCallback(\n    () => window.matchMedia(query).matches,\n    [query],\n  );\n  const subscribe = useCallback(\n    (callback: () => void) => {\n      const mediaQuery = window.matchMedia(query);\n      mediaQuery.addEventListener(\"change\", callback);\n      return () => mediaQuery.removeEventListener(\"change\", callback);\n    },\n    [query],\n  );\n\n  return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n\ninterface LinkPreviewProps extends React.HTMLAttributes<HTMLAnchorElement> {\n  /** The URL of the link to preview. */\n  link: string;\n  /** The text to display for the link. */\n  text: string;\n  /**\n   * Whether to show an icon next to the link text.\n   * @default true\n   */\n  showIcon?: boolean;\n}\n\nexport default function LinkPreview({\n  link,\n  text,\n  showIcon = true,\n  className,\n  onMouseEnter,\n  onMouseLeave,\n  onFocus,\n  onBlur,\n  ...linkProps\n}: LinkPreviewProps) {\n  // scroll: true keeps viewport coordinates fresh while the page scrolls\n  const [containerRef, containerBounds] = useMeasure({ scroll: true });\n  const desktop = useMediaQuery(\"(min-width: 768px)\");\n  const shouldReduceMotion = useReducedMotion();\n\n  const [isHover, setIsHover] = useState(false);\n\n  return (\n    <>\n      <a\n        href={link}\n        className={cn(\n          \"inline-flex relative items-center underline underline-offset-3 cursor-pointer\",\n          className,\n        )}\n        onMouseEnter={(e) => {\n          onMouseEnter?.(e);\n          if (desktop) setIsHover(true);\n        }}\n        onMouseLeave={(e) => {\n          onMouseLeave?.(e);\n          setIsHover(false);\n        }}\n        onFocus={(e) => {\n          onFocus?.(e);\n          if (desktop) setIsHover(true);\n        }}\n        onBlur={(e) => {\n          onBlur?.(e);\n          setIsHover(false);\n        }}\n        ref={containerRef}\n        {...linkProps}\n      >\n        {text}\n        {showIcon && (\n          <span className=\"ml-1 text-sm\">\n            <FaArrowUpRightFromSquare />\n          </span>\n        )}\n      </a>\n      <AnimatePresence>\n        {isHover && desktop && (\n          <aside\n            aria-label={`Link preview for ${link}`}\n            className=\"fixed z-50 -translate-x-1/2 -translate-y-full pointer-events-auto\"\n            style={{\n              left: containerBounds.left + containerBounds.width / 2,\n              top: containerBounds.top - 8,\n            }}\n            onMouseEnter={() => setIsHover(true)}\n            onMouseLeave={() => setIsHover(false)}\n          >\n            <motion.div\n              className=\"overflow-clip w-fit bg-popover text-popover-foreground border border-border rounded-xl shadow-xl origin-bottom\"\n              initial={\n                shouldReduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, scale: 0.95, y: 4 }\n              }\n              animate={{ opacity: 1, scale: 1, y: 0 }}\n              exit={\n                shouldReduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, scale: 0.97 }\n              }\n              transition={\n                shouldReduceMotion\n                  ? motionTransition.reduced\n                  : motionTransition.enter\n              }\n            >\n              <div className=\"flex flex-col gap-y-2 px-4 py-2 w-fit rounded-xl\">\n                <div className=\"flex justify-between gap-x-4 w-full text-sm\">\n                  External Link\n                  <a href={link} aria-label={`Open ${link}`}>\n                    <FaArrowUpRightFromSquare />\n                  </a>\n                </div>\n                <a href={link} className=\"text-nowrap underline\">\n                  {link}\n                </a>\n              </div>\n            </motion.div>\n          </aside>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n",
      "target": "components/ui/link-preview/link-preview.tsx"
    }
  ],
  "dependencies": [
    "motion",
    "react-icons",
    "react-use-measure"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils",
    "@sona-ui/sona-motion"
  ]
}