"use client" import { IconsGrid } from "@/components/icon-grid" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { BASE_URL, REPO_PATH } from "@/constants" import { formatIconName } from "@/lib/utils" import type { AuthorData, Icon, IconFile } from "@/types/icons" import confetti from "canvas-confetti" import { motion } from "framer-motion" import { ArrowRight, Check, Copy, Download, FileType, Github, Moon, PaletteIcon, Sun } from "lucide-react" import dynamic from "next/dynamic" import Image from "next/image" import Link from "next/link" import { useCallback, useState } from "react" import { toast } from "sonner" import { Carbon } from "./carbon" import { MagicCard } from "./magicui/magic-card" import { Badge } from "./ui/badge" export type IconDetailsProps = { icon: string iconData: Icon authorData: AuthorData allIcons: IconFile } export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetailsProps) { const authorName = authorData.name || authorData.login || "" const iconColorVariants = iconData.colors const formattedDate = new Date(iconData.update.timestamp).toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric", }) const getAvailableFormats = () => { switch (iconData.base) { case "svg": return ["svg", "png", "webp"] case "png": return ["png", "webp"] default: return [iconData.base] } } const availableFormats = getAvailableFormats() const [copiedVariants, setCopiedVariants] = useState>({}) // Launch confetti from the pointer position const launchConfetti = useCallback((originX?: number, originY?: number) => { const defaults = { startVelocity: 15, spread: 180, ticks: 50, zIndex: 20, disableForReducedMotion: true, colors: ["#ff0a54", "#ff477e", "#ff7096", "#ff85a1", "#fbb1bd", "#f9bec7"], } // If we have origin coordinates, use them if (originX !== undefined && originY !== undefined) { confetti({ ...defaults, particleCount: 50, origin: { x: originX / window.innerWidth, y: originY / window.innerHeight, }, }) } else { // Default to center of screen confetti({ ...defaults, particleCount: 50, origin: { x: 0.5, y: 0.5 }, }) } }, []) const handleCopy = (url: string, variantKey: string, event?: React.MouseEvent) => { navigator.clipboard.writeText(url) setCopiedVariants((prev) => ({ ...prev, [variantKey]: true, })) setTimeout(() => { setCopiedVariants((prev) => ({ ...prev, [variantKey]: false, })) }, 2000) // Launch confetti from click position or center of screen if (event) { launchConfetti(event.clientX, event.clientY) } else { launchConfetti() } toast.success("URL copied", { description: "The icon URL has been copied to your clipboard. Ready to use!", }) } const handleDownload = async (event: React.MouseEvent, url: string, filename: string) => { event.preventDefault() // Launch confetti from download button position launchConfetti(event.clientX, event.clientY) try { // Show loading toast toast.loading("Preparing download...") // Fetch the file first as a blob const response = await fetch(url) const blob = await response.blob() // Create a blob URL and use it for download const blobUrl = URL.createObjectURL(blob) const link = document.createElement("a") link.href = blobUrl link.download = filename document.body.appendChild(link) link.click() // Clean up document.body.removeChild(link) setTimeout(() => URL.revokeObjectURL(blobUrl), 100) toast.dismiss() toast.success("Download started", { description: "Your icon file is being downloaded and will be saved to your device.", }) } catch (error) { console.error("Download error:", error) toast.dismiss() toast.error("Download failed", { description: "There was an error downloading the file. Please try again.", }) } } const renderVariant = (format: string, iconName: string, theme?: "light" | "dark") => { const variantName = theme && iconColorVariants?.[theme] ? iconColorVariants[theme] : iconName const imageUrl = `${BASE_URL}/${format}/${variantName}.${format}` const githubUrl = `${REPO_PATH}/tree/main/${format}/${iconName}.${format}` const variantKey = `${format}-${theme || "default"}` const isCopied = copiedVariants[variantKey] || false return (
handleCopy(imageUrl, variantKey, e)} aria-label={`Copy ${format.toUpperCase()} URL for ${iconName}${theme ? ` (${theme} theme)` : ""}`} >
{`${iconName}

Click to copy direct URL to clipboard

{format.toUpperCase()}

Download icon file

Copy direct URL to clipboard

View on GitHub

) } const formatedIconName = formatIconName(icon) return (
{/* Left Column: Icon Info and Author */}
{`High

{formatedIconName}

Updated on:

By:

{authorName ? authorName.slice(0, 2).toUpperCase() : "??"} {authorData.html_url ? ( {authorName} ) : ( {authorName} )}
{iconData.categories && iconData.categories.length > 0 && (

Categories

{iconData.categories.map((category) => ( {category .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ")} ))}
)} {iconData.aliases && iconData.aliases.length > 0 && (

Aliases

{iconData.aliases.map((alias) => ( {alias} ))}
)}

About this icon

Available in{" "} {availableFormats.length > 1 ? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) ` : `${availableFormats[0].toUpperCase()} format `} with a base format of {iconData.base.toUpperCase()}. {iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}

Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {formatIconName(icon)}{" "} logo.

{/* Middle Column: Icon variants */}

Icon variants

Click on any icon to copy its URL to your clipboard
{!iconData.colors ? (
{availableFormats.map((format) => renderVariant(format, icon))}
) : (

Light theme

{availableFormats.map((format) => renderVariant(format, icon, "light"))}

Dark theme

{availableFormats.map((format) => renderVariant(format, icon, "dark"))}
)}
{/* Right Column: Technical details */}
Technical details

Base format

{iconData.base.toUpperCase()}

Available formats

{availableFormats.map((format) => (
{format.toUpperCase()}
))}
{iconData.colors && (

Color variants

{Object.entries(iconData.colors).map(([theme, variant]) => (
{theme}: {variant}
))}
)}

Source

{iconData.categories && iconData.categories.length > 0 && (() => { const MAX_RELATED_ICONS = 16 const currentCategories = iconData.categories || [] const relatedIconsWithScore = Object.entries(allIcons) .map(([name, data]) => { if (name === icon) return null // Exclude the current icon const otherCategories = data.categories || [] const commonCategories = currentCategories.filter((cat) => otherCategories.includes(cat)) const score = commonCategories.length return score > 0 ? { name, data, score } : null }) .filter((item): item is { name: string; data: Icon; score: number } => item !== null) // Type guard .sort((a, b) => b.score - a.score) // Sort by score DESC const topRelatedIcons = relatedIconsWithScore.slice(0, MAX_RELATED_ICONS) const viewMoreUrl = `/icons?${currentCategories.map((cat) => `category=${encodeURIComponent(cat)}`).join("&")}` if (topRelatedIcons.length === 0) return null return (
Other icons from {currentCategories.map((cat) => cat.replace(/-/g, " ")).join(", ")} categories {relatedIconsWithScore.length > MAX_RELATED_ICONS && (
)}
) })()}
) }