Link Preview
gaia/link-previewFreeComponent
An interactive link component with rich preview tooltip showing website metadata like title, description, and images.
Install it
npx shadcn@latest add https://ui.heygaia.io/r/link-preview.jsonSource
1"use client";23import Image from "next/image";4import { type ReactNode, useEffect, useRef, useState } from "react";5import { Globe02Icon, HugeiconsIcon } from "@/components/icons";67import {8 Tooltip,9 TooltipContent,10 TooltipTrigger,11} from "@/components/ui/tooltip";1213interface UrlMetadata {14 title: string | null;15 description: string | null;16 favicon: string | null;17 website_name: string | null;18 website_image: string | null;19 url: string;20}2122interface LinkPreviewProps {23 href: string;24 children: ReactNode | string | null;25 className?: string;26}2728const isEmail = (str: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);2930const isValidHttpUrl = (str: string): boolean => {31 try {32 const url = new URL(str);33 return /^(http|https):$/.test(url.protocol);34 } catch {35 return false;36 }37};3839export function LinkPreview({40 href,41 children,42 className = "cursor-pointer rounded-sm bg-primary/20 px-1 text-sm font-medium text-primary transition-all hover:text-white hover:underline",43}: LinkPreviewProps) {44 const elementRef = useRef<HTMLAnchorElement>(null);45 const [isInView, setIsInView] = useState(false);46 const [validFavicon, setValidFavicon] = useState(true);47 const [validImage, setValidImage] = useState(true);48 const [metadata, setMetadata] = useState<UrlMetadata | null>(null);49 const [isLoading, setIsLoading] = useState(false);50 const [error, setError] = useState<Error | null>(null);5152 const isValidUrl =53 href &&54 isValidHttpUrl(href) &&55 !isEmail(href) &&56 !href.startsWith("mailto:");5758 // Fetch metadata when in view59 useEffect(() => {60 if (!isInView || !isValidUrl || metadata) return;6162 let isMounted = true;6364 async function fetchMetadata() {65 setIsLoading(true);66 setError(null);6768 try {69 // Fetch the URL directly70 const response = await fetch(href);7172 if (!response.ok) {73 throw new Error("Failed to fetch URL");74 }7576 const html = await response.text();7778 // Extract metadata from HTML79 const getMetaTag = (name: string): string | null => {80 const patterns = [81 new RegExp(82 `<meta[^>]*property=["']${name}["'][^>]*content=["']([^"']*)["']`,83 "i",84 ),85 new RegExp(86 `<meta[^>]*name=["']${name}["'][^>]*content=["']([^"']*)["']`,87 "i",88 ),89 new RegExp(90 `<meta[^>]*content=["']([^"']*)["'][^>]*property=["']${name}["']`,91 "i",92 ),93 new RegExp(94 `<meta[^>]*content=["']([^"']*)["'][^>]*name=["']${name}["']`,95 "i",96 ),97 ];9899// … truncated
What it pulls in
Other registry items
Files it writes
More from gaia
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Area Chartarea-chart | gaia | Components | Free | 1 dep | |
| Author Tooltipauthor-tooltip | gaia | Components | Free | no deps | |
| Bar Chartbar-chart | gaia | Components | Free | 1 dep | |
| Composercomposer | gaia | Components | Free | no deps | |
| Email Compose Cardemail-compose-card | gaia | Components | Free | no deps | |
| Gauge Chartgauge-chart | gaia | Components | Free | 1 dep | |
| GitHub Stars Buttongithub-stars-button | gaia | Components | Free | 1 dep | |
| Holo Cardholo-card | gaia | Components | Free | 1 dep · 2 files |
