import { cva } from "class-variance-authority"; import React, { useCallback, useState } from "react"; import { GlobeAltIcon } from "@sparkle/icons"; import { cn } from "@sparkle/lib/utils"; const faviconVariants = cva("", { variants: { size: { sm: "s-w-4 s-h-4", md: "s-w-5 s-h-5", lg: "s-w-6 s-h-6", }, }, defaultVariants: { size: "sm", }, }); interface FaviconIconProps { faviconUrl?: string; websiteUrl?: string; size?: "sm" | "md" | "lg"; className?: string; } /** * Component that displays a website favicon with fallback to GlobeAltIcon * If faviconUrl is provided, uses that. If websiteUrl is provided, generates favicon URL. * Falls back to GlobeAltIcon if favicon fails to load. */ export function FaviconIcon({ faviconUrl, websiteUrl, size = "sm", className, }: FaviconIconProps) { const [hasError, setHasError] = useState(false); const [isLoading, setIsLoading] = useState(true); const handleError = useCallback(() => { setHasError(true); setIsLoading(false); }, []); const handleLoad = useCallback(() => { setIsLoading(false); }, []); // Determine favicon URL let finalFaviconUrl = faviconUrl; if (!finalFaviconUrl && websiteUrl) { try { const domain = new URL(websiteUrl).hostname; finalFaviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=64`; } catch { // Invalid URL, fallback to icon } } // If no favicon URL or it failed to load, show fallback icon if (!finalFaviconUrl || hasError) { return ; } return (
Website icon {(isLoading || hasError) && ( )}
); }