"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, FileType, Github, Moon, PaletteIcon, Sun, Type } from "lucide-react" import Image from "next/image" import Link from "next/link" import type React from "react" import { useCallback, useState } from "react" import { toast } from "sonner" import { Carbon } from "./carbon" import { IconActions } from "./icon-actions" import { MagicCard } from "./magicui/magic-card" import { Badge } from "./ui/badge" type RenderVariantFn = ( format: string, iconName: string, theme?: "light" | "dark" ) => React.ReactNode type IconVariantsSectionProps = { title: string description: string iconElement: React.ReactNode aavailableFormats: string[] icon: string iconData: Icon handleCopy: (url: string, variantKey: string, event?: React.MouseEvent) => void handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise copiedVariants: Record theme?: "light" | "dark" renderVariant: RenderVariantFn } function IconVariantsSection({ title, description, iconElement, aavailableFormats, icon, iconData, theme, renderVariant, }: IconVariantsSectionProps) { const iconName = theme && iconData.colors?.[theme] ? iconData.colors[theme] : icon return (

{iconElement} {title}

{description}

{aavailableFormats.map((format) => renderVariant(format, iconName, theme))}
) } type WordmarkSectionProps = { iconData: Icon icon: string aavailableFormats: string[] handleCopy: (url: string, variantKey: string, event?: React.MouseEvent) => void handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise copiedVariants: Record renderVariant: RenderVariantFn } function WordmarkSection({ iconData, aavailableFormats, renderVariant, }: WordmarkSectionProps) { if (!iconData.wordmark) return null return (

Wordmark Variants

Icon variants that include the brand name. Click to copy URL.

{iconData.wordmark.light && (

Light Theme Wordmark

{aavailableFormats.map((format) => { if (!iconData.wordmark?.light) return null return renderVariant(format, iconData.wordmark.light, "light") })}
)} {iconData.wordmark.dark && (

Dark Theme Wordmark

{aavailableFormats.map((format) => { if (!iconData.wordmark?.dark) return null return renderVariant(format, iconData.wordmark.dark, "dark") })}
)}
) } 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 iconWordmarkVariants = iconData.wordmark 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>({}) const [copiedUrlKey, setCopiedUrlKey] = useState(null) const [copiedImageKey, setCopiedImageKey] = useState(null) const launchConfetti = useCallback((originX?: number, originY?: number) => { if (typeof confetti !== "function") return const defaults = { startVelocity: 15, spread: 180, ticks: 50, zIndex: 20, disableForReducedMotion: true, colors: ["#ff0a54", "#ff477e", "#ff7096", "#ff85a1", "#fbb1bd", "#f9bec7"], } if (originX !== undefined && originY !== undefined) { confetti({ ...defaults, particleCount: 50, origin: { x: originX / window.innerWidth, y: originY / window.innerHeight, }, }) } else { confetti({ ...defaults, particleCount: 50, origin: { x: 0.5, y: 0.5 }, }) } }, []) const handleCopyUrl = (url: string, variantKey: string, event?: React.MouseEvent) => { navigator.clipboard.writeText(url) setCopiedUrlKey(variantKey) setTimeout(() => { setCopiedUrlKey(null) }, 2000) 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 handleCopyImage = async ( imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent ) => { try { toast.loading("Copying image...") if (format === 'svg') { const response = await fetch(imageUrl) if (!response.ok) { throw new Error(`Failed to fetch SVG: ${response.statusText}`) } const svgText = await response.text() await navigator.clipboard.writeText(svgText) setCopiedImageKey(variantKey) setTimeout(() => { setCopiedImageKey(null) }, 2000) if (event) { launchConfetti(event.clientX, event.clientY) } else { launchConfetti() } toast.dismiss() toast.success("SVG Markup Copied", { description: "The SVG code has been copied to your clipboard.", }) } else if (format === 'png' || format === 'webp') { const mimeType = `image/${format}` const response = await fetch(imageUrl) if (!response.ok) { throw new Error(`Failed to fetch image: ${response.statusText}`) } const blob = await response.blob() if (!blob) { throw new Error('Failed to generate image blob') } await navigator.clipboard.write([new ClipboardItem({ [mimeType]: blob })]); setCopiedImageKey(variantKey) setTimeout(() => { setCopiedImageKey(null) }, 2000) if (event) { launchConfetti(event.clientX, event.clientY) } else { launchConfetti() } toast.dismiss() toast.success("Image copied", { description: `The ${format.toUpperCase()} image has been copied to your clipboard.`, }) } else { throw new Error(`Unsupported format for image copy: ${format}`) } } catch (error) { console.error("Copy error:", error) toast.dismiss() let description = "Could not copy. Check console for details." if (error instanceof Error) { description = error.message } toast.error("Copy failed", { description }) } } const handleDownload = async (event: React.MouseEvent, url: string, filename: string) => { event.preventDefault() launchConfetti(event.clientX, event.clientY) try { toast.loading("Preparing download...") const response = await fetch(url) const blob = await response.blob() const blobUrl = URL.createObjectURL(blob) const link = document.createElement("a") link.href = blobUrl link.download = filename document.body.appendChild(link) link.click() 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 (
handleCopyUrl(imageUrl, variantKey, e)} aria-label={`Copy ${format.toUpperCase()} URL for ${iconName}${theme ? ` (${theme} theme)` : ""}`} >
{`${iconName}

Click to copy direct URL to clipboard

{format.toUpperCase()}

) } const formatedIconName = formatIconName(icon) return (
{`High

{formatedIconName}

Updated on:

By:

{authorName ? authorName.slice(0, 2).toUpperCase() : "??"} {authorData.html_url && ( {authorName} )} {!authorData.html_url && ( {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."} {iconData.wordmark && " Wordmark variants are also available for enhanced branding options."}

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

Icon variants

Click on any icon to copy its URL to your clipboard
{!iconData.colors && ( } aavailableFormats={availableFormats} icon={icon} iconData={iconData} handleCopy={handleCopyUrl} handleDownload={handleDownload} copiedVariants={copiedVariants} renderVariant={renderVariant} /> )} {iconData.colors && ( <> } aavailableFormats={availableFormats} icon={icon} theme="light" iconData={iconData} handleCopy={handleCopyUrl} handleDownload={handleDownload} copiedVariants={copiedVariants} renderVariant={renderVariant} /> } aavailableFormats={availableFormats} icon={icon} theme="dark" iconData={iconData} handleCopy={handleCopyUrl} handleDownload={handleDownload} copiedVariants={copiedVariants} renderVariant={renderVariant} /> )} {iconData.wordmark && ( )}
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}
))}
)} {iconData.wordmark && (

Wordmark variants

{iconData.wordmark.light && (
Light: {iconData.wordmark.light}
)} {iconData.wordmark.dark && (
Dark: {iconData.wordmark.dark}
)}
)}

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 && (
)}
) })()}
) }