chore: format codebase

This commit is contained in:
Thomas Camlong
2025-09-29 11:01:14 +02:00
parent 68970f5908
commit b5c72677fc
30 changed files with 220 additions and 290 deletions

View File

@@ -7,10 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": [
"src/**",
"!src/components/ui"
]
"includes": ["src/**", "!src/components/ui"]
},
"formatter": {
"lineWidth": 140,
@@ -22,6 +19,8 @@
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off",
"noUnknownAtRules": "off",
"noArrayIndexKey": "off"
}
}

View File

@@ -1,18 +1,11 @@
"use client"
import { Button } from "@/components/ui/button"
import { AlertTriangle, ArrowLeft, RefreshCcw } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { Button } from "@/components/ui/button"
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
export default function ErrorPage({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
const router = useRouter()
useEffect(() => {

View File

@@ -125,20 +125,20 @@
--background: oklch(0.99 0 0);
--foreground: oklch(0.32 0 0);
--card: oklch(1.0 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.32 0 0);
--popover: oklch(1.0 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.32 0 0);
--primary: oklch(0.67 0.2 23.8);
--primary-foreground: oklch(1.0 0 0);
--secondary: oklch(0.97 0.0 264.54);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.97 0 264.54);
--secondary-foreground: oklch(0.45 0.03 256.8);
--muted: oklch(0.98 0.0 247.84);
--muted: oklch(0.98 0 247.84);
--muted-foreground: oklch(0.55 0.02 264.36);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.64 0.21 25.33);
--destructive-foreground: oklch(1.0 0 0);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.9 0.01 247.88);
--input: oklch(0.92 0.004 286.32);
@@ -159,16 +159,11 @@
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px
hsl(0 0% 0% / 0.1);
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px
hsl(0 0% 0% / 0.1);
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 2px 4px -1px
hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 4px 6px -1px
hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 8px 10px -1px
hsl(0 0% 0% / 0.1);
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.25);
--magic-gradient-color: oklch(0.67 0.2 23.8 / 15%);
@@ -182,7 +177,7 @@
--popover: oklch(0.29 0.02 268.4);
--popover-foreground: oklch(0.92 0 0);
--primary: oklch(0.67 0.2 23.8);
--primary-foreground: oklch(1.0 0 0);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.31 0.03 266.71);
--secondary-foreground: oklch(0.92 0 0);
--muted: oklch(0.31 0.03 266.71);
@@ -190,7 +185,7 @@
--accent: oklch(0.34 0.06 267.59);
--accent-foreground: oklch(0.88 0.06 254.13);
--destructive: oklch(0.64 0.21 25.33);
--destructive-foreground: oklch(1.0 0 0);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.38 0.03 269.73);
--input: oklch(1 0 0 / 15%);
@@ -211,16 +206,11 @@
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px
hsl(0 0% 0% / 0.1);
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px
hsl(0 0% 0% / 0.1);
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 2px 4px -1px
hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 4px 6px -1px
hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 8px 10px -1px
hsl(0 0% 0% / 0.1);
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.1), 0px 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.25);
--magic-gradient-color: oklch(0.27 0 0);

View File

@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises"
import { join } from "node:path"
import { getAllIcons } from "@/lib/api"
import { ImageResponse } from "next/og"
import { getAllIcons } from "@/lib/api"
export const dynamic = "force-static"
@@ -42,7 +42,7 @@ export default async function Image({ params }: { params: { icon: string } }) {
const iconPath = join(process.cwd(), `../png/${icon}.png`)
console.log(`Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`)
iconData = await readFile(iconPath)
} catch (error) {
} catch (_error) {
console.error(`Icon ${icon} was not found locally`)
}

View File

@@ -1,8 +1,8 @@
import type { Metadata, ResolvingMetadata } from "next"
import { notFound } from "next/navigation"
import { IconDetails } from "@/components/icon-details"
import { BASE_URL, WEB_URL } from "@/constants"
import { getAllIcons, getAuthorData } from "@/lib/api"
import type { Metadata, ResolvingMetadata } from "next"
import { notFound } from "next/navigation"
export const dynamicParams = false
export async function generateStaticParams() {
@@ -19,7 +19,7 @@ type Props = {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata> {
export async function generateMetadata({ params, searchParams }: Props, _parent: ResolvingMetadata): Promise<Metadata> {
const { icon } = await params
const iconsData = await getAllIcons()
if (!iconsData[icon]) {
@@ -76,13 +76,14 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
type: "website",
url: pageUrl,
siteName: "Dashboard Icons",
images: [{
url: `${BASE_URL}/webp/${icon}.webp`,
width: 512,
height: 512,
alt: `${formattedIconName} icon`,
}]
images: [
{
url: `${BASE_URL}/webp/${icon}.webp`,
width: 512,
height: 512,
alt: `${formattedIconName} icon`,
},
],
},
twitter: {
card: "summary_large_image",

View File

@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils"
import type React from "react"
import { cn } from "@/lib/utils"
interface BackgroundWrapperProps {
children: React.ReactNode

View File

@@ -1,5 +1,5 @@
import { getAllIcons } from "@/lib/api"
import { ImageResponse } from "next/og"
import { getAllIcons } from "@/lib/api"
export const dynamic = "force-static"

View File

@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import { IconSearch } from "@/components/icon-search"
import { BASE_URL } from "@/constants"
import { getIconsArray } from "@/lib/api"
import type { Metadata } from "next"
export async function generateMetadata(): Promise<Metadata> {
const icons = await getIconsArray()

View File

@@ -1,12 +1,12 @@
import { PostHogProvider } from "@/components/PostHogProvider"
import { Footer } from "@/components/footer"
import { HeaderWrapper } from "@/components/header-wrapper"
import { LicenseNotice } from "@/components/license-notice"
import { BASE_URL, WEB_URL, getDescription, websiteTitle } from "@/constants"
import { getTotalIcons } from "@/lib/api"
import type { Metadata, Viewport } from "next"
import { Inter } from "next/font/google"
import { Toaster } from "sonner"
import { Footer } from "@/components/footer"
import { HeaderWrapper } from "@/components/header-wrapper"
import { LicenseNotice } from "@/components/license-notice"
import { PostHogProvider } from "@/components/PostHogProvider"
import { BASE_URL, getDescription, WEB_URL, websiteTitle } from "@/constants"
import { getTotalIcons } from "@/lib/api"
import "./globals.css"
import { ThemeProvider } from "./theme-provider"

View File

@@ -1,13 +1,9 @@
import { AlertTriangle, ArrowLeft } from "lucide-react"
import Link from "next/link"
import { IconSubmissionContent } from "@/components/icon-submission-form"
import { Button } from "@/components/ui/button"
import { AlertTriangle, ArrowLeft, PlusCircle } from "lucide-react"
import Link from "next/link"
export default function NotFound({
error,
}: {
error: Error & { digest?: string }
}) {
export default function NotFound({ error }: { error: Error & { digest?: string } }) {
return (
<div className="py-16 flex items-center justify-center">
<div className="text-center space-y-8 max-w-2xl mx-auto">

View File

@@ -1,6 +1,6 @@
import type { MetadataRoute } from "next"
import { BASE_URL, WEB_URL } from "@/constants"
import { getAllIcons } from "@/lib/api"
import type { MetadataRoute } from "next"
export const dynamic = "force-static"

View File

@@ -1,11 +1,5 @@
import { useEffect, useRef } from "react"
export function Carbon() {
// biome-ignore lint/style/noNonNullAssertion: <explanation>
const ref = useRef<HTMLDivElement>(null!)
if (process.env.NODE_ENV === "development") {
return null
}
useEffect(() => {
const serve = "CW7IKKQM"
const placement = "dashboardiconscom"
@@ -16,6 +10,11 @@ export function Carbon() {
ref.current.appendChild(s)
}, [])
const ref = useRef<HTMLDivElement>(null!)
if (process.env.NODE_ENV === "development") {
return null
}
return (
<>
<style>

View File

@@ -1,13 +1,13 @@
"use client"
import { Badge } from "@/components/ui/badge"
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { useMediaQuery } from "@/hooks/use-media-query"
import { filterAndSortIcons, formatIconName, fuzzySearch } from "@/lib/utils"
import type { IconWithName } from "@/types/icons"
import { Info, Search as SearchIcon, Tag } from "lucide-react"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react"
import { Badge } from "@/components/ui/badge"
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { useMediaQuery } from "@/hooks/use-media-query"
import { filterAndSortIcons, formatIconName } from "@/lib/utils"
import type { IconWithName } from "@/types/icons"
interface CommandMenuProps {
icons: IconWithName[]
@@ -20,7 +20,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
const router = useRouter()
const [internalOpen, setInternalOpen] = useState(false)
const [query, setQuery] = useState("")
const isDesktop = useMediaQuery("(min-width: 768px)")
const _isDesktop = useMediaQuery("(min-width: 768px)")
// Use either external or internal state for controlling open state
const isOpen = externalOpen !== undefined ? externalOpen : internalOpen

View File

@@ -1,6 +1,6 @@
import { REPO_PATH } from "@/constants"
import { ExternalLink } from "lucide-react"
import Link from "next/link"
import { REPO_PATH } from "@/constants"
import { HeartEasterEgg } from "./heart"
export function Footer() {

View File

@@ -1,8 +1,8 @@
"use client"
import { cn } from "@/lib/utils"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
export function HeaderNav() {
const pathname = usePathname()

View File

@@ -1,13 +1,13 @@
"use client"
import { Github, PlusCircle, Search } from "lucide-react"
import Link from "next/link"
import { useEffect, useState } from "react"
import { IconSubmissionForm } from "@/components/icon-submission-form"
import { ThemeSwitcher } from "@/components/theme-switcher"
import { REPO_PATH } from "@/constants"
import { getIconsArray } from "@/lib/api"
import type { IconWithName } from "@/types/icons"
import { Github, PlusCircle, Search } from "lucide-react"
import Link from "next/link"
import { useEffect, useState } from "react"
import { CommandMenu } from "./command-menu"
import { HeaderNav } from "./header-nav"
import { Button } from "./ui/button"

View File

@@ -1,8 +1,7 @@
"use client"
import { Heart } from "lucide-react"
import { motion } from "framer-motion"
import { Heart } from "lucide-react"
import { useState } from "react"
export function HeartEasterEgg() {

View File

@@ -1,9 +1,5 @@
"use client"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import { Separator } from "@radix-ui/react-dropdown-menu"
import { motion, useAnimation, useInView } from "framer-motion"
import {
@@ -25,6 +21,10 @@ import {
} from "lucide-react"
import Link from "next/link"
import { useEffect, useRef, useState } from "react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import { AuroraText } from "./magicui/aurora-text"
import { InteractiveHoverButton } from "./magicui/interactive-hover-button"
import { NumberTicker } from "./magicui/number-ticker"
@@ -35,7 +35,7 @@ interface IconCardProps {
imageUrl: string
}
function IconCard({ name, imageUrl }: IconCardProps) {
function _IconCard({ name, imageUrl }: IconCardProps) {
return (
<Card className="p-4 flex flex-col items-center gap-2 cursor-pointer group hover-lift card-hover">
<div className="w-16 h-16 flex items-center justify-center">

View File

@@ -1,122 +1,109 @@
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Check, Copy, Download, Github, Link as LinkIcon } from "lucide-react";
import Link from "next/link";
import type React from "react";
import { Check, Copy, Download, Github, Link as LinkIcon } from "lucide-react"
import Link from "next/link"
import type React from "react"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
export type IconActionsProps = {
imageUrl: string;
githubUrl: string;
iconName: string;
format: string;
variantKey: string;
copiedUrlKey: string | null;
copiedImageKey: string | null;
handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise<void>;
handleCopyUrl: (url: string, variantKey: string, event?: React.MouseEvent) => void;
handleCopyImage: (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => Promise<void>;
};
imageUrl: string
githubUrl: string
iconName: string
format: string
variantKey: string
copiedUrlKey: string | null
copiedImageKey: string | null
handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise<void>
handleCopyUrl: (url: string, variantKey: string, event?: React.MouseEvent) => void
handleCopyImage: (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => Promise<void>
}
export function IconActions({
imageUrl,
githubUrl,
iconName,
format,
variantKey,
copiedUrlKey,
copiedImageKey,
handleDownload,
handleCopyUrl,
handleCopyImage,
imageUrl,
githubUrl,
iconName,
format,
variantKey,
copiedUrlKey,
copiedImageKey,
handleDownload,
handleCopyUrl,
handleCopyImage,
}: IconActionsProps) {
const downloadFilename = `${iconName}.${format}`;
const isUrlCopied = copiedUrlKey === variantKey;
const isImageCopied = copiedImageKey === variantKey;
const downloadFilename = `${iconName}.${format}`
const isUrlCopied = copiedUrlKey === variantKey
const isImageCopied = copiedImageKey === variantKey
return (
<TooltipProvider delayDuration={300}>
<div className="flex gap-2 mt-3 w-full justify-center">
{/* Download Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleDownload(e, imageUrl, downloadFilename)}
aria-label={`Download ${iconName} as ${format.toUpperCase()}`}
>
<Download className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Download {format.toUpperCase()}</p>
</TooltipContent>
</Tooltip>
return (
<TooltipProvider delayDuration={300}>
<div className="flex gap-2 mt-3 w-full justify-center">
{/* Download Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleDownload(e, imageUrl, downloadFilename)}
aria-label={`Download ${iconName} as ${format.toUpperCase()}`}
>
<Download className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Download {format.toUpperCase()}</p>
</TooltipContent>
</Tooltip>
{/* Copy Image Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleCopyImage(imageUrl, format, variantKey, e)}
aria-label={`Copy ${iconName} image as ${format.toUpperCase()}`}
>
{isImageCopied ? (
<Check className="w-4 h-4 text-green-500" />
) : (
<Copy className="w-4 h-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy image to clipboard</p>
</TooltipContent>
</Tooltip>
{/* Copy Image Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleCopyImage(imageUrl, format, variantKey, e)}
aria-label={`Copy ${iconName} image as ${format.toUpperCase()}`}
>
{isImageCopied ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy image to clipboard</p>
</TooltipContent>
</Tooltip>
{/* Copy URL Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleCopyUrl(imageUrl, variantKey, e)}
aria-label={`Copy direct URL for ${iconName} ${format.toUpperCase()}`}
>
{isUrlCopied ? (
<Check className="w-4 h-4 text-green-500" />
) : (
<LinkIcon className="w-4 h-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy direct URL</p>
</TooltipContent>
</Tooltip>
{/* Copy URL Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg cursor-pointer"
onClick={(e) => handleCopyUrl(imageUrl, variantKey, e)}
aria-label={`Copy direct URL for ${iconName} ${format.toUpperCase()}`}
>
{isUrlCopied ? <Check className="w-4 h-4 text-green-500" /> : <LinkIcon className="w-4 h-4" />}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy direct URL</p>
</TooltipContent>
</Tooltip>
{/* View on GitHub Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
<Link
href={githubUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={`View ${iconName} ${format} file on GitHub`}
>
<Github className="w-4 h-4" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>View on GitHub</p>
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
);
{/* View on GitHub Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
<Link href={githubUrl} target="_blank" rel="noopener noreferrer" aria-label={`View ${iconName} ${format} file on GitHub`}>
<Github className="w-4 h-4" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>View on GitHub</p>
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
)
}

View File

@@ -1,19 +1,11 @@
import Image from "next/image"
import Link from "next/link"
import { MagicCard } from "@/components/magicui/magic-card"
import { BASE_URL } from "@/constants"
import { formatIconName } from "@/lib/utils"
import type { Icon } from "@/types/icons"
import Image from "next/image"
import Link from "next/link"
export function IconCard({
name,
data: iconData,
matchedAlias,
}: {
name: string
data: Icon
matchedAlias?: string
}) {
export function IconCard({ name, data: iconData, matchedAlias }: { name: string; data: Icon; matchedAlias?: string }) {
const formatedIconName = formatIconName(name)
return (
<MagicCard className="rounded-md shadow-md">

View File

@@ -1,13 +1,5 @@
"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"
@@ -16,16 +8,20 @@ import Link from "next/link"
import type React from "react"
import { useCallback, useState } from "react"
import { toast } from "sonner"
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 { 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 RenderVariantFn = (format: string, iconName: string, theme?: "light" | "dark") => React.ReactNode
type IconVariantsSectionProps = {
title: string
@@ -76,11 +72,7 @@ type WordmarkSectionProps = {
renderVariant: RenderVariantFn
}
function WordmarkSection({
iconData,
aavailableFormats,
renderVariant,
}: WordmarkSectionProps) {
function WordmarkSection({ iconData, aavailableFormats, renderVariant }: WordmarkSectionProps) {
if (!iconData.wordmark) return null
return (
@@ -89,9 +81,7 @@ function WordmarkSection({
<Type className="w-4 h-4 text-green-500" />
Wordmark Variants
</h3>
<p className="text-sm text-muted-foreground mb-4">
Icon variants that include the brand name. Click to copy URL.
</p>
<p className="text-sm text-muted-foreground mb-4">Icon variants that include the brand name. Click to copy URL.</p>
<div className="space-y-6">
{iconData.wordmark.light && (
<div>
@@ -135,8 +125,8 @@ export type IconDetailsProps = {
export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetailsProps) {
const authorName = authorData.name || authorData.login || ""
const iconColorVariants = iconData.colors
const iconWordmarkVariants = iconData.wordmark
const _iconColorVariants = iconData.colors
const _iconWordmarkVariants = iconData.wordmark
const formattedDate = new Date(iconData.update.timestamp).toLocaleDateString("en-GB", {
day: "numeric",
month: "long",
@@ -155,7 +145,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
}
const availableFormats = getAvailableFormats()
const [copiedVariants, setCopiedVariants] = useState<Record<string, boolean>>({})
const [copiedVariants, _setCopiedVariants] = useState<Record<string, boolean>>({})
const [copiedUrlKey, setCopiedUrlKey] = useState<string | null>(null)
const [copiedImageKey, setCopiedImageKey] = useState<string | null>(null)
@@ -207,16 +197,11 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
})
}
const handleCopyImage = async (
imageUrl: string,
format: string,
variantKey: string,
event?: React.MouseEvent
) => {
const handleCopyImage = async (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => {
try {
toast.loading("Copying image...")
if (format === 'svg') {
if (format === "svg") {
const response = await fetch(imageUrl)
if (!response.ok) {
throw new Error(`Failed to fetch SVG: ${response.statusText}`)
@@ -240,8 +225,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
toast.success("SVG Markup Copied", {
description: "The SVG code has been copied to your clipboard.",
})
} else if (format === 'png' || format === 'webp') {
} else if (format === "png" || format === "webp") {
const mimeType = `image/${format}`
const response = await fetch(imageUrl)
if (!response.ok) {
@@ -250,10 +234,10 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
const blob = await response.blob()
if (!blob) {
throw new Error('Failed to generate image blob')
throw new Error("Failed to generate image blob")
}
await navigator.clipboard.write([new ClipboardItem({ [mimeType]: blob })]);
await navigator.clipboard.write([new ClipboardItem({ [mimeType]: blob })])
setCopiedImageKey(variantKey)
setTimeout(() => {
@@ -270,11 +254,9 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
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()
@@ -445,9 +427,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
{authorName}
</Link>
)}
{!authorData.html_url && (
<span className="text-sm">{authorName}</span>
)}
{!authorData.html_url && <span className="text-sm">{authorName}</span>}
</div>
</div>
</div>
@@ -696,6 +676,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
<Card className="bg-background/50 border shadow-lg">
<CardHeader>
<CardTitle>
{/** biome-ignore lint/correctness/useUniqueElementIds: I want the ID to be fixed */}
<h2 id="related-icons-title">Related Icons</h2>
</CardTitle>
<CardDescription>

View File

@@ -1,7 +1,6 @@
import type { Icon } from "@/types/icons"
import { useWindowVirtualizer } from "@tanstack/react-virtual"
import { useEffect, useMemo, useRef, useState } from "react"
import type { Icon } from "@/types/icons"
import { IconCard } from "./icon-card"
interface IconsGridProps {

View File

@@ -1,5 +1,11 @@
"use client"
import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useTheme } from "next-themes"
import posthog from "posthog-js"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { toast } from "sonner"
import { VirtualizedIconsGrid } from "@/components/icon-grid"
import { IconSubmissionContent } from "@/components/icon-submission-form"
import { Badge } from "@/components/ui/badge"
@@ -17,14 +23,8 @@ import {
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { type SortOption, filterAndSortIcons } from "@/lib/utils"
import { filterAndSortIcons, type SortOption } from "@/lib/utils"
import type { IconSearchProps } from "@/types/icons"
import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react"
import { useTheme } from "next-themes"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import posthog from "posthog-js"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { toast } from "sonner"
export function IconSearch({ icons }: IconSearchProps) {
const searchParams = useSearchParams()
@@ -359,6 +359,7 @@ export function IconSearch({ icons }: IconSearchProps) {
<p className="text-lg text-muted-foreground mt-2">Help us expand our collection</p>
</div>
<div className="flex flex-col gap-4 items-center w-full">
{/** biome-ignore lint/correctness/useUniqueElementIds: I want the ID to be fixed */}
<div id="icon-submission-content" className="w-full">
<IconSubmissionContent />
</div>

View File

@@ -1,12 +1,12 @@
"use client"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { REPO_PATH } from "@/constants"
import { DialogDescription } from "@radix-ui/react-dialog"
import { ExternalLink, PlusCircle } from "lucide-react"
import Link from "next/link"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { REPO_PATH } from "@/constants"
export const ISSUE_TEMPLATES = [
{

View File

@@ -1,11 +1,11 @@
"use client"
import { Button } from "@/components/ui/button"
import { REPO_PATH } from "@/constants"
import { AnimatePresence, motion } from "framer-motion"
import { X } from "lucide-react"
import Link from "next/link"
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { REPO_PATH } from "@/constants"
const LOCAL_STORAGE_KEY = "licenseNoticeDismissed"

View File

@@ -1,6 +1,6 @@
import { cn } from "@/lib/utils"
import { ArrowRight } from "lucide-react"
import React from "react"
import { cn } from "@/lib/utils"
import { Button } from "../ui/button"
interface InteractiveHoverButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

View File

@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils"
import type { ComponentPropsWithoutRef } from "react"
import { cn } from "@/lib/utils"
interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
/**

View File

@@ -1,13 +1,13 @@
"use client"
import { Marquee } from "@/components/magicui/marquee"
import { BASE_URL } from "@/constants"
import { cn, formatIconName } from "@/lib/utils"
import type { Icon, IconWithName } from "@/types/icons"
import { format, isToday, isYesterday } from "date-fns"
import { ArrowRight, Clock, ExternalLink } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { Marquee } from "@/components/magicui/marquee"
import { BASE_URL } from "@/constants"
import { cn, formatIconName } from "@/lib/utils"
import type { Icon, IconWithName } from "@/types/icons"
function formatIconDate(timestamp: string): string {
const date = new Date(timestamp)
@@ -71,13 +71,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) {
}
// Marquee-compatible icon card
function RecentIconCard({
name,
data,
}: {
name: string
data: Icon
}) {
function RecentIconCard({ name, data }: { name: string; data: Icon }) {
const formattedIconName = formatIconName(name)
return (
<Link

View File

@@ -2,11 +2,10 @@
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { useState } from "react"
export function ThemeSwitcher() {
const { setTheme } = useTheme()

View File

@@ -1,6 +1,6 @@
import type { IconWithName } from "@/types/icons"
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import type { IconWithName } from "@/types/icons"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))