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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises" import { readFile } from "node:fs/promises"
import { join } from "node:path" import { join } from "node:path"
import { getAllIcons } from "@/lib/api"
import { ImageResponse } from "next/og" import { ImageResponse } from "next/og"
import { getAllIcons } from "@/lib/api"
export const dynamic = "force-static" 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`) const iconPath = join(process.cwd(), `../png/${icon}.png`)
console.log(`Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`) console.log(`Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`)
iconData = await readFile(iconPath) iconData = await readFile(iconPath)
} catch (error) { } catch (_error) {
console.error(`Icon ${icon} was not found locally`) 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 { IconDetails } from "@/components/icon-details"
import { BASE_URL, WEB_URL } from "@/constants" import { BASE_URL, WEB_URL } from "@/constants"
import { getAllIcons, getAuthorData } from "@/lib/api" import { getAllIcons, getAuthorData } from "@/lib/api"
import type { Metadata, ResolvingMetadata } from "next"
import { notFound } from "next/navigation"
export const dynamicParams = false export const dynamicParams = false
export async function generateStaticParams() { export async function generateStaticParams() {
@@ -19,7 +19,7 @@ type Props = {
searchParams: Promise<{ [key: string]: string | string[] | undefined }> 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 { icon } = await params
const iconsData = await getAllIcons() const iconsData = await getAllIcons()
if (!iconsData[icon]) { if (!iconsData[icon]) {
@@ -76,13 +76,14 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
type: "website", type: "website",
url: pageUrl, url: pageUrl,
siteName: "Dashboard Icons", siteName: "Dashboard Icons",
images: [{ images: [
{
url: `${BASE_URL}/webp/${icon}.webp`, url: `${BASE_URL}/webp/${icon}.webp`,
width: 512, width: 512,
height: 512, height: 512,
alt: `${formattedIconName} icon`, alt: `${formattedIconName} icon`,
}] },
],
}, },
twitter: { twitter: {
card: "summary_large_image", card: "summary_large_image",

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import { IconSearch } from "@/components/icon-search" import { IconSearch } from "@/components/icon-search"
import { BASE_URL } from "@/constants" import { BASE_URL } from "@/constants"
import { getIconsArray } from "@/lib/api" import { getIconsArray } from "@/lib/api"
import type { Metadata } from "next"
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const icons = await getIconsArray() 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 type { Metadata, Viewport } from "next"
import { Inter } from "next/font/google" import { Inter } from "next/font/google"
import { Toaster } from "sonner" 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 "./globals.css"
import { ThemeProvider } from "./theme-provider" 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 { IconSubmissionContent } from "@/components/icon-submission-form"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { AlertTriangle, ArrowLeft, PlusCircle } from "lucide-react"
import Link from "next/link"
export default function NotFound({ export default function NotFound({ error }: { error: Error & { digest?: string } }) {
error,
}: {
error: Error & { digest?: string }
}) {
return ( return (
<div className="py-16 flex items-center justify-center"> <div className="py-16 flex items-center justify-center">
<div className="text-center space-y-8 max-w-2xl mx-auto"> <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 { BASE_URL, WEB_URL } from "@/constants"
import { getAllIcons } from "@/lib/api" import { getAllIcons } from "@/lib/api"
import type { MetadataRoute } from "next"
export const dynamic = "force-static" export const dynamic = "force-static"

View File

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

View File

@@ -1,13 +1,13 @@
"use client" "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 { Info, Search as SearchIcon, Tag } from "lucide-react"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react" 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 { interface CommandMenuProps {
icons: IconWithName[] icons: IconWithName[]
@@ -20,7 +20,7 @@ export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalO
const router = useRouter() const router = useRouter()
const [internalOpen, setInternalOpen] = useState(false) const [internalOpen, setInternalOpen] = useState(false)
const [query, setQuery] = useState("") 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 // Use either external or internal state for controlling open state
const isOpen = externalOpen !== undefined ? externalOpen : internalOpen const isOpen = externalOpen !== undefined ? externalOpen : internalOpen

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
"use client" "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 { IconSubmissionForm } from "@/components/icon-submission-form"
import { ThemeSwitcher } from "@/components/theme-switcher" import { ThemeSwitcher } from "@/components/theme-switcher"
import { REPO_PATH } from "@/constants" import { REPO_PATH } from "@/constants"
import { getIconsArray } from "@/lib/api" import { getIconsArray } from "@/lib/api"
import type { IconWithName } from "@/types/icons" 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 { CommandMenu } from "./command-menu"
import { HeaderNav } from "./header-nav" import { HeaderNav } from "./header-nav"
import { Button } from "./ui/button" import { Button } from "./ui/button"

View File

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

View File

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

View File

@@ -1,21 +1,21 @@
import { Button } from "@/components/ui/button"; import { Check, Copy, Download, Github, Link as LinkIcon } from "lucide-react"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import Link from "next/link"
import { Check, Copy, Download, Github, Link as LinkIcon } from "lucide-react"; import type React from "react"
import Link from "next/link"; import { Button } from "@/components/ui/button"
import type React from "react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
export type IconActionsProps = { export type IconActionsProps = {
imageUrl: string; imageUrl: string
githubUrl: string; githubUrl: string
iconName: string; iconName: string
format: string; format: string
variantKey: string; variantKey: string
copiedUrlKey: string | null; copiedUrlKey: string | null
copiedImageKey: string | null; copiedImageKey: string | null
handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise<void>; handleDownload: (event: React.MouseEvent, url: string, filename: string) => Promise<void>
handleCopyUrl: (url: string, variantKey: string, event?: React.MouseEvent) => void; handleCopyUrl: (url: string, variantKey: string, event?: React.MouseEvent) => void
handleCopyImage: (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => Promise<void>; handleCopyImage: (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => Promise<void>
}; }
export function IconActions({ export function IconActions({
imageUrl, imageUrl,
@@ -29,9 +29,9 @@ export function IconActions({
handleCopyUrl, handleCopyUrl,
handleCopyImage, handleCopyImage,
}: IconActionsProps) { }: IconActionsProps) {
const downloadFilename = `${iconName}.${format}`; const downloadFilename = `${iconName}.${format}`
const isUrlCopied = copiedUrlKey === variantKey; const isUrlCopied = copiedUrlKey === variantKey
const isImageCopied = copiedImageKey === variantKey; const isImageCopied = copiedImageKey === variantKey
return ( return (
<TooltipProvider delayDuration={300}> <TooltipProvider delayDuration={300}>
@@ -64,11 +64,7 @@ export function IconActions({
onClick={(e) => handleCopyImage(imageUrl, format, variantKey, e)} onClick={(e) => handleCopyImage(imageUrl, format, variantKey, e)}
aria-label={`Copy ${iconName} image as ${format.toUpperCase()}`} aria-label={`Copy ${iconName} image as ${format.toUpperCase()}`}
> >
{isImageCopied ? ( {isImageCopied ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
<Check className="w-4 h-4 text-green-500" />
) : (
<Copy className="w-4 h-4" />
)}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
@@ -86,11 +82,7 @@ export function IconActions({
onClick={(e) => handleCopyUrl(imageUrl, variantKey, e)} onClick={(e) => handleCopyUrl(imageUrl, variantKey, e)}
aria-label={`Copy direct URL for ${iconName} ${format.toUpperCase()}`} aria-label={`Copy direct URL for ${iconName} ${format.toUpperCase()}`}
> >
{isUrlCopied ? ( {isUrlCopied ? <Check className="w-4 h-4 text-green-500" /> : <LinkIcon className="w-4 h-4" />}
<Check className="w-4 h-4 text-green-500" />
) : (
<LinkIcon className="w-4 h-4" />
)}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
@@ -102,12 +94,7 @@ export function IconActions({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild> <Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
<Link <Link href={githubUrl} target="_blank" rel="noopener noreferrer" aria-label={`View ${iconName} ${format} file on GitHub`}>
href={githubUrl}
target="_blank"
rel="noopener noreferrer"
aria-label={`View ${iconName} ${format} file on GitHub`}
>
<Github className="w-4 h-4" /> <Github className="w-4 h-4" />
</Link> </Link>
</Button> </Button>
@@ -118,5 +105,5 @@ export function IconActions({
</Tooltip> </Tooltip>
</div> </div>
</TooltipProvider> </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 { MagicCard } from "@/components/magicui/magic-card"
import { BASE_URL } from "@/constants" import { BASE_URL } from "@/constants"
import { formatIconName } from "@/lib/utils" import { formatIconName } from "@/lib/utils"
import type { Icon } from "@/types/icons" import type { Icon } from "@/types/icons"
import Image from "next/image"
import Link from "next/link"
export function IconCard({ export function IconCard({ name, data: iconData, matchedAlias }: { name: string; data: Icon; matchedAlias?: string }) {
name,
data: iconData,
matchedAlias,
}: {
name: string
data: Icon
matchedAlias?: string
}) {
const formatedIconName = formatIconName(name) const formatedIconName = formatIconName(name)
return ( return (
<MagicCard className="rounded-md shadow-md"> <MagicCard className="rounded-md shadow-md">

View File

@@ -1,13 +1,5 @@
"use client" "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 confetti from "canvas-confetti"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { ArrowRight, Check, FileType, Github, Moon, PaletteIcon, Sun, Type } from "lucide-react" 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 type React from "react"
import { useCallback, useState } from "react" import { useCallback, useState } from "react"
import { toast } from "sonner" 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 { Carbon } from "./carbon"
import { IconActions } from "./icon-actions" import { IconActions } from "./icon-actions"
import { MagicCard } from "./magicui/magic-card" import { MagicCard } from "./magicui/magic-card"
import { Badge } from "./ui/badge" import { Badge } from "./ui/badge"
type RenderVariantFn = ( type RenderVariantFn = (format: string, iconName: string, theme?: "light" | "dark") => React.ReactNode
format: string,
iconName: string,
theme?: "light" | "dark"
) => React.ReactNode
type IconVariantsSectionProps = { type IconVariantsSectionProps = {
title: string title: string
@@ -76,11 +72,7 @@ type WordmarkSectionProps = {
renderVariant: RenderVariantFn renderVariant: RenderVariantFn
} }
function WordmarkSection({ function WordmarkSection({ iconData, aavailableFormats, renderVariant }: WordmarkSectionProps) {
iconData,
aavailableFormats,
renderVariant,
}: WordmarkSectionProps) {
if (!iconData.wordmark) return null if (!iconData.wordmark) return null
return ( return (
@@ -89,9 +81,7 @@ function WordmarkSection({
<Type className="w-4 h-4 text-green-500" /> <Type className="w-4 h-4 text-green-500" />
Wordmark Variants Wordmark Variants
</h3> </h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="text-sm text-muted-foreground mb-4">Icon variants that include the brand name. Click to copy URL.</p>
Icon variants that include the brand name. Click to copy URL.
</p>
<div className="space-y-6"> <div className="space-y-6">
{iconData.wordmark.light && ( {iconData.wordmark.light && (
<div> <div>
@@ -135,8 +125,8 @@ export type IconDetailsProps = {
export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetailsProps) { export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetailsProps) {
const authorName = authorData.name || authorData.login || "" const authorName = authorData.name || authorData.login || ""
const iconColorVariants = iconData.colors const _iconColorVariants = iconData.colors
const iconWordmarkVariants = iconData.wordmark const _iconWordmarkVariants = iconData.wordmark
const formattedDate = new Date(iconData.update.timestamp).toLocaleDateString("en-GB", { const formattedDate = new Date(iconData.update.timestamp).toLocaleDateString("en-GB", {
day: "numeric", day: "numeric",
month: "long", month: "long",
@@ -155,7 +145,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
} }
const availableFormats = getAvailableFormats() 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 [copiedUrlKey, setCopiedUrlKey] = useState<string | null>(null)
const [copiedImageKey, setCopiedImageKey] = 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 ( const handleCopyImage = async (imageUrl: string, format: string, variantKey: string, event?: React.MouseEvent) => {
imageUrl: string,
format: string,
variantKey: string,
event?: React.MouseEvent
) => {
try { try {
toast.loading("Copying image...") toast.loading("Copying image...")
if (format === 'svg') { if (format === "svg") {
const response = await fetch(imageUrl) const response = await fetch(imageUrl)
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch SVG: ${response.statusText}`) 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", { toast.success("SVG Markup Copied", {
description: "The SVG code has been copied to your clipboard.", 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 mimeType = `image/${format}`
const response = await fetch(imageUrl) const response = await fetch(imageUrl)
if (!response.ok) { if (!response.ok) {
@@ -250,10 +234,10 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
const blob = await response.blob() const blob = await response.blob()
if (!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) setCopiedImageKey(variantKey)
setTimeout(() => { setTimeout(() => {
@@ -270,11 +254,9 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
toast.success("Image copied", { toast.success("Image copied", {
description: `The ${format.toUpperCase()} image has been copied to your clipboard.`, description: `The ${format.toUpperCase()} image has been copied to your clipboard.`,
}) })
} else { } else {
throw new Error(`Unsupported format for image copy: ${format}`) throw new Error(`Unsupported format for image copy: ${format}`)
} }
} catch (error) { } catch (error) {
console.error("Copy error:", error) console.error("Copy error:", error)
toast.dismiss() toast.dismiss()
@@ -445,9 +427,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
{authorName} {authorName}
</Link> </Link>
)} )}
{!authorData.html_url && ( {!authorData.html_url && <span className="text-sm">{authorName}</span>}
<span className="text-sm">{authorName}</span>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -696,6 +676,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
<Card className="bg-background/50 border shadow-lg"> <Card className="bg-background/50 border shadow-lg">
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
{/** biome-ignore lint/correctness/useUniqueElementIds: I want the ID to be fixed */}
<h2 id="related-icons-title">Related Icons</h2> <h2 id="related-icons-title">Related Icons</h2>
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>

View File

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

View File

@@ -1,5 +1,11 @@
"use client" "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 { VirtualizedIconsGrid } from "@/components/icon-grid"
import { IconSubmissionContent } from "@/components/icon-submission-form" import { IconSubmissionContent } from "@/components/icon-submission-form"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
@@ -17,14 +23,8 @@ import {
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator" 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 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) { export function IconSearch({ icons }: IconSearchProps) {
const searchParams = useSearchParams() 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> <p className="text-lg text-muted-foreground mt-2">Help us expand our collection</p>
</div> </div>
<div className="flex flex-col gap-4 items-center w-full"> <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"> <div id="icon-submission-content" className="w-full">
<IconSubmissionContent /> <IconSubmissionContent />
</div> </div>

View File

@@ -1,12 +1,12 @@
"use client" "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 { DialogDescription } from "@radix-ui/react-dialog"
import { ExternalLink, PlusCircle } from "lucide-react" import { ExternalLink, PlusCircle } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useState } from "react" 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 = [ export const ISSUE_TEMPLATES = [
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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