mirror of
				https://github.com/walkxcode/dashboard-icons.git
				synced 2025-10-31 16:57:58 +01:00 
			
		
		
		
	styling
This commit is contained in:
		| @@ -58,7 +58,7 @@ | ||||
|  | ||||
| 	--animate-marquee: marquee var(--duration) infinite linear; | ||||
| 	--animate-marquee-vertical: marquee-vertical var(--duration) linear infinite; | ||||
| } | ||||
| 	--animate-aurora: aurora 8s ease-in-out infinite alternate; | ||||
|  | ||||
| 	@keyframes accordion-down { | ||||
| 		from { | ||||
| @@ -68,7 +68,7 @@ | ||||
| 			height: var(--radix-accordion-content-height); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	@keyframes accordion-up { | ||||
| 		from { | ||||
| 			height: var(--radix-accordion-content-height); | ||||
| @@ -77,23 +77,55 @@ | ||||
| 			height: 0; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
|  | ||||
| 	@keyframes marquee { | ||||
| 	from { | ||||
| 		transform: translateX(0);} | ||||
| 	to { | ||||
| 		transform: translateX(calc(-100% - var(--gap)));}} | ||||
| 		transform: translateX(calc(-100% - var(--gap)));} | ||||
| 	} | ||||
|  | ||||
| 	@keyframes marquee-vertical { | ||||
| 	from { | ||||
| 		transform: translateY(0);} | ||||
| 	to { | ||||
| 		transform: translateY(calc(-100% - var(--gap)));}} | ||||
| 		transform: translateY(calc(-100% - var(--gap)));} | ||||
| 	} | ||||
|  | ||||
| 	@keyframes aurora { | ||||
|     0% { | ||||
|       background-position: 0% 50%; | ||||
|       transform: rotate(-5deg) scale(0.9); | ||||
|     } | ||||
|     25% { | ||||
|       background-position: 50% 100%; | ||||
|       transform: rotate(5deg) scale(1.1); | ||||
|     } | ||||
|     50% { | ||||
|       background-position: 100% 50%; | ||||
|       transform: rotate(-3deg) scale(0.95); | ||||
|     } | ||||
|     75% { | ||||
|       background-position: 50% 0%; | ||||
|       transform: rotate(3deg) scale(1.05); | ||||
|     } | ||||
|     100% { | ||||
|       background-position: 0% 50%; | ||||
|       transform: rotate(-5deg) scale(0.9); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| 	--animate-shiny-text: shiny-text 8s infinite | ||||
|  | ||||
| ; | ||||
|   @keyframes shiny-text { | ||||
|   0%, 90%, 100% { | ||||
|     background-position: calc(-100% - var(--shiny-width)) 0;} | ||||
|   30%, 60% { | ||||
|     background-position: calc(100% + var(--shiny-width)) 0;}}} | ||||
|  | ||||
| :root { | ||||
| 	--radius: 0.75rem; | ||||
| 	--radius: 0.4rem; | ||||
|  | ||||
|   --background: oklch(0.99 0 0); | ||||
|   --foreground: oklch(0.32 0 0); | ||||
| @@ -107,14 +139,14 @@ | ||||
|   --secondary-foreground: oklch(0.45 0.03 256.80); | ||||
|   --muted: oklch(0.98 0.00 247.84); | ||||
|   --muted-foreground: oklch(0.55 0.02 264.36); | ||||
|   --accent: oklch(0.74 0.09 251.62); | ||||
|   --accent-foreground: oklch(0.38 0.14 265.52); | ||||
| 	--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.00 0 0); | ||||
|   --border: oklch(0.90 0.01 247.88); | ||||
|  | ||||
| 	--input: oklch(0.92 0.004 286.32); | ||||
| 	--ring: oklch(0.637 0.237 25.331); | ||||
|  | ||||
| 	--chart-1: oklch(0.646 0.222 41.116); | ||||
| 	--chart-2: oklch(0.6 0.118 184.704); | ||||
| 	--chart-3: oklch(0.398 0.07 227.392); | ||||
| @@ -140,13 +172,13 @@ | ||||
| } | ||||
|  | ||||
| .dark { | ||||
|   --background: oklch(0.26 0.03 262.67); | ||||
|   --background: oklch(.141 .005 285.823); | ||||
|   --foreground: oklch(0.92 0 0); | ||||
|   --card: oklch(0.31 0.03 268.64); | ||||
|   --card-foreground: oklch(0.92 0 0); | ||||
|   --popover: oklch(0.29 0.02 268.40); | ||||
|   --popover-foreground: oklch(0.92 0 0); | ||||
|   --primary: oklch(0.64 0.17 36.44); | ||||
|   --primary: oklch(0.67 0.20 23.80); | ||||
|   --primary-foreground: oklch(1.00 0 0); | ||||
|   --secondary: oklch(0.31 0.03 266.71); | ||||
|   --secondary-foreground: oklch(0.92 0 0); | ||||
|   | ||||
| @@ -106,41 +106,6 @@ export default async function IconPage({ params }: { params: Promise<{ icon: str | ||||
|  | ||||
| 	return ( | ||||
| 		<div className="relative isolate overflow-hidden pt-14"> | ||||
| 			{/* Background glow effect */} | ||||
| 			<div className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" aria-hidden="true"> | ||||
| 				<div | ||||
| 					className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-rose-400/50 to-red-300/50 dark:from-red-600/70 dark:to-red-900/70 opacity-50 dark:opacity-60 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]" | ||||
| 					style={{ | ||||
| 						clipPath: | ||||
| 							"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)", | ||||
| 					}} | ||||
| 				/> | ||||
| 			</div> | ||||
|  | ||||
| 			{/* Secondary glow for additional effect */} | ||||
| 			<div | ||||
| 				className="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]" | ||||
| 				aria-hidden="true" | ||||
| 			> | ||||
| 				<div | ||||
| 					className="relative left-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-red-300/50 to-rose-500/50 dark:from-red-700/50 dark:to-red-500/50 opacity-50 dark:opacity-50 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]" | ||||
| 					style={{ | ||||
| 						clipPath: | ||||
| 							"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)", | ||||
| 					}} | ||||
| 				/> | ||||
| 			</div> | ||||
|  | ||||
| 			{/* Additional central glow */} | ||||
| 			<div className="absolute inset-x-0 top-1/2 -z-10 transform-gpu overflow-hidden blur-3xl" aria-hidden="true"> | ||||
| 				<div | ||||
| 					className="relative left-[calc(50%)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-rose-300/50 to-red-400/50 dark:from-red-800/40 dark:to-red-600/40 opacity-50 dark:opacity-40 sm:w-[50.1875rem]" | ||||
| 					style={{ | ||||
| 						clipPath: | ||||
| 							"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)", | ||||
| 					}} | ||||
| 				/> | ||||
| 			</div> | ||||
| 			{/* Existing Icon Details */} | ||||
| 			<IconDetails icon={icon} iconData={originalIconData} authorData={authorData} /> | ||||
| 		</div> | ||||
|   | ||||
| @@ -1,302 +1,259 @@ | ||||
| "use client" | ||||
| "use client"; | ||||
|  | ||||
| import { IconSubmissionContent } from "@/components/icon-submission-form" | ||||
| import { Badge } from "@/components/ui/badge" | ||||
| import { Button } from "@/components/ui/button" | ||||
| import { IconSubmissionContent } from "@/components/icon-submission-form"; | ||||
| import { MagicCard } from "@/components/magicui/magic-card"; | ||||
| import { Badge } from "@/components/ui/badge"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { | ||||
| 	DropdownMenu, | ||||
| 	DropdownMenuCheckboxItem, | ||||
| 	DropdownMenuContent, | ||||
| 	DropdownMenuItem, | ||||
| 	DropdownMenuLabel, | ||||
| 	DropdownMenuPortal, | ||||
| 	DropdownMenuRadioGroup, | ||||
| 	DropdownMenuRadioItem, | ||||
| 	DropdownMenuSeparator, | ||||
| 	DropdownMenuSub, | ||||
| 	DropdownMenuSubContent, | ||||
| 	DropdownMenuSubTrigger, | ||||
| 	DropdownMenuTrigger, | ||||
| } from "@/components/ui/dropdown-menu" | ||||
| import { Input } from "@/components/ui/input" | ||||
| import { Separator } from "@/components/ui/separator" | ||||
| import { BASE_URL } from "@/constants" | ||||
| import { fuzzySearch } from "@/lib/utils" | ||||
| import { cn } from "@/lib/utils" | ||||
| import type { Icon, IconSearchProps, IconWithName } from "@/types/icons" | ||||
| import { motion } from "framer-motion" | ||||
| import { useInView } from "framer-motion" | ||||
| import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react" | ||||
| import { useTheme } from "next-themes" | ||||
| import Image from "next/image" | ||||
| import Link from "next/link" | ||||
| import { usePathname, useRouter, useSearchParams } from "next/navigation" | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react" | ||||
| } from "@/components/ui/dropdown-menu"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { Separator } from "@/components/ui/separator"; | ||||
| import { BASE_URL } from "@/constants"; | ||||
| import type { Icon, IconSearchProps } from "@/types/icons"; | ||||
| import { useInView } from "framer-motion"; | ||||
| import { | ||||
| 	ArrowDownAZ, | ||||
| 	ArrowUpZA, | ||||
| 	Calendar, | ||||
| 	Filter, | ||||
| 	Search, | ||||
| 	SortAsc, | ||||
| 	X, | ||||
| } from "lucide-react"; | ||||
| import { useTheme } from "next-themes"; | ||||
| import Image from "next/image"; | ||||
| import Link from "next/link"; | ||||
| import { usePathname, useRouter, useSearchParams } from "next/navigation"; | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
|  | ||||
| type SortOption = "relevance" | "alphabetical-asc" | "alphabetical-desc" | "newest" | ||||
| type SortOption = | ||||
| 	| "relevance" | ||||
| 	| "alphabetical-asc" | ||||
| 	| "alphabetical-desc" | ||||
| 	| "newest"; | ||||
|  | ||||
| export function IconSearch({ icons }: IconSearchProps) { | ||||
| 	const searchParams = useSearchParams() | ||||
| 	const initialQuery = searchParams.get("q") | ||||
| 	const initialCategories = searchParams.getAll("category") | ||||
| 	const initialSort = (searchParams.get("sort") as SortOption) || "relevance" | ||||
| 	const router = useRouter() | ||||
| 	const pathname = usePathname() | ||||
| 	const [searchQuery, setSearchQuery] = useState(initialQuery ?? "") | ||||
| 	const [selectedCategories, setSelectedCategories] = useState<string[]>(initialCategories ?? []) | ||||
| 	const [sortOption, setSortOption] = useState<SortOption>(initialSort) | ||||
| 	const timeoutRef = useRef<NodeJS.Timeout | null>(null) | ||||
| 	const { resolvedTheme } = useTheme() | ||||
| 	const searchParams = useSearchParams(); | ||||
| 	const initialQuery = searchParams.get("q"); | ||||
| 	const initialCategories = searchParams.getAll("category"); | ||||
| 	const initialSort = (searchParams.get("sort") as SortOption) || "relevance"; | ||||
| 	const router = useRouter(); | ||||
| 	const pathname = usePathname(); | ||||
| 	const [searchQuery, setSearchQuery] = useState(initialQuery ?? ""); | ||||
| 	const [selectedCategories, setSelectedCategories] = useState<string[]>( | ||||
| 		initialCategories ?? [], | ||||
| 	); | ||||
| 	const [sortOption, setSortOption] = useState<SortOption>(initialSort); | ||||
| 	const timeoutRef = useRef<NodeJS.Timeout | null>(null); | ||||
| 	const { resolvedTheme } = useTheme(); | ||||
|  | ||||
| 	// Extract all unique categories | ||||
| 	const allCategories = useMemo(() => { | ||||
| 		const categories = new Set<string>() | ||||
| 		const categories = new Set<string>(); | ||||
| 		for (const icon of icons) { | ||||
| 			for (const category of icon.data.categories) { | ||||
| 				categories.add(category) | ||||
| 				categories.add(category); | ||||
| 			} | ||||
| 		} | ||||
| 		return Array.from(categories).sort() | ||||
| 	}, [icons]) | ||||
| 		return Array.from(categories).sort(); | ||||
| 	}, [icons]); | ||||
|  | ||||
| 	// Define filterIconsByQueryAndCategory BEFORE it's used in useState | ||||
| 	const filterIconsByQueryAndCategories = useCallback((iconList: typeof icons, query: string, categories: string[], sort: SortOption) => { | ||||
| 		let filtered = iconList | ||||
|  | ||||
| 		// Apply category filters (if any are selected) | ||||
| 		if (categories.length > 0) { | ||||
| 			filtered = filtered.filter(({ data }) => | ||||
| 				// Check if the icon has at least one of the selected categories | ||||
| 				data.categories.some((cat) => categories.some((selectedCat) => cat.toLowerCase() === selectedCat.toLowerCase())), | ||||
| 			) | ||||
| 		} | ||||
|  | ||||
| 		// Create a scored version of icons for relevance and other sorting | ||||
| 		let scoredIcons: { icon: (typeof filtered)[0]; score: number; matchedAlias?: string }[] = [] | ||||
|  | ||||
| 		// Apply search query filter and calculate scores | ||||
| 		if (query.trim()) { | ||||
| 			scoredIcons = filtered.map((icon) => { | ||||
| 				// Calculate scores for different fields | ||||
| 				const nameScore = fuzzySearch(icon.name, query) * 1.5 // Give more weight to name matches | ||||
|  | ||||
| 				// Find matching alias if any | ||||
| 				let matchedAlias: string | undefined = undefined | ||||
| 				let maxAliasScore = 0 | ||||
|  | ||||
| 				// Check each alias for a match | ||||
| 				if (icon.data.aliases.length > 0) { | ||||
| 					for (const alias of icon.data.aliases) { | ||||
| 						const aliasScore = fuzzySearch(alias, query) * 1.4 | ||||
| 						if (aliasScore > maxAliasScore) { | ||||
| 							maxAliasScore = aliasScore | ||||
| 							matchedAlias = alias | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// Get max score from categories | ||||
| 				const categoryScore = | ||||
| 					icon.data.categories.length > 0 ? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query))) : 0 | ||||
|  | ||||
| 				// Use the highest score | ||||
| 				const score = Math.max(nameScore, maxAliasScore, categoryScore) | ||||
|  | ||||
| 				return { | ||||
| 					icon, | ||||
| 					score, | ||||
| 					matchedAlias: score === maxAliasScore && maxAliasScore > 0 ? matchedAlias : undefined, | ||||
| 				} | ||||
| 			}) | ||||
|  | ||||
| 			// Filter icons with a minimum score | ||||
| 			scoredIcons = scoredIcons.filter((item) => item.score > 0.2) // Minimum threshold | ||||
|  | ||||
| 			// If we're using relevance sorting, apply it now | ||||
| 			if (sort === "relevance") { | ||||
| 				// Sort by score | ||||
| 				scoredIcons.sort((a, b) => b.score - a.score) | ||||
| 				return scoredIcons.map((item) => item.icon) | ||||
| 	// Simple filter function using substring matching | ||||
| 	const filterIcons = useCallback( | ||||
| 		(query: string, categories: string[], sort: SortOption) => { | ||||
| 			// First filter by categories if any are selected | ||||
| 			let filtered = icons; | ||||
| 			if (categories.length > 0) { | ||||
| 				filtered = filtered.filter(({ data }) => | ||||
| 					data.categories.some((cat) => | ||||
| 						categories.some( | ||||
| 							(selectedCat) => cat.toLowerCase() === selectedCat.toLowerCase(), | ||||
| 						), | ||||
| 					), | ||||
| 				); | ||||
| 			} | ||||
|  | ||||
| 			// Otherwise, we'll use the filtered results for other sorting methods | ||||
| 			filtered = scoredIcons.map((item) => item.icon) | ||||
| 		} | ||||
| 			// Then filter by search query | ||||
| 			if (query.trim()) { | ||||
| 				const q = query.toLowerCase(); | ||||
| 				filtered = filtered.filter(({ name, data }) => { | ||||
| 					if (name.toLowerCase().includes(q)) return true; | ||||
| 					if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true; | ||||
| 					if (data.categories.some((category) => category.toLowerCase().includes(q))) return true; | ||||
| 					return false; | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 		// Apply sorting if not by relevance (which was already handled above) | ||||
| 		if (sort === "alphabetical-asc") { | ||||
| 			return filtered.sort((a, b) => a.name.localeCompare(b.name)) | ||||
| 		} | ||||
| 		if (sort === "alphabetical-desc") { | ||||
| 			return filtered.sort((a, b) => b.name.localeCompare(a.name)) | ||||
| 		} | ||||
| 		if (sort === "newest") { | ||||
| 			return filtered.sort((a, b) => { | ||||
| 				return new Date(b.data.update.timestamp).getTime() - new Date(a.data.update.timestamp).getTime() | ||||
| 			}) | ||||
| 		} | ||||
| 			// Apply sorting | ||||
| 			if (sort === "alphabetical-asc") { | ||||
| 				return filtered.sort((a, b) => a.name.localeCompare(b.name)); | ||||
| 			} | ||||
| 			if (sort === "alphabetical-desc") { | ||||
| 				return filtered.sort((a, b) => b.name.localeCompare(a.name)); | ||||
| 			} | ||||
| 			if (sort === "newest") { | ||||
| 				return filtered.sort((a, b) => { | ||||
| 					return ( | ||||
| 						new Date(b.data.update.timestamp).getTime() - | ||||
| 						new Date(a.data.update.timestamp).getTime() | ||||
| 					); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 		// Default alphabetical sort if no query or sort option not recognized | ||||
| 		return filtered.sort((a, b) => a.name.localeCompare(b.name)) | ||||
| 	}, []) | ||||
| 			// Default sort (relevance or fallback to alphabetical) | ||||
| 			return filtered.sort((a, b) => a.name.localeCompare(b.name)); | ||||
| 		}, | ||||
| 		[icons], | ||||
| 	); | ||||
|  | ||||
| 	// Now use the function after it's been defined | ||||
| 	const [filteredIcons, setFilteredIcons] = useState(() => { | ||||
| 		return filterIconsByQueryAndCategories(icons, initialQuery ?? "", initialCategories ?? [], initialSort) | ||||
| 	}) | ||||
| 	// Find matched aliases for display purposes | ||||
| 	const matchedAliases = useMemo(() => { | ||||
| 		if (!searchQuery.trim()) return {}; | ||||
|  | ||||
| 	// Store matched aliases separately | ||||
| 	const [matchedAliases, setMatchedAliases] = useState<Record<string, string>>({}) | ||||
| 		const q = searchQuery.toLowerCase(); | ||||
| 		const matches: Record<string, string> = {}; | ||||
|  | ||||
| 		icons.forEach(({ name, data }) => { | ||||
| 			// If name doesn't match but an alias does, store the first matching alias | ||||
| 			if (!name.toLowerCase().includes(q)) { | ||||
| 				const matchingAlias = data.aliases.find((alias) => | ||||
| 					alias.toLowerCase().includes(q) | ||||
| 				); | ||||
| 				if (matchingAlias) { | ||||
| 					matches[name] = matchingAlias; | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		return matches; | ||||
| 	}, [icons, searchQuery]); | ||||
|  | ||||
| 	// Use useMemo for filtered icons | ||||
| 	const filteredIcons = useMemo(() => { | ||||
| 		return filterIcons(searchQuery, selectedCategories, sortOption); | ||||
| 	}, [filterIcons, searchQuery, selectedCategories, sortOption]); | ||||
|  | ||||
| 	const updateResults = useCallback( | ||||
| 		(query: string, categories: string[], sort: SortOption) => { | ||||
| 			// Clear existing matched aliases | ||||
| 			setMatchedAliases({}) | ||||
|  | ||||
| 			// If we have a query, capture any matched aliases before filtering | ||||
| 			if (query.trim()) { | ||||
| 				const newMatches: Record<string, string> = {} | ||||
| 				const scoredResults = icons.map((icon) => { | ||||
| 					// Check for alias matches | ||||
| 					let bestAliasScore = 0 | ||||
| 					let bestAlias = "" | ||||
| 					for (const alias of icon.data.aliases) { | ||||
| 						const score = fuzzySearch(alias, query) * 1.4 | ||||
| 						if (score > bestAliasScore && score > 0.3) { | ||||
| 							// Only consider strong matches | ||||
| 							bestAliasScore = score | ||||
| 							bestAlias = alias | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					// If the name match is weaker than alias match, store the alias | ||||
| 					const nameScore = fuzzySearch(icon.name, query) * 1.5 | ||||
| 					if (bestAliasScore > nameScore && bestAliasScore > 0.3) { | ||||
| 						newMatches[icon.name] = bestAlias | ||||
| 					} | ||||
|  | ||||
| 					return { icon } | ||||
| 				}) | ||||
|  | ||||
| 				// Update the matched aliases | ||||
| 				setMatchedAliases(newMatches) | ||||
| 			} | ||||
|  | ||||
| 			// Continue with normal filtering | ||||
| 			setFilteredIcons(filterIconsByQueryAndCategories(icons, query, categories, sort)) | ||||
| 			const params = new URLSearchParams() | ||||
| 			if (query) params.set("q", query) | ||||
| 			const params = new URLSearchParams(); | ||||
| 			if (query) params.set("q", query); | ||||
|  | ||||
| 			// Clear existing category params and add new ones | ||||
| 			for (const category of categories) { | ||||
| 				params.append("category", category) | ||||
| 				params.append("category", category); | ||||
| 			} | ||||
|  | ||||
| 			// Add sort parameter if not default | ||||
| 			if (sort !== "relevance" || initialSort !== "relevance") { | ||||
| 				params.set("sort", sort) | ||||
| 				params.set("sort", sort); | ||||
| 			} | ||||
|  | ||||
| 			const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname | ||||
| 			router.push(newUrl, { scroll: false }) | ||||
| 			const newUrl = params.toString() | ||||
| 				? `${pathname}?${params.toString()}` | ||||
| 				: pathname; | ||||
| 			router.push(newUrl, { scroll: false }); | ||||
| 		}, | ||||
| 		[filterIconsByQueryAndCategories, icons, pathname, router, initialSort], | ||||
| 	) | ||||
| 		[pathname, router, initialSort], | ||||
| 	); | ||||
|  | ||||
| 	const handleSearch = useCallback( | ||||
| 		(query: string) => { | ||||
| 			setSearchQuery(query) | ||||
| 			setSearchQuery(query); | ||||
| 			if (timeoutRef.current) { | ||||
| 				clearTimeout(timeoutRef.current) | ||||
| 				clearTimeout(timeoutRef.current); | ||||
| 			} | ||||
| 			timeoutRef.current = setTimeout(() => { | ||||
| 				updateResults(query, selectedCategories, sortOption) | ||||
| 			}, 100) | ||||
| 				updateResults(query, selectedCategories, sortOption); | ||||
| 			}, 200); // Changed from 100ms to 200ms | ||||
| 		}, | ||||
| 		[updateResults, selectedCategories, sortOption], | ||||
| 	) | ||||
| 	); | ||||
|  | ||||
| 	const handleCategoryChange = useCallback( | ||||
| 		(category: string) => { | ||||
| 			let newCategories: string[] | ||||
| 			let newCategories: string[]; | ||||
|  | ||||
| 			if (selectedCategories.includes(category)) { | ||||
| 				// Remove the category if it's already selected | ||||
| 				newCategories = selectedCategories.filter((c) => c !== category) | ||||
| 				newCategories = selectedCategories.filter((c) => c !== category); | ||||
| 			} else { | ||||
| 				// Add the category if it's not selected | ||||
| 				newCategories = [...selectedCategories, category] | ||||
| 				newCategories = [...selectedCategories, category]; | ||||
| 			} | ||||
|  | ||||
| 			setSelectedCategories(newCategories) | ||||
| 			updateResults(searchQuery, newCategories, sortOption) | ||||
| 			setSelectedCategories(newCategories); | ||||
| 			updateResults(searchQuery, newCategories, sortOption); | ||||
| 		}, | ||||
| 		[updateResults, searchQuery, selectedCategories, sortOption], | ||||
| 	) | ||||
| 	); | ||||
|  | ||||
| 	const handleSortChange = useCallback( | ||||
| 		(sort: SortOption) => { | ||||
| 			setSortOption(sort) | ||||
| 			updateResults(searchQuery, selectedCategories, sort) | ||||
| 			setSortOption(sort); | ||||
| 			updateResults(searchQuery, selectedCategories, sort); | ||||
| 		}, | ||||
| 		[updateResults, searchQuery, selectedCategories], | ||||
| 	) | ||||
| 	); | ||||
|  | ||||
| 	const clearFilters = useCallback(() => { | ||||
| 		setSearchQuery("") | ||||
| 		setSelectedCategories([]) | ||||
| 		setSortOption("relevance") | ||||
| 		updateResults("", [], "relevance") | ||||
| 	}, [updateResults]) | ||||
| 		setSearchQuery(""); | ||||
| 		setSelectedCategories([]); | ||||
| 		setSortOption("relevance"); | ||||
| 		updateResults("", [], "relevance"); | ||||
| 	}, [updateResults]); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		return () => { | ||||
| 			if (timeoutRef.current) { | ||||
| 				clearTimeout(timeoutRef.current) | ||||
| 				clearTimeout(timeoutRef.current); | ||||
| 			} | ||||
| 		} | ||||
| 	}, []) | ||||
| 		}; | ||||
| 	}, []); | ||||
|  | ||||
| 	if (!searchParams) return null | ||||
| 	if (!searchParams) return null; | ||||
|  | ||||
| 	const getSortLabel = (sort: SortOption) => { | ||||
| 		switch (sort) { | ||||
| 			case "relevance": | ||||
| 				return "Best match" | ||||
| 				return "Best match"; | ||||
| 			case "alphabetical-asc": | ||||
| 				return "A to Z" | ||||
| 				return "A to Z"; | ||||
| 			case "alphabetical-desc": | ||||
| 				return "Z to A" | ||||
| 				return "Z to A"; | ||||
| 			case "newest": | ||||
| 				return "Newest first" | ||||
| 				return "Newest first"; | ||||
| 			default: | ||||
| 				return "Sort" | ||||
| 				return "Sort"; | ||||
| 		} | ||||
| 	} | ||||
| 	}; | ||||
|  | ||||
| 	const getSortIcon = (sort: SortOption) => { | ||||
| 		switch (sort) { | ||||
| 			case "relevance": | ||||
| 				return <Search className="h-4 w-4" /> | ||||
| 				return <Search className="h-4 w-4" />; | ||||
| 			case "alphabetical-asc": | ||||
| 				return <ArrowDownAZ className="h-4 w-4" /> | ||||
| 				return <ArrowDownAZ className="h-4 w-4" />; | ||||
| 			case "alphabetical-desc": | ||||
| 				return <ArrowUpZA className="h-4 w-4" /> | ||||
| 				return <ArrowUpZA className="h-4 w-4" />; | ||||
| 			case "newest": | ||||
| 				return <Calendar className="h-4 w-4" /> | ||||
| 				return <Calendar className="h-4 w-4" />; | ||||
| 			default: | ||||
| 				return <SortAsc className="h-4 w-4" /> | ||||
| 				return <SortAsc className="h-4 w-4" />; | ||||
| 		} | ||||
| 	} | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<> | ||||
| 			<motion.div | ||||
| 				className="space-y-4 w-full" | ||||
| 				initial={{ opacity: 0, y: 10 }} | ||||
| 				animate={{ opacity: 1, y: 0 }} | ||||
| 				transition={{ duration: 0.5 }} | ||||
| 			> | ||||
| 			<div className="space-y-4 w-full"> | ||||
| 				{/* Search input */} | ||||
| 				<div className="relative w-full"> | ||||
| 					<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground transition-all duration-300"> | ||||
| @@ -305,7 +262,7 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 					<Input | ||||
| 						type="search" | ||||
| 						placeholder="Search icons by name, alias, or category..." | ||||
| 						className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base bg-background/80 dark:bg-background/90 border-border shadow-sm hover:border-rose-500/50 focus-visible:ring-rose-500/20" | ||||
| 						className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base   border-border shadow-sm" | ||||
| 						value={searchQuery} | ||||
| 						onChange={(e) => handleSearch(e.target.value)} | ||||
| 					/> | ||||
| @@ -319,7 +276,7 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 							<Button | ||||
| 								variant="outline" | ||||
| 								size="sm" | ||||
| 								className="flex-1 sm:flex-none cursor-pointer bg-background/80 dark:bg-background/90 border-border shadow-sm hover:bg-rose-500/10 dark:hover:bg-rose-900/20 hover:border-rose-500" | ||||
| 								className="flex-1 sm:flex-none cursor-pointer   border-border shadow-sm0/10 " | ||||
| 							> | ||||
| 								<Filter className="h-4 w-4 mr-2" /> | ||||
| 								<span>Filter</span> | ||||
| @@ -331,7 +288,9 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 							</Button> | ||||
| 						</DropdownMenuTrigger> | ||||
| 						<DropdownMenuContent align="start" className="w-64 sm:w-56"> | ||||
| 							<DropdownMenuLabel className="font-semibold">Categories</DropdownMenuLabel> | ||||
| 							<DropdownMenuLabel className="font-semibold"> | ||||
| 								Categories | ||||
| 							</DropdownMenuLabel> | ||||
| 							<DropdownMenuSeparator /> | ||||
|  | ||||
| 							<div className="max-h-[40vh] overflow-y-auto p-1"> | ||||
| @@ -342,7 +301,9 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 										onCheckedChange={() => handleCategoryChange(category)} | ||||
| 										className="cursor-pointer capitalize" | ||||
| 									> | ||||
| 										{category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} | ||||
| 										{category | ||||
| 											.replace(/-/g, " ") | ||||
| 											.replace(/\b\w/g, (c) => c.toUpperCase())} | ||||
| 									</DropdownMenuCheckboxItem> | ||||
| 								))} | ||||
| 							</div> | ||||
| @@ -352,10 +313,10 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 									<DropdownMenuSeparator /> | ||||
| 									<DropdownMenuItem | ||||
| 										onClick={() => { | ||||
| 											setSelectedCategories([]) | ||||
| 											updateResults(searchQuery, [], sortOption) | ||||
| 											setSelectedCategories([]); | ||||
| 											updateResults(searchQuery, [], sortOption); | ||||
| 										}} | ||||
| 										className="cursor-pointer text-rose-500 focus:text-rose-500 focus:bg-rose-50 dark:focus:bg-rose-950/20" | ||||
| 										className="cursor-pointer  focus: focus:bg-rose-50 dark:focus:bg-rose-950/20" | ||||
| 									> | ||||
| 										Clear all filters | ||||
| 									</DropdownMenuItem> | ||||
| @@ -370,27 +331,44 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 							<Button | ||||
| 								variant="outline" | ||||
| 								size="sm" | ||||
| 								className="flex-1 sm:flex-none cursor-pointer bg-background/80 dark:bg-background/90 border-border shadow-sm hover:bg-rose-500/10 dark:hover:bg-rose-900/20 hover:border-rose-500" | ||||
| 								className="flex-1 sm:flex-none cursor-pointer   border-border shadow-sm0/10  hover:border-rose-500" | ||||
| 							> | ||||
| 								{getSortIcon(sortOption)} | ||||
| 								<span className="ml-2">{getSortLabel(sortOption)}</span> | ||||
| 							</Button> | ||||
| 						</DropdownMenuTrigger> | ||||
| 						<DropdownMenuContent align="start" className="w-56"> | ||||
| 							<DropdownMenuLabel className="font-semibold">Sort By</DropdownMenuLabel> | ||||
| 							<DropdownMenuLabel className="font-semibold"> | ||||
| 								Sort By | ||||
| 							</DropdownMenuLabel> | ||||
| 							<DropdownMenuSeparator /> | ||||
| 							<DropdownMenuRadioGroup value={sortOption} onValueChange={(value) => handleSortChange(value as SortOption)}> | ||||
| 								<DropdownMenuRadioItem value="relevance" className="cursor-pointer"> | ||||
| 							<DropdownMenuRadioGroup | ||||
| 								value={sortOption} | ||||
| 								onValueChange={(value) => handleSortChange(value as SortOption)} | ||||
| 							> | ||||
| 								<DropdownMenuRadioItem | ||||
| 									value="relevance" | ||||
| 									className="cursor-pointer" | ||||
| 								> | ||||
| 									<Search className="h-4 w-4 mr-2" /> | ||||
| 									Best match | ||||
| 								</DropdownMenuRadioItem> | ||||
| 								<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer"> | ||||
| 								<DropdownMenuRadioItem | ||||
| 									value="alphabetical-asc" | ||||
| 									className="cursor-pointer" | ||||
| 								> | ||||
| 									<ArrowDownAZ className="h-4 w-4 mr-2" />A to Z | ||||
| 								</DropdownMenuRadioItem> | ||||
| 								<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer"> | ||||
| 								<DropdownMenuRadioItem | ||||
| 									value="alphabetical-desc" | ||||
| 									className="cursor-pointer" | ||||
| 								> | ||||
| 									<ArrowUpZA className="h-4 w-4 mr-2" />Z to A | ||||
| 								</DropdownMenuRadioItem> | ||||
| 								<DropdownMenuRadioItem value="newest" className="cursor-pointer"> | ||||
| 								<DropdownMenuRadioItem | ||||
| 									value="newest" | ||||
| 									className="cursor-pointer" | ||||
| 								> | ||||
| 									<Calendar className="h-4 w-4 mr-2" /> | ||||
| 									Newest first | ||||
| 								</DropdownMenuRadioItem> | ||||
| @@ -399,12 +377,14 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 					</DropdownMenu> | ||||
|  | ||||
| 					{/* Clear all button */} | ||||
| 					{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && ( | ||||
| 					{(searchQuery || | ||||
| 						selectedCategories.length > 0 || | ||||
| 						sortOption !== "relevance") && ( | ||||
| 						<Button | ||||
| 							variant="outline" | ||||
| 							size="sm" | ||||
| 							onClick={clearFilters} | ||||
| 							className="flex-1 sm:flex-none cursor-pointer bg-background/80 dark:bg-background/90 hover:bg-rose-500/10 dark:hover:bg-rose-900/20 border-rose-500/20" | ||||
| 							className="flex-1 sm:flex-none cursor-pointer    border-rose-500/20" | ||||
| 						> | ||||
| 							<X className="h-4 w-4 mr-2" /> | ||||
| 							<span>Clear all</span> | ||||
| @@ -421,9 +401,11 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 								<Badge | ||||
| 									key={category} | ||||
| 									variant="secondary" | ||||
| 									className="flex items-center gap-1 pl-2 pr-1 bg-rose-500/10 dark:bg-rose-900/20 text-rose-700 dark:text-rose-300 border border-rose-500/20" | ||||
| 									className="flex items-center gap-1 pl-2 pr-1" | ||||
| 								> | ||||
| 									{category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} | ||||
| 									{category | ||||
| 										.replace(/-/g, " ") | ||||
| 										.replace(/\b\w/g, (c) => c.toUpperCase())} | ||||
| 									<Button | ||||
| 										variant="ghost" | ||||
| 										size="sm" | ||||
| @@ -440,10 +422,10 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 							variant="ghost" | ||||
| 							size="sm" | ||||
| 							onClick={() => { | ||||
| 								setSelectedCategories([]) | ||||
| 								updateResults(searchQuery, [], sortOption) | ||||
| 								setSelectedCategories([]); | ||||
| 								updateResults(searchQuery, [], sortOption); | ||||
| 							}} | ||||
| 							className="text-xs h-7 px-2 text-rose-500 hover:text-rose-600 hover:bg-rose-500/10 cursor-pointer" | ||||
| 							className="text-xs h-7 px-2  0/10 cursor-pointer" | ||||
| 						> | ||||
| 							Clear all | ||||
| 						</Button> | ||||
| @@ -451,17 +433,14 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 				)} | ||||
|  | ||||
| 				<Separator className="my-2" /> | ||||
| 			</motion.div> | ||||
| 			</div> | ||||
|  | ||||
| 			{filteredIcons.length === 0 ? ( | ||||
| 				<motion.div | ||||
| 					className="flex flex-col gap-8 py-12 max-w-2xl mx-auto" | ||||
| 					initial={{ opacity: 0 }} | ||||
| 					animate={{ opacity: 1 }} | ||||
| 					transition={{ duration: 0.5, delay: 0.2 }} | ||||
| 				> | ||||
| 				<div className="flex flex-col gap-8 py-12 max-w-2xl mx-auto"> | ||||
| 					<div className="text-center"> | ||||
| 						<h2 className="text-3xl sm:text-5xl font-semibold">We don't have this one...yet!</h2> | ||||
| 						<h2 className="text-3xl sm:text-5xl font-semibold"> | ||||
| 							We don't have this one...yet! | ||||
| 						</h2> | ||||
| 						<p className="mt-4 text-muted-foreground"> | ||||
| 							{searchQuery && selectedCategories.length > 0 | ||||
| 								? `No icons found matching "${searchQuery}" with the selected filters.` | ||||
| @@ -471,17 +450,15 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 										? "No icons found with the selected filters." | ||||
| 										: "No icons found matching your criteria."} | ||||
| 						</p> | ||||
| 						<Button variant="outline" className="mt-4 cursor-pointer" onClick={clearFilters}> | ||||
| 							Clear all filters | ||||
| 						</Button> | ||||
| 					</div> | ||||
| 					<IconSubmissionContent /> | ||||
| 				</motion.div> | ||||
| 				</div> | ||||
| 			) : ( | ||||
| 				<> | ||||
| 					<div className="flex justify-between items-center pb-2"> | ||||
| 						<p className="text-sm text-muted-foreground"> | ||||
| 							Found {filteredIcons.length} icon{filteredIcons.length !== 1 ? "s" : ""}. | ||||
| 							Found {filteredIcons.length} icon | ||||
| 							{filteredIcons.length !== 1 ? "s" : ""}. | ||||
| 						</p> | ||||
| 						<div className="flex items-center gap-1 text-xs text-muted-foreground"> | ||||
| 							{getSortIcon(sortOption)} | ||||
| @@ -490,30 +467,35 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 					</div> | ||||
| 					<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-2"> | ||||
| 						{filteredIcons.map(({ name, data }) => ( | ||||
| 							<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name] || null} /> | ||||
| 							<IconCard | ||||
| 								key={name} | ||||
| 								name={name} | ||||
| 								data={data} | ||||
| 								matchedAlias={matchedAliases[name] || null} | ||||
| 							/> | ||||
| 						))} | ||||
| 					</div> | ||||
| 				</> | ||||
| 			)} | ||||
| 		</> | ||||
| 	) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| function IconCard({ | ||||
| 	name, | ||||
| 	data, | ||||
| 	data: iconData, | ||||
| 	matchedAlias, | ||||
| }: { | ||||
| 	name: string | ||||
| 	data: Icon | ||||
| 	matchedAlias?: string | null | ||||
| 	name: string; | ||||
| 	data: Icon; | ||||
| 	matchedAlias?: string | null; | ||||
| }) { | ||||
| 	const ref = useRef(null) | ||||
| 	const ref = useRef(null); | ||||
| 	const isInView = useInView(ref, { | ||||
| 		once: false, | ||||
| 		amount: 0.2, | ||||
| 		margin: "100px 0px", | ||||
| 	}) | ||||
| 	}); | ||||
|  | ||||
| 	const variants = { | ||||
| 		hidden: { opacity: 0, y: 20, scale: 0.95 }, | ||||
| @@ -529,40 +511,33 @@ function IconCard({ | ||||
| 			scale: 0.98, | ||||
| 			transition: { duration: 0.3, ease: [0.25, 0.1, 0.25, 1.0] }, | ||||
| 		}, | ||||
| 	} | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<motion.div | ||||
| 			ref={ref} | ||||
| 			initial="hidden" | ||||
| 			animate={isInView ? "visible" : "hidden"} | ||||
| 			exit="exit" | ||||
| 			variants={variants} | ||||
| 			className="will-change-transform" | ||||
| 		> | ||||
| 		<MagicCard className="rounded-md shadow-md"> | ||||
| 			<Link | ||||
| 				prefetch={false} | ||||
| 				href={`/icons/${name}`} | ||||
| 				className="group flex flex-col items-center p-3 sm:p-4 rounded-lg border border-border bg-background hover:border-rose-500 hover:bg-rose-500/10 dark:hover:bg-rose-900/30 dark:hover:border-rose-500 transition-all duration-300 hover:shadow-lg hover:shadow-rose-500/5 relative overflow-hidden cursor-pointer" | ||||
| 				className="group flex flex-col items-center p-3 sm:p-4 cursor-pointer" | ||||
| 			> | ||||
| 				<div className="absolute inset-0 bg-gradient-to-br from-rose-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" /> | ||||
|  | ||||
| 				<div className="relative h-12 w-12 sm:h-16 sm:w-16 mb-2"> | ||||
| 					<Image | ||||
| 						src={`${BASE_URL}/${data.base}/${name}.${data.base}`} | ||||
| 						src={`${BASE_URL}/${iconData.base}/${name}.${iconData.base}`} | ||||
| 						alt={`${name} icon`} | ||||
| 						fill | ||||
| 						className="object-contain p-1 group-hover:scale-110 transition-transform duration-300" | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<span className="text-xs sm:text-sm text-center truncate w-full capitalize group-hover:text-rose-600 dark:group-hover:text-rose-400 transition-colors duration-200 font-medium"> | ||||
| 				<span className="text-xs sm:text-sm text-center truncate w-full capitalize group- dark:group-hover:text-rose-400 transition-colors duration-200 font-medium"> | ||||
| 					{name.replace(/-/g, " ")} | ||||
| 				</span> | ||||
|  | ||||
| 				{matchedAlias && ( | ||||
| 					<span className="text-[10px] text-center truncate w-full text-rose-500 dark:text-rose-400 mt-1">Alias: {matchedAlias}</span> | ||||
| 					<span className="text-[10px] text-center truncate w-full mt-1"> | ||||
| 						Alias: {matchedAlias} | ||||
| 					</span> | ||||
| 				)} | ||||
| 			</Link> | ||||
| 		</motion.div> | ||||
| 	) | ||||
| 		</MagicCard> | ||||
| 	); | ||||
| } | ||||
|   | ||||
| @@ -79,7 +79,7 @@ export function Carbon() { | ||||
| 					} | ||||
| 				`} | ||||
| 			</style> | ||||
| 			<div className="bg-background shadow-xl flex flex-col m-4 space-y-2 rounded-l-lg"> | ||||
| 			<div className=" shadow-xl flex flex-col m-4 space-y-2 rounded-l-lg"> | ||||
| 				<div ref={ref} className="carbon-outer" /> | ||||
| 			</div> | ||||
| 		</> | ||||
|   | ||||
| @@ -35,7 +35,7 @@ export function Footer() { | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<footer className="border-t py-4 bg-background relative overflow-hidden"> | ||||
| 		<footer className="border-t py-4  relative overflow-hidden"> | ||||
| 			<div className="absolute inset-0 bg-gradient-to-r from-rose-500/[0.03] via-transparent to-rose-500/[0.03]" /> | ||||
|  | ||||
| 			<div className="container mx-auto px-4 md:px-6 relative z-10"> | ||||
| @@ -55,13 +55,13 @@ export function Footer() { | ||||
| 						<div className="flex flex-col gap-2"> | ||||
| 							<Link | ||||
| 								href="/" | ||||
| 								className="text-sm text-muted-foreground hover:text-rose-500 transition-colors duration-200 flex items-center w-fit" | ||||
| 								className="text-sm text-muted-foreground hover: transition-colors duration-200 flex items-center w-fit" | ||||
| 							> | ||||
| 								<span>Home</span> | ||||
| 							</Link> | ||||
| 							<Link | ||||
| 								href="/icons" | ||||
| 								className="text-sm text-muted-foreground hover:text-rose-500 transition-colors duration-200 flex items-center w-fit" | ||||
| 								className="text-sm text-muted-foreground hover: transition-colors duration-200 flex items-center w-fit" | ||||
| 							> | ||||
| 								<span>Icons</span> | ||||
| 							</Link> | ||||
| @@ -69,10 +69,10 @@ export function Footer() { | ||||
| 								href={REPO_PATH} | ||||
| 								target="_blank" | ||||
| 								rel="noopener noreferrer" | ||||
| 								className="text-sm text-muted-foreground hover:text-rose-500 transition-colors duration-200 flex items-center gap-1.5 w-fit group" | ||||
| 								className="text-sm text-muted-foreground hover: transition-colors duration-200 flex items-center gap-1.5 w-fit group" | ||||
| 							> | ||||
| 								<span>GitHub</span> | ||||
| 								<Github className="h-3.5 w-3.5 group-hover:text-rose-500 transition-colors duration-200 flex-shrink-0 self-center" /> | ||||
| 								<Github className="h-3.5 w-3.5 group-hover: transition-colors duration-200 flex-shrink-0 self-center" /> | ||||
| 							</Link> | ||||
| 						</div> | ||||
| 					</div> | ||||
| @@ -85,7 +85,7 @@ export function Footer() { | ||||
| 						transition={{ duration: 0.5, delay: 0.2 }} | ||||
| 					> | ||||
| 						<h3 className="font-bold text-lg text-foreground/90">Community</h3> | ||||
| 						<div className="text-sm text-muted-foreground flex flex-wrap items-center gap-1.5 leading-relaxed"> | ||||
| 						<div className="text-sm flex flex-wrap items-center gap-1.5 leading-relaxed"> | ||||
| 							Made with{" "} | ||||
| 							<div className="relative inline-block"> | ||||
| 								<motion.div | ||||
| @@ -105,7 +105,7 @@ export function Footer() { | ||||
| 										}} | ||||
| 									> | ||||
| 										<Heart | ||||
| 											className="h-3.5 w-3.5 text-rose-500 flex-shrink-0 hover:scale-125 transition-all duration-200" | ||||
| 											className="h-3.5 w-3.5  flex-shrink-0 hover:scale-125 transition-all duration-200" | ||||
| 											fill={isHeartFilled ? "#f43f5e" : "none"} | ||||
| 											strokeWidth={isHeartFilled ? 1.5 : 2} | ||||
| 										/> | ||||
| @@ -133,7 +133,7 @@ export function Footer() { | ||||
| 												className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none" | ||||
| 											> | ||||
| 												<Heart | ||||
| 													className={`h-2 w-2 ${i < 3 ? "text-rose-300" : i < 6 ? "text-rose-400" : "text-rose-500"}`} | ||||
| 													className={`h-2 w-2 ${i < 3 ? "text-rose-300" : i < 6 ? "text-rose-400" : ""}`} | ||||
| 												/> | ||||
| 											</motion.div> | ||||
| 										))} | ||||
| @@ -183,7 +183,7 @@ export function Footer() { | ||||
| 												className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none" | ||||
| 											> | ||||
| 												<Heart | ||||
| 													className="h-2 w-2 text-rose-500" | ||||
| 													className="h-2 w-2 " | ||||
| 													fill="#f43f5e" | ||||
| 												/> | ||||
| 											</motion.div> | ||||
| @@ -197,7 +197,7 @@ export function Footer() { | ||||
| 							href="https://github.com/homarr-labs" | ||||
| 							target="_blank" | ||||
| 							rel="noopener noreferrer" | ||||
| 							className="text-sm text-rose-500 hover:text-rose-600 transition-colors duration-200 flex items-center gap-1.5 w-fit mt-1 group" | ||||
| 							className="text-sm   transition-colors duration-200 flex items-center gap-1.5 w-fit mt-1 group" | ||||
| 						> | ||||
| 							<span>Contribute to this project</span> | ||||
| 							<ExternalLink className="h-3.5 w-3.5 flex-shrink-0" /> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ export function HeaderNav() { | ||||
| 			<Link | ||||
| 				href="/" | ||||
| 				className={cn( | ||||
| 					"text-sm font-medium transition-colors hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer", | ||||
| 					"text-sm font-medium transition-colors  dark:hover:text-rose-400 cursor-pointer", | ||||
| 					pathname === "/" && "text-primary font-semibold", | ||||
| 				)} | ||||
| 			> | ||||
| @@ -23,7 +23,7 @@ export function HeaderNav() { | ||||
| 				prefetch | ||||
| 				href="/icons" | ||||
| 				className={cn( | ||||
| 					"text-sm font-medium transition-colors hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer", | ||||
| 					"text-sm font-medium transition-colors  dark:hover:text-rose-400 cursor-pointer", | ||||
| 					isIconsActive && "text-primary font-semibold", | ||||
| 				)} | ||||
| 			> | ||||
|   | ||||
| @@ -6,13 +6,12 @@ import { REPO_PATH } from "@/constants"; | ||||
| import { getIconsArray } from "@/lib/api"; | ||||
| import type { IconWithName } from "@/types/icons"; | ||||
| import { motion } from "framer-motion"; | ||||
| import { Github, Menu, Search } from "lucide-react"; | ||||
| import { Github, 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"; | ||||
| import { Sheet, SheetContent, SheetTrigger } from "./ui/sheet"; | ||||
| import { | ||||
| 	Tooltip, | ||||
| 	TooltipContent, | ||||
| @@ -47,7 +46,7 @@ export function Header() { | ||||
|  | ||||
| 	return ( | ||||
| 		<motion.header | ||||
| 			className="border-b sticky top-0 z-50 bg-background/95 backdrop-blur-md border-border/50" | ||||
| 			className="border-b sticky top-0 z-50  backdrop-blur-md border-border/50" | ||||
| 			initial={{ y: -20, opacity: 0 }} | ||||
| 			animate={{ y: 0, opacity: 1 }} | ||||
| 			transition={{ duration: 0.3, ease: "easeOut" }} | ||||
| @@ -58,7 +57,7 @@ export function Header() { | ||||
| 						href="/" | ||||
| 						className="text-lg md:text-xl font-bold group hidden md:block" | ||||
| 					> | ||||
| 						<span className="transition-colors duration-300 group-hover:text-rose-500"> | ||||
| 						<span className="transition-colors duration-300 group-hover:"> | ||||
| 							Dashboard Icons | ||||
| 						</span> | ||||
| 					</Link> | ||||
| @@ -71,7 +70,7 @@ export function Header() { | ||||
| 					<div className="hidden md:block"> | ||||
| 						<Button | ||||
| 							variant="outline" | ||||
| 							className="gap-2 cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 hover:border-rose-500 dark:hover:border-rose-500 transition-all duration-300" | ||||
| 							className="gap-2 cursor-pointer0/10 dark:hover:bg-rose-900/30   transition-all duration-300" | ||||
| 							onClick={openCommandMenu} | ||||
| 						> | ||||
| 							<Search className="h-4 w-4 transition-all duration-300" /> | ||||
| @@ -87,7 +86,7 @@ export function Header() { | ||||
| 						<Button | ||||
| 							variant="ghost" | ||||
| 							size="icon" | ||||
| 							className="rounded-lg cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 transition-all duration-300 focus:ring-2 focus:ring-rose-500/20" | ||||
| 							className="rounded-lg cursor-pointer0/10 dark:hover:bg-rose-900/30 transition-all duration-300 focus:ring-2 focus:ring-rose-500/20" | ||||
| 							onClick={openCommandMenu} | ||||
| 						> | ||||
| 							<Search className="h-5 w-5 transition-all duration-300" /> | ||||
| @@ -103,11 +102,11 @@ export function Header() { | ||||
| 									<Button | ||||
| 										variant="ghost" | ||||
| 										size="icon" | ||||
| 										className="rounded-lg cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 transition-all duration-300 focus:ring-2 focus:ring-rose-500/20" | ||||
| 										className="rounded-lg cursor-pointer0/10 dark:hover:bg-rose-900/30 transition-all duration-300 focus:ring-2 focus:ring-rose-500/20" | ||||
| 										asChild | ||||
| 									> | ||||
| 										<Link href={REPO_PATH} target="_blank" className="group"> | ||||
| 											<Github className="h-5 w-5 group-hover:text-rose-500 transition-all duration-300" /> | ||||
| 											<Github className="h-5 w-5 group-hover: transition-all duration-300" /> | ||||
| 											<span className="sr-only">View on GitHub</span> | ||||
| 										</Link> | ||||
| 									</Button> | ||||
|   | ||||
| @@ -1,17 +1,20 @@ | ||||
| "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 { motion, useAnimation, useInView } from "framer-motion" | ||||
| import { Github, Heart, Search, Sparkles } 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 { motion, useAnimation, useInView } from "framer-motion"; | ||||
| import { Github, Heart, Search } from "lucide-react"; | ||||
| import Link from "next/link"; | ||||
| import { useEffect, useRef, useState } from "react"; | ||||
| import { AnimatedShinyText } from "./magicui/animated-shiny-text"; | ||||
| import { AuroraText } from "./magicui/aurora-text"; | ||||
| import { InteractiveHoverButton } from "./magicui/interactive-hover-button"; | ||||
|  | ||||
| interface IconCardProps { | ||||
| 	name: string | ||||
| 	imageUrl: string | ||||
| 	name: string; | ||||
| 	imageUrl: string; | ||||
| } | ||||
|  | ||||
| function IconCard({ name, imageUrl }: IconCardProps) { | ||||
| @@ -20,9 +23,11 @@ function IconCard({ name, imageUrl }: IconCardProps) { | ||||
| 			<div className="w-16 h-16 flex items-center justify-center"> | ||||
| 				<img src={imageUrl} alt={name} className="max-w-full max-h-full" /> | ||||
| 			</div> | ||||
| 			<p className="text-sm text-center text-muted-foreground group-hover:text-foreground transition-colors">{name}</p> | ||||
| 			<p className="text-sm text-center text-muted-foreground group-hover:text-foreground transition-colors"> | ||||
| 				{name} | ||||
| 			</p> | ||||
| 		</Card> | ||||
| 	) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| function ElegantShape({ | ||||
| @@ -35,28 +40,28 @@ function ElegantShape({ | ||||
| 	mobileWidth, | ||||
| 	mobileHeight, | ||||
| }: { | ||||
| 	className?: string | ||||
| 	delay?: number | ||||
| 	width?: number | ||||
| 	height?: number | ||||
| 	rotate?: number | ||||
| 	gradient?: string | ||||
| 	mobileWidth?: number | ||||
| 	mobileHeight?: number | ||||
| 	className?: string; | ||||
| 	delay?: number; | ||||
| 	width?: number; | ||||
| 	height?: number; | ||||
| 	rotate?: number; | ||||
| 	gradient?: string; | ||||
| 	mobileWidth?: number; | ||||
| 	mobileHeight?: number; | ||||
| }) { | ||||
| 	const controls = useAnimation() | ||||
| 	const [isMobile, setIsMobile] = useState(false) | ||||
| 	const ref = useRef(null) | ||||
| 	const isInView = useInView(ref, { once: true, amount: 0.1 }) | ||||
| 	const controls = useAnimation(); | ||||
| 	const [isMobile, setIsMobile] = useState(false); | ||||
| 	const ref = useRef(null); | ||||
| 	const isInView = useInView(ref, { once: true, amount: 0.1 }); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		const checkMobile = () => { | ||||
| 			setIsMobile(window.innerWidth < 768) | ||||
| 		} | ||||
| 		checkMobile() | ||||
| 		window.addEventListener("resize", checkMobile) | ||||
| 		return () => window.removeEventListener("resize", checkMobile) | ||||
| 	}, []) | ||||
| 			setIsMobile(window.innerWidth < 768); | ||||
| 		}; | ||||
| 		checkMobile(); | ||||
| 		window.addEventListener("resize", checkMobile); | ||||
| 		return () => window.removeEventListener("resize", checkMobile); | ||||
| 	}, []); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		if (isInView) { | ||||
| @@ -73,9 +78,9 @@ function ElegantShape({ | ||||
| 					ease: [0.23, 0.86, 0.39, 0.96], | ||||
| 					opacity: { duration: 1.2 }, | ||||
| 				}, | ||||
| 			}) | ||||
| 			}); | ||||
| 		} | ||||
| 	}, [controls, delay, isInView, rotate]) | ||||
| 	}, [controls, delay, isInView, rotate]); | ||||
|  | ||||
| 	return ( | ||||
| 		<motion.div | ||||
| @@ -117,27 +122,14 @@ function ElegantShape({ | ||||
| 				/> | ||||
| 			</motion.div> | ||||
| 		</motion.div> | ||||
| 	) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| export function HeroSection({ totalIcons }: { totalIcons: number }) { | ||||
| 	const [searchQuery, setSearchQuery] = useState("") | ||||
|  | ||||
| 	const fadeUpVariants = { | ||||
| 		hidden: { opacity: 0, y: 30 }, | ||||
| 		visible: (i: number) => ({ | ||||
| 			opacity: 1, | ||||
| 			y: 0, | ||||
| 			transition: { | ||||
| 				duration: 1, | ||||
| 				delay: 0.5 + i * 0.2, | ||||
| 				ease: [0.25, 0.4, 0.25, 1], | ||||
| 			}, | ||||
| 		}), | ||||
| 	} | ||||
| 	const [searchQuery, setSearchQuery] = useState(""); | ||||
|  | ||||
| 	return ( | ||||
| 		<div className="relative pt-20 md:pt-40 pb-10 md:pb-20 w-full flex items-center justify-center overflow-hidden bg-background"> | ||||
| 		<div className="relative my-20 w-full flex items-center justify-center overflow-hidden"> | ||||
| 			<div className="absolute inset-0 bg-gradient-to-br from-rose-500/[0.1] via-transparent to-rose-500/[0.1] blur-3xl" /> | ||||
|  | ||||
| 			<div className="absolute inset-0 overflow-hidden pointer-events-none"> | ||||
| @@ -199,101 +191,61 @@ export function HeroSection({ totalIcons }: { totalIcons: number }) { | ||||
|  | ||||
| 			<div className="relative z-10 container mx-auto px-4 md:px-6"> | ||||
| 				<div className="max-w-4xl mx-auto text-center flex flex-col gap-4"> | ||||
| 					<Link prefetch href="https://github.com/homarr-labs" target="_blank" rel="noopener noreferrer" className="mx-auto"> | ||||
| 						<motion.div variants={fadeUpVariants} custom={0} initial="hidden" animate="visible" whileHover="hover"> | ||||
| 							<motion.div | ||||
| 								className="overflow-hidden rounded-xl relative" | ||||
| 								variants={{ | ||||
| 									hover: { | ||||
| 										scale: 1.05, | ||||
| 										boxShadow: "0 10px 20px rgba(244, 63, 94, 0.15)", | ||||
| 									}, | ||||
| 								}} | ||||
| 								transition={{ | ||||
| 									type: "spring", | ||||
| 									stiffness: 400, | ||||
| 									damping: 17, | ||||
| 								}} | ||||
| 							> | ||||
| 								<motion.div | ||||
| 									className="absolute inset-0 bg-gradient-to-r from-rose-500/10 via-fuchsia-500/10 to-rose-500/10 opacity-0 z-0" | ||||
| 									variants={{ | ||||
| 										hover: { | ||||
| 											opacity: 1, | ||||
| 											backgroundPosition: ["0% 0%", "100% 100%"], | ||||
| 										}, | ||||
| 									}} | ||||
| 									transition={{ | ||||
| 										duration: 1.5, | ||||
| 										ease: "easeInOut", | ||||
| 										backgroundPosition: { | ||||
| 											repeat: Number.POSITIVE_INFINITY, | ||||
| 											duration: 3, | ||||
| 										}, | ||||
| 									}} | ||||
| 								/> | ||||
| 								<Card className="p-2 px-4 flex flex-row items-center gap-2 border-2 border-rose-200 dark:border-rose-900/30 z-10 relative glass-effect"> | ||||
| 										<Heart color="red" fill="red" className="h-4 w-4 motion-safe:motion-preset-pulse-sm" /> | ||||
| 									<span className="text-sm text-foreground/70 tracking-wide">Made with love by Homarr Labs</span> | ||||
| 								</Card> | ||||
| 							</motion.div> | ||||
| 						</motion.div> | ||||
| 					<Link | ||||
| 						prefetch | ||||
| 						href="https://github.com/homarr-labs" | ||||
| 						target="_blank" | ||||
| 						rel="noopener noreferrer" | ||||
| 						className="mx-auto" | ||||
| 					> | ||||
| 						<Card className="group p-2 px-4 flex flex-row items-center gap-2 border-2  z-10 relative glass-effect motion-safe:motion-preset-slide-up motion-duration-1500 hover:scale-105 transition-all duration-300"> | ||||
| 							<Heart | ||||
| 								// Filled when hovered | ||||
| 								className="h-4 w-4 text-primary group-hover:fill-primary transition-all duration-300" | ||||
| 							/> | ||||
| 							<span className="text-sm text-foreground/70 tracking-wide"> | ||||
| 								Made with love by Homarr Labs | ||||
| 							</span> | ||||
| 						</Card> | ||||
| 					</Link> | ||||
|  | ||||
| 					<motion.div custom={1} variants={fadeUpVariants} initial="hidden" animate="visible"> | ||||
| 						<h1 className="text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight"> | ||||
| 							<span className="text-foreground relative inline-block"> | ||||
| 								Your definitive source for | ||||
| 								<motion.span | ||||
| 									className="absolute -right-4 sm:-right-6 md:-right-8 -top-3 sm:-top-4 md:-top-6 text-rose-500" | ||||
| 									animate={{ rotate: [0, 15, 0] }} | ||||
| 									transition={{ duration: 5, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }} | ||||
| 								> | ||||
| 									<Sparkles className="h-4 w-4 sm:h-5 sm:w-5 md:h-8 md:w-8" /> | ||||
| 								</motion.span> | ||||
| 							</span> | ||||
| 							<br /> | ||||
| 							<motion.span | ||||
| 								className="bg-clip-text text-transparent bg-gradient-to-r from-rose-500 via-fuchsia-500 to-rose-500 bg-[length:200%] relative inline-block" | ||||
| 								animate={{ | ||||
| 									backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"], | ||||
| 									textShadow: ["0 0 10px rgba(244,63,94,0.3)", "0 0 20px rgba(244,63,94,0.5)", "0 0 10px rgba(244,63,94,0.3)"], | ||||
| 								}} | ||||
| 								transition={{ | ||||
| 									duration: 8, | ||||
| 									repeat: Number.POSITIVE_INFINITY, | ||||
| 									ease: "easeInOut", | ||||
| 								}} | ||||
| 							> | ||||
| 								dashboard icons. | ||||
| 							</motion.span> | ||||
| 						</h1> | ||||
| 					</motion.div> | ||||
| 					<h1 className="text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight motion-safe:motion-preset-slide-up motion-duration-1500"> | ||||
| 						Your definitive source for | ||||
| 						<br /> | ||||
| 						<AuroraText colors={["#FA5352", "#FA5352", "orange"]}> | ||||
| 							dashboard icons | ||||
| 						</AuroraText> | ||||
| 					</h1> | ||||
|  | ||||
| 					<motion.div custom={2} variants={fadeUpVariants} initial="hidden" animate="visible"> | ||||
| 					<motion.div | ||||
| 						custom={2} | ||||
| 						className="motion-safe:motion-preset-slide-up motion-duration-1500" | ||||
| 					> | ||||
| 						<p className="text-sm sm:text-base md:text-xl text-muted-foreground mb-6 md:mb-8 leading-relaxed font-light tracking-wide max-w-2xl mx-auto px-4"> | ||||
| 							A collection of <span className="font-medium text-rose-500">{totalIcons}</span> curated icons for services, applications and | ||||
| 							tools, designed specifically for dashboards and app directories. | ||||
| 							A collection of <span className="font-medium ">{totalIcons}</span>{" "} | ||||
| 							curated icons for services, applications and tools, designed | ||||
| 							specifically for dashboards and app directories. | ||||
| 						</p> | ||||
| 					</motion.div> | ||||
|  | ||||
| 					<motion.div | ||||
| 						custom={3} | ||||
| 						variants={fadeUpVariants} | ||||
| 						initial="hidden" | ||||
| 						animate="visible" | ||||
| 						className="flex flex-col items-center gap-4 md:gap-6 mb-8 md:mb-12" | ||||
| 						className="flex flex-col items-center gap-4 md:gap-6 mb-8 md:mb-12 motion-safe:motion-preset-slide-up motion-duration-1500" | ||||
| 					> | ||||
| 						<form action="/icons" method="GET" className="relative w-full max-w-md group"> | ||||
| 						<form | ||||
| 							action="/icons" | ||||
| 							method="GET" | ||||
| 							className="relative w-full max-w-md group" | ||||
| 						> | ||||
| 							<Input | ||||
| 								name="q" | ||||
| 								type="search" | ||||
| 								placeholder={`Find any of ${totalIcons} icons by name or category...`} | ||||
| 								className="pl-10 h-10 md:h-12 rounded-lg border-muted-foreground/20 focus:border-rose-500 focus:ring-rose-500/20 transition-all bg-background/95 dark:bg-background/90 backdrop-blur-sm text-sm md:text-base" | ||||
| 								className="pl-10 h-10 md:h-12 rounded-lg border-muted-foreground/20  focus:ring-rose-500/20 transition-all   backdrop-blur-sm text-sm md:text-base" | ||||
| 								value={searchQuery} | ||||
| 								onChange={(e) => setSearchQuery(e.target.value)} | ||||
| 							/> | ||||
| 							<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 md:h-5 w-4 md:w-5 text-muted-foreground group-focus-within:text-rose-500 transition-all duration-300" /> | ||||
| 							<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 md:h-5 w-4 md:w-5 text-muted-foreground group-focus-within: transition-all duration-300" /> | ||||
| 							<motion.span | ||||
| 								className="absolute inset-0 -z-10 rounded-lg bg-rose-500/5 opacity-0 transition-opacity group-hover:opacity-100" | ||||
| 								initial={{ scale: 0.95 }} | ||||
| @@ -302,14 +254,12 @@ export function HeroSection({ totalIcons }: { totalIcons: number }) { | ||||
| 							/> | ||||
| 						</form> | ||||
| 						<div className="flex gap-3 md:gap-4 flex-wrap justify-center"> | ||||
| 							<Button variant="default" className="h-9 md:h-10 px-4 gap-2 bg-rose-500 hover:bg-rose-600 text-white" asChild> | ||||
| 								<Link href="/icons" className="flex items-center text-sm md:text-base"> | ||||
| 									Explore all icons | ||||
| 								</Link> | ||||
| 							</Button> | ||||
| 							<Link href="/icons"> | ||||
| 								<InteractiveHoverButton className="rounded-md">Explore icons</InteractiveHoverButton> | ||||
| 							</Link> | ||||
| 							<Button | ||||
| 								variant="outline" | ||||
| 								className="h-9 md:h-10 px-4 gap-2 border-rose-200 dark:border-rose-900/30 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:border-rose-300 dark:hover:border-rose-800 bg-background/95 dark:bg-background/90 backdrop-blur-sm" | ||||
| 								className="h-9 md:h-10 px-4 gap-2 backdrop-blur-sm" | ||||
| 								asChild | ||||
| 							> | ||||
| 								<Link | ||||
| @@ -329,5 +279,5 @@ export function HeroSection({ totalIcons }: { totalIcons: number }) { | ||||
|  | ||||
| 			<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-background/80 pointer-events-none" /> | ||||
| 		</div> | ||||
| 	) | ||||
| 	); | ||||
| } | ||||
|   | ||||
| @@ -29,7 +29,6 @@ import { | ||||
| 	PaletteIcon, | ||||
| 	Sun, | ||||
| } from "lucide-react"; | ||||
| import { useTheme } from "next-themes"; | ||||
| import Image from "next/image"; | ||||
| import Link from "next/link"; | ||||
| import { useCallback, useState } from "react"; | ||||
| @@ -190,22 +189,22 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 	) => { | ||||
| 		const variantName = | ||||
| 			theme && iconColorVariants?.[theme] ? iconColorVariants[theme] : iconName; | ||||
| 		const url = `${BASE_URL}/${format}/${variantName}.${format}`; | ||||
| 		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 ( | ||||
| 			<TooltipProvider key={variantKey} delayDuration={500}> | ||||
| 				<MagicCard> | ||||
| 					<div className="flex flex-col items-center rounded-lg p-4 border border-border shadow-sm hover:shadow-md transition-all"> | ||||
| 				<MagicCard className="rounded-md"> | ||||
| 					<div className="flex flex-col items-center p-4 transition-all"> | ||||
| 						<Tooltip> | ||||
| 							<TooltipTrigger asChild> | ||||
| 								<motion.div | ||||
| 									className="relative w-28 h-28 mb-3 cursor-pointer rounded-xl overflow-hidden group" | ||||
| 									whileHover={{ scale: 1.05 }} | ||||
| 									whileTap={{ scale: 0.95 }} | ||||
| 									onClick={(e) => handleCopy(url, variantKey, e)} | ||||
| 									onClick={(e) => handleCopy(imageUrl, variantKey, e)} | ||||
| 								> | ||||
| 									<div className="absolute inset-0 border-2 border-transparent group-hover:border-primary/20 rounded-xl z-10 transition-colors" /> | ||||
|  | ||||
| @@ -232,7 +231,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 									</motion.div> | ||||
|  | ||||
| 									<Image | ||||
| 										src={url} | ||||
| 										src={imageUrl} | ||||
| 										alt={`${iconName} in ${format} format${theme ? ` (${theme} theme)` : ""}`} | ||||
| 										fill | ||||
| 										className="object-contain p-4" | ||||
| @@ -254,7 +253,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 										size="icon" | ||||
| 										className="h-8 w-8 rounded-lg cursor-pointer" | ||||
| 										onClick={(e) => | ||||
| 											handleDownload(e, url, `${iconName}.${format}`) | ||||
| 											handleDownload(e, imageUrl, `${iconName}.${format}`) | ||||
| 										} | ||||
| 									> | ||||
| 										<Download className="w-4 h-4" /> | ||||
| @@ -271,7 +270,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 										variant="outline" | ||||
| 										size="icon" | ||||
| 										className="h-8 w-8 rounded-lg cursor-pointer" | ||||
| 										onClick={(e) => handleCopy(url, `btn-${variantKey}`, e)} | ||||
| 										onClick={(e) => handleCopy(imageUrl, `btn-${variantKey}`, e)} | ||||
| 									> | ||||
| 										{copiedVariants[`btn-${variantKey}`] ? ( | ||||
| 											<Check className="w-4 h-4 text-green-500" /> | ||||
| @@ -318,7 +317,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 			<div className="grid grid-cols-1 lg:grid-cols-4 gap-6"> | ||||
| 				{/* Left Column: Icon Info and Author */} | ||||
| 				<div className="lg:col-span-1"> | ||||
| 					<Card className="h-full backdrop-blur-sm  border shadow-lg"> | ||||
| 					<Card className="h-full backdrop-blur-sm bg-card/50 border shadow-lg"> | ||||
| 						<CardHeader className="pb-4"> | ||||
| 							<div className="flex flex-col items-center"> | ||||
| 								<div className="relative w-32 h-32  rounded-xl overflow-hidden border flex items-center justify-center p-3 "> | ||||
| @@ -386,7 +385,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 												<Link | ||||
| 													key={category} | ||||
| 													href={`/icons?category=${encodeURIComponent(category)}`} | ||||
| 													className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold hover:bg-rose-500/10 hover:border-rose-500/30 transition-colors cursor-pointer" | ||||
| 													className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold0/10  transition-colors cursor-pointer" | ||||
| 												> | ||||
| 													{category | ||||
| 														.split("-") | ||||
| @@ -410,7 +409,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
| 											{iconData.aliases.map((alias) => ( | ||||
| 												<span | ||||
| 													key={alias} | ||||
| 													className="inline-flex items-center rounded-full bg-rose-50 dark:bg-rose-950/20 border border-rose-100 dark:border-rose-900/30 px-2.5 py-1 text-xs text-rose-700 dark:text-rose-300" | ||||
| 													className="inline-flex items-center rounded-full bg-rose-50 dark:bg-rose-950/20 border   px-2.5 py-1 text-xs text-rose-700 dark:text-rose-300" | ||||
| 													title={`This icon can also be found by searching for "${alias}"`} | ||||
| 												> | ||||
| 													{alias} | ||||
| @@ -430,7 +429,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
|  | ||||
| 				{/* Middle Column: Icon variants */} | ||||
| 				<div className="lg:col-span-2"> | ||||
| 					<Card className="h-full backdrop-blur-sm bg-card shadow-lg"> | ||||
| 					<Card className="h-full backdrop-blur-sm bg-card/50 shadow-lg"> | ||||
| 						<CardHeader> | ||||
| 							<CardTitle>Icon variants</CardTitle> | ||||
| 							<CardDescription> | ||||
| @@ -476,7 +475,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) { | ||||
|  | ||||
| 				{/* Right Column: Technical details */} | ||||
| 				<div className="lg:col-span-1"> | ||||
| 					<Card className="h-full backdrop-blur-sm  border shadow-lg"> | ||||
| 					<Card className="h-full backdrop-blur-sm bg-card/50 border shadow-lg"> | ||||
| 						<CardHeader> | ||||
| 							<CardTitle>Technical details</CardTitle> | ||||
| 						</CardHeader> | ||||
|   | ||||
| @@ -49,11 +49,11 @@ export function IconSubmissionContent({ onClose }: { onClose?: () => void }) { | ||||
| 						<Button | ||||
| 							key={template.id} | ||||
| 							variant="outline" | ||||
| 							className="w-full flex flex-col items-start gap-1 h-auto p-4 text-left cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 hover:border-rose-500 dark:hover:border-rose-500 transition-all duration-300" | ||||
| 							className="w-full flex flex-col items-start gap-1 h-auto p-4 text-left cursor-pointer0/10 dark:hover:bg-rose-900/30   transition-all duration-300" | ||||
| 						> | ||||
| 							<div className="flex w-full items-center justify-between"> | ||||
| 								<span className="font-medium group-hover:text-rose-500 transition-all duration-300">{template.name}</span> | ||||
| 								<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-rose-500 transition-all duration-300" /> | ||||
| 								<span className="font-medium group-hover: transition-all duration-300">{template.name}</span> | ||||
| 								<ExternalLink className="h-4 w-4 text-muted-foreground group-hover: transition-all duration-300" /> | ||||
| 							</div> | ||||
| 							<span className="text-xs text-muted-foreground">{template.description}</span> | ||||
| 						</Button> | ||||
| @@ -71,7 +71,7 @@ export function IconSubmissionForm() { | ||||
| 			<DialogTrigger asChild> | ||||
| 				<Button | ||||
| 					variant="outline" | ||||
| 					className="hidden md:inline-flex cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 hover:border-rose-500 dark:hover:border-rose-500 transition-all duration-300" | ||||
| 					className="hidden md:inline-flex cursor-pointer0/10 dark:hover:bg-rose-900/30   transition-all duration-300" | ||||
| 				> | ||||
| 					<PlusCircle className="h-4 w-4 transition-all duration-300" /> Contribute new icon | ||||
| 				</Button> | ||||
|   | ||||
							
								
								
									
										39
									
								
								web/src/components/magicui/animated-shiny-text.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/src/components/magicui/animated-shiny-text.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import { ComponentPropsWithoutRef, CSSProperties, FC } from "react"; | ||||
|  | ||||
| import { cn } from "@/lib/utils"; | ||||
|  | ||||
| export interface AnimatedShinyTextProps | ||||
|   extends ComponentPropsWithoutRef<"span"> { | ||||
|   shimmerWidth?: number; | ||||
| } | ||||
|  | ||||
| export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({ | ||||
|   children, | ||||
|   className, | ||||
|   shimmerWidth = 100, | ||||
|   ...props | ||||
| }) => { | ||||
|   return ( | ||||
|     <span | ||||
|       style={ | ||||
|         { | ||||
|           "--shiny-width": `${shimmerWidth}px`, | ||||
|         } as CSSProperties | ||||
|       } | ||||
|       className={cn( | ||||
|         "mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70", | ||||
|  | ||||
|         // Shine effect | ||||
|         "animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]", | ||||
|  | ||||
|         // Shine gradient | ||||
|         "bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent  dark:via-white/80", | ||||
|  | ||||
|         className, | ||||
|       )} | ||||
|       {...props} | ||||
|     > | ||||
|       {children} | ||||
|     </span> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										43
									
								
								web/src/components/magicui/aurora-text.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/src/components/magicui/aurora-text.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| "use client"; | ||||
|  | ||||
| import React, { memo } from "react"; | ||||
|  | ||||
| interface AuroraTextProps { | ||||
|   children: React.ReactNode; | ||||
|   className?: string; | ||||
|   colors?: string[]; | ||||
|   speed?: number; | ||||
| } | ||||
|  | ||||
| export const AuroraText = memo( | ||||
|   ({ | ||||
|     children, | ||||
|     className = "", | ||||
|     colors = ["#FF0080", "#7928CA", "#0070F3", "#38bdf8"], | ||||
|     speed = 1, | ||||
|   }: AuroraTextProps) => { | ||||
|     const gradientStyle = { | ||||
|       backgroundImage: `linear-gradient(135deg, ${colors.join(", ")}, ${ | ||||
|         colors[0] | ||||
|       })`, | ||||
|       WebkitBackgroundClip: "text", | ||||
|       WebkitTextFillColor: "transparent", | ||||
|       animationDuration: `${10 / speed}s`, | ||||
|     }; | ||||
|  | ||||
|     return ( | ||||
|       <span className={`relative inline-block ${className}`}> | ||||
|         <span className="sr-only">{children}</span> | ||||
|         <span | ||||
|           className="relative animate-aurora bg-[length:200%_auto] bg-clip-text text-transparent" | ||||
|           style={gradientStyle} | ||||
|           aria-hidden="true" | ||||
|         > | ||||
|           {children} | ||||
|         </span> | ||||
|       </span> | ||||
|     ); | ||||
|   }, | ||||
| ); | ||||
|  | ||||
| AuroraText.displayName = "AuroraText"; | ||||
							
								
								
									
										35
									
								
								web/src/components/magicui/interactive-hover-button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/src/components/magicui/interactive-hover-button.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import React from "react"; | ||||
| import { ArrowRight } from "lucide-react"; | ||||
| import { cn } from "@/lib/utils"; | ||||
|  | ||||
| interface InteractiveHoverButtonProps | ||||
| 	extends React.ButtonHTMLAttributes<HTMLButtonElement> {} | ||||
|  | ||||
| export const InteractiveHoverButton = React.forwardRef< | ||||
| 	HTMLButtonElement, | ||||
| 	InteractiveHoverButtonProps | ||||
| >(({ children, className, ...props }, ref) => { | ||||
| 	return ( | ||||
| 		<button | ||||
| 			ref={ref} | ||||
| 			className={cn( | ||||
| 				"group relative w-auto cursor-pointer overflow-hidden rounded-full border bg-background p-2 px-6 text-center font-semibold", | ||||
| 				className, | ||||
| 			)} | ||||
| 			{...props} | ||||
| 		> | ||||
| 			<div className="flex items-center gap-2"> | ||||
| 				<div className="h-2 w-2 rounded-full bg-primary transition-all duration-300 group-hover:scale-[100.8]"></div> | ||||
| 				<span className="inline-block transition-all duration-300 group-hover:translate-x-12 group-hover:opacity-0"> | ||||
| 					{children} | ||||
| 				</span> | ||||
| 			</div> | ||||
| 			<div className="absolute top-0 z-10 flex h-full w-full translate-x-12 items-center justify-center gap-2 text-primary-foreground opacity-0 transition-all duration-300 group-hover:-translate-x-5 group-hover:opacity-100"> | ||||
| 				<span>{children}</span> | ||||
| 				<ArrowRight /> | ||||
| 			</div> | ||||
| 		</button> | ||||
| 	); | ||||
| }); | ||||
|  | ||||
| InteractiveHoverButton.displayName = "InteractiveHoverButton"; | ||||
| @@ -26,7 +26,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) { | ||||
| 	const secondRow = icons.slice(Math.ceil(icons.length / 2)); | ||||
|  | ||||
| 	return ( | ||||
| 		<div className="relative isolate overflow-hidden py-16 md:py-24"> | ||||
| 		<div className="relative isolate overflow-hidden my-8"> | ||||
| 			{/* Background glow */} | ||||
| 			<div | ||||
| 				className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" | ||||
| @@ -34,17 +34,17 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) { | ||||
| 			/> | ||||
|  | ||||
| 			<div className="mx-auto px-6 lg:px-8"> | ||||
| 				<div className="mx-auto max-w-2xl text-center mb-12"> | ||||
| 					<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500"> | ||||
| 				<div className="mx-auto max-w-2xl text-center my-4"> | ||||
| 					<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500  motion-safe:motion-preset-fade-lg motion-duration-2000"> | ||||
| 						Recently Added Icons | ||||
| 					</h2> | ||||
| 					<p className="mt-3 text-lg text-muted-foreground"> | ||||
| 						Check out the latest additions to our collection. | ||||
| 					</p> | ||||
| 				</div> | ||||
|  | ||||
| 				<div className="relative flex w-full flex-col items-center justify-center overflow-hidden"> | ||||
| 					<Marquee pauseOnHover className="[--duration:30s] [--gap:1em]"> | ||||
| 					<Marquee | ||||
| 						pauseOnHover | ||||
| 						className="[--duration:60s] [--gap:1em] motion-safe:motion-preset-slide-left-sm motion-duration-1000" | ||||
| 					> | ||||
| 						{firstRow.map(({ name, data }) => ( | ||||
| 							<RecentIconCard key={name} name={name} data={data} /> | ||||
| 						))} | ||||
| @@ -52,7 +52,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) { | ||||
| 					<Marquee | ||||
| 						reverse | ||||
| 						pauseOnHover | ||||
| 						className="[--duration:30s] [--gap:1rem] mt-6" | ||||
| 						className="[--duration:60s] [--gap:1rem] motion-safe:motion-preset-slide-right-sm motion-duration-1000" | ||||
| 					> | ||||
| 						{secondRow.map(({ name, data }) => ( | ||||
| 							<RecentIconCard key={name} name={name} data={data} /> | ||||
| @@ -65,7 +65,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) { | ||||
| 				<div className="mt-12 text-center"> | ||||
| 					<Link | ||||
| 						href="/icons" | ||||
| 						className="text-rose-500 dark:text-rose-400 hover:text-rose-600 dark:hover:text-rose-300 font-medium inline-flex items-center py-2 px-4 rounded-full border border-rose-200 dark:border-rose-800/30 hover:bg-rose-50 dark:hover:bg-rose-900/20 transition-all duration-200 group hover-lift soft-shadow" | ||||
| 						className="font-medium inline-flex items-center py-2 px-4 rounded-full border  transition-all duration-200 group hover-lift soft-shadow" | ||||
| 					> | ||||
| 						View complete collection | ||||
| 						<ArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" /> | ||||
| @@ -89,7 +89,7 @@ function RecentIconCard({ | ||||
| 			prefetch={false} | ||||
| 			href={`/icons/${name}`} | ||||
| 			className={cn( | ||||
| 				"flex flex-col items-center p-3 sm:p-4 rounded-xl border border-border bg-background/95 dark:bg-background/80", | ||||
| 				"flex flex-col items-center p-3 sm:p-4 rounded-xl border border-border", | ||||
| 				"transition-all duration-300 hover:shadow-lg hover:shadow-rose-500/5 relative overflow-hidden hover-lift", | ||||
| 				"w-36 mx-2", | ||||
| 			)} | ||||
| @@ -104,18 +104,18 @@ function RecentIconCard({ | ||||
| 					className="object-contain p-1 hover:scale-110 transition-transform duration-300" | ||||
| 				/> | ||||
| 			</div> | ||||
| 			<span className="text-xs sm:text-sm text-center truncate w-full capitalize hover:text-rose-600 dark:hover:text-rose-400 transition-colors duration-200 font-medium"> | ||||
| 			<span className="text-xs sm:text-sm text-center truncate w-full capitalize  dark:hover:text-rose-400 transition-colors duration-200 font-medium"> | ||||
| 				{name.replace(/-/g, " ")} | ||||
| 			</span> | ||||
| 			<div className="flex items-center justify-center mt-2 w-full"> | ||||
| 				<span className="text-[10px] sm:text-xs text-muted-foreground flex items-center whitespace-nowrap hover:text-rose-500/70 transition-colors duration-200"> | ||||
| 				<span className="text-[10px] sm:text-xs text-muted-foreground flex items-center whitespace-nowrap hover:/70 transition-colors duration-200"> | ||||
| 					<Clock className="w-3 h-3 mr-1.5 shrink-0" /> | ||||
| 					{formatIconDate(data.update.timestamp)} | ||||
| 				</span> | ||||
| 			</div> | ||||
|  | ||||
| 			<div className="absolute top-2 right-2 opacity-0 hover:opacity-100 transition-opacity duration-200"> | ||||
| 				<ExternalLink className="w-3 h-3 text-rose-500" /> | ||||
| 				<ExternalLink className="w-3 h-3 " /> | ||||
| 			</div> | ||||
| 		</Link> | ||||
| 	); | ||||
|   | ||||
| @@ -19,12 +19,12 @@ export function ThemeSwitcher() { | ||||
| 					<TooltipTrigger asChild> | ||||
| 						<DropdownMenuTrigger asChild> | ||||
| 							<Button | ||||
| 								className="rounded-md cursor-pointer hover:bg-rose-500/10 dark:hover:bg-rose-900/30 transition-colors duration-200 group" | ||||
| 								className="rounded-md cursor-pointer0/10 dark:hover:bg-rose-900/30 transition-colors duration-200 group" | ||||
| 								variant="ghost" | ||||
| 								size="icon" | ||||
| 							> | ||||
| 								<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0 group-hover:text-rose-500" /> | ||||
| 								<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 group-hover:text-rose-500" /> | ||||
| 								<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0 group-hover:" /> | ||||
| 								<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 group-hover:" /> | ||||
| 								<span className="sr-only">Toggle theme</span> | ||||
| 							</Button> | ||||
| 						</DropdownMenuTrigger> | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
| import { buttonVariants } from "@/components/ui/button" | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| function AlertDialog({ | ||||
|   ...props | ||||
| @@ -54,7 +54,7 @@ function AlertDialogContent({ | ||||
|       <AlertDialogPrimitive.Content | ||||
|         data-slot="alert-dialog-content" | ||||
|         className={cn( | ||||
|           "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", | ||||
|           " data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", | ||||
|           className | ||||
|         )} | ||||
|         {...props} | ||||
| @@ -143,15 +143,7 @@ function AlertDialogCancel({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   AlertDialog, | ||||
|   AlertDialogPortal, | ||||
|   AlertDialogOverlay, | ||||
|   AlertDialogTrigger, | ||||
|   AlertDialogContent, | ||||
|   AlertDialogHeader, | ||||
|   AlertDialogFooter, | ||||
|   AlertDialogTitle, | ||||
|   AlertDialogDescription, | ||||
|   AlertDialogAction, | ||||
|   AlertDialogCancel, | ||||
|     AlertDialog, AlertDialogAction, | ||||
|     AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										61
									
								
								web/src/components/ui/aurora-background.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								web/src/components/ui/aurora-background.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| "use client"; | ||||
| import { cn } from "@/lib/utils"; | ||||
| import React, { ReactNode } from "react"; | ||||
|  | ||||
| interface AuroraBackgroundProps extends React.HTMLProps<HTMLDivElement> { | ||||
|   children: ReactNode; | ||||
|   showRadialGradient?: boolean; | ||||
| } | ||||
|  | ||||
| export const AuroraBackground = ({ | ||||
|   className, | ||||
|   children, | ||||
|   showRadialGradient = true, | ||||
|   ...props | ||||
| }: AuroraBackgroundProps) => { | ||||
|   return ( | ||||
|     <main> | ||||
|       <div | ||||
|         className={cn( | ||||
|           "transition-bg relative flex h-[100vh] flex-col items-center justify-center bg-zinc-50 text-slate-950 dark:bg-zinc-900", | ||||
|           className, | ||||
|         )} | ||||
|         {...props} | ||||
|       > | ||||
|         <div | ||||
|           className="absolute inset-0 overflow-hidden" | ||||
|           style={ | ||||
|             { | ||||
|               "--aurora": | ||||
|                 "repeating-linear-gradient(100deg,#3b82f6_10%,#a5b4fc_15%,#93c5fd_20%,#ddd6fe_25%,#60a5fa_30%)", | ||||
|               "--dark-gradient": | ||||
|                 "repeating-linear-gradient(100deg,#000_0%,#000_7%,transparent_10%,transparent_12%,#000_16%)", | ||||
|               "--white-gradient": | ||||
|                 "repeating-linear-gradient(100deg,#fff_0%,#fff_7%,transparent_10%,transparent_12%,#fff_16%)", | ||||
|  | ||||
|               "--blue-300": "#93c5fd", | ||||
|               "--blue-400": "#60a5fa", | ||||
|               "--blue-500": "#3b82f6", | ||||
|               "--indigo-300": "#a5b4fc", | ||||
|               "--violet-200": "#ddd6fe", | ||||
|               "--black": "#000", | ||||
|               "--white": "#fff", | ||||
|               "--transparent": "transparent", | ||||
|             } as React.CSSProperties | ||||
|           } | ||||
|         > | ||||
|           <div | ||||
|             //   I'm sorry but this is what peak developer performance looks like // trigger warning | ||||
|             className={cn( | ||||
|               `after:animate-aurora pointer-events-none absolute -inset-[10px] [background-image:var(--white-gradient),var(--aurora)] [background-size:300%,_200%] [background-position:50%_50%,50%_50%] opacity-50 blur-[10px] invert filter will-change-transform [--aurora:repeating-linear-gradient(100deg,var(--blue-500)_10%,var(--indigo-300)_15%,var(--blue-300)_20%,var(--violet-200)_25%,var(--blue-400)_30%)] [--dark-gradient:repeating-linear-gradient(100deg,var(--black)_0%,var(--black)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--black)_16%)] [--white-gradient:repeating-linear-gradient(100deg,var(--white)_0%,var(--white)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--white)_16%)] after:absolute after:inset-0 after:[background-image:var(--white-gradient),var(--aurora)] after:[background-size:200%,_100%] after:[background-attachment:fixed] after:mix-blend-difference after:content-[""] dark:[background-image:var(--dark-gradient),var(--aurora)] dark:invert-0 after:dark:[background-image:var(--dark-gradient),var(--aurora)]`, | ||||
|  | ||||
|               showRadialGradient && | ||||
|                 `[mask-image:radial-gradient(ellipse_at_100%_0%,black_10%,var(--transparent)_70%)]`, | ||||
|             )} | ||||
|           ></div> | ||||
|         </div> | ||||
|         {children} | ||||
|       </div> | ||||
|     </main> | ||||
|   ); | ||||
| }; | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as React from "react" | ||||
| import { Slot } from "@radix-ui/react-slot" | ||||
| import { cva, type VariantProps } from "class-variance-authority" | ||||
| import { type VariantProps, cva } from "class-variance-authority" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -14,7 +14,7 @@ const buttonVariants = cva( | ||||
|         destructive: | ||||
|           "bg-destructive text-white shadow-sm hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 hover:shadow-md", | ||||
|         outline: | ||||
|           "border bg-background shadow-sm hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 hover:shadow-md", | ||||
|           "border  shadow-sm hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 hover:shadow-md", | ||||
|         secondary: | ||||
|           "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 hover:shadow-md", | ||||
|         ghost: | ||||
|   | ||||
| @@ -171,7 +171,7 @@ function ChartTooltipContent({ | ||||
|   return ( | ||||
|     <div | ||||
|       className={cn( | ||||
|         "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl", | ||||
|         "border-border/50  grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl", | ||||
|         className | ||||
|       )} | ||||
|     > | ||||
| @@ -340,9 +340,9 @@ function getPayloadConfigFromPayload( | ||||
| } | ||||
|  | ||||
| export { | ||||
| 	ChartContainer, ChartLegend, | ||||
| 	ChartLegendContent, | ||||
| 	ChartStyle, ChartTooltip, | ||||
| 	ChartTooltipContent | ||||
|     ChartContainer, ChartLegend, | ||||
|     ChartLegendContent, | ||||
|     ChartStyle, ChartTooltip, | ||||
|     ChartTooltipContent | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as DialogPrimitive from "@radix-ui/react-dialog" | ||||
| import { XIcon } from "lucide-react" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -57,7 +57,7 @@ function DialogContent({ | ||||
|       <DialogPrimitive.Content | ||||
|         data-slot="dialog-content" | ||||
|         className={cn( | ||||
|           "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", | ||||
|           " data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", | ||||
|           className | ||||
|         )} | ||||
|         {...props} | ||||
| @@ -122,14 +122,15 @@ function DialogDescription({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Dialog, | ||||
|   DialogClose, | ||||
|   DialogContent, | ||||
|   DialogDescription, | ||||
|   DialogFooter, | ||||
|   DialogHeader, | ||||
|   DialogOverlay, | ||||
|   DialogPortal, | ||||
|   DialogTitle, | ||||
|   DialogTrigger, | ||||
|     Dialog, | ||||
|     DialogClose, | ||||
|     DialogContent, | ||||
|     DialogDescription, | ||||
|     DialogFooter, | ||||
|     DialogHeader, | ||||
|     DialogOverlay, | ||||
|     DialogPortal, | ||||
|     DialogTitle, | ||||
|     DialogTrigger | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -56,7 +56,7 @@ function DrawerContent({ | ||||
|       <DrawerPrimitive.Content | ||||
|         data-slot="drawer-content" | ||||
|         className={cn( | ||||
|           "group/drawer-content bg-background fixed z-50 flex h-auto flex-col", | ||||
|           "group/drawer-content  fixed z-50 flex h-auto flex-col", | ||||
|           "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b", | ||||
|           "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t", | ||||
|           "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm", | ||||
| @@ -119,14 +119,7 @@ function DrawerDescription({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Drawer, | ||||
|   DrawerPortal, | ||||
|   DrawerOverlay, | ||||
|   DrawerTrigger, | ||||
|   DrawerClose, | ||||
|   DrawerContent, | ||||
|   DrawerHeader, | ||||
|   DrawerFooter, | ||||
|   DrawerTitle, | ||||
|   DrawerDescription, | ||||
|     Drawer, DrawerClose, | ||||
|     DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as MenubarPrimitive from "@radix-ui/react-menubar" | ||||
| import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -14,7 +14,7 @@ function Menubar({ | ||||
|     <MenubarPrimitive.Root | ||||
|       data-slot="menubar" | ||||
|       className={cn( | ||||
|         "bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs", | ||||
|         " flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
| @@ -257,20 +257,8 @@ function MenubarSubContent({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Menubar, | ||||
|   MenubarPortal, | ||||
|   MenubarMenu, | ||||
|   MenubarTrigger, | ||||
|   MenubarContent, | ||||
|   MenubarGroup, | ||||
|   MenubarSeparator, | ||||
|   MenubarLabel, | ||||
|   MenubarItem, | ||||
|   MenubarShortcut, | ||||
|   MenubarCheckboxItem, | ||||
|   MenubarRadioGroup, | ||||
|   MenubarRadioItem, | ||||
|   MenubarSub, | ||||
|   MenubarSubTrigger, | ||||
|   MenubarSubContent, | ||||
|     Menubar, MenubarCheckboxItem, MenubarContent, | ||||
|     MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, | ||||
|     MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as React from "react" | ||||
| import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" | ||||
| import { cva } from "class-variance-authority" | ||||
| import { ChevronDownIcon } from "lucide-react" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -59,7 +59,7 @@ function NavigationMenuItem({ | ||||
| } | ||||
|  | ||||
| const navigationMenuTriggerStyle = cva( | ||||
|   "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1" | ||||
|   "group inline-flex h-9 w-max items-center justify-center rounded-md  px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1" | ||||
| ) | ||||
|  | ||||
| function NavigationMenuTrigger({ | ||||
| @@ -156,13 +156,6 @@ function NavigationMenuIndicator({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   NavigationMenu, | ||||
|   NavigationMenuList, | ||||
|   NavigationMenuItem, | ||||
|   NavigationMenuContent, | ||||
|   NavigationMenuTrigger, | ||||
|   NavigationMenuLink, | ||||
|   NavigationMenuIndicator, | ||||
|   NavigationMenuViewport, | ||||
|   navigationMenuTriggerStyle, | ||||
|     NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle, NavigationMenuViewport | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as SheetPrimitive from "@radix-ui/react-dialog" | ||||
| import { XIcon } from "lucide-react" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -58,7 +58,7 @@ function SheetContent({ | ||||
|       <SheetPrimitive.Content | ||||
|         data-slot="sheet-content" | ||||
|         className={cn( | ||||
|           "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", | ||||
|           " data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", | ||||
|           side === "right" && | ||||
|             "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", | ||||
|           side === "left" && | ||||
| @@ -128,12 +128,7 @@ function SheetDescription({ | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Sheet, | ||||
|   SheetTrigger, | ||||
|   SheetClose, | ||||
|   SheetContent, | ||||
|   SheetHeader, | ||||
|   SheetFooter, | ||||
|   SheetTitle, | ||||
|   SheetDescription, | ||||
|     Sheet, SheetClose, | ||||
|     SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -9,18 +9,18 @@ import { Button } from "@/components/ui/button" | ||||
| import { Input } from "@/components/ui/input" | ||||
| import { Separator } from "@/components/ui/separator" | ||||
| import { | ||||
| 	Sheet, | ||||
| 	SheetContent, | ||||
| 	SheetDescription, | ||||
| 	SheetHeader, | ||||
| 	SheetTitle, | ||||
|     Sheet, | ||||
|     SheetContent, | ||||
|     SheetDescription, | ||||
|     SheetHeader, | ||||
|     SheetTitle, | ||||
| } from "@/components/ui/sheet" | ||||
| import { Skeleton } from "@/components/ui/skeleton" | ||||
| import { | ||||
| 	Tooltip, | ||||
| 	TooltipContent, | ||||
| 	TooltipProvider, | ||||
| 	TooltipTrigger, | ||||
|     Tooltip, | ||||
|     TooltipContent, | ||||
|     TooltipProvider, | ||||
|     TooltipTrigger, | ||||
| } from "@/components/ui/tooltip" | ||||
| import { useIsMobile } from "@/hooks/use-mobile" | ||||
| import { cn } from "@/lib/utils" | ||||
| @@ -296,7 +296,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { | ||||
|     <main | ||||
|       data-slot="sidebar-inset" | ||||
|       className={cn( | ||||
|         "bg-background relative flex w-full flex-1 flex-col", | ||||
|         " relative flex w-full flex-1 flex-col", | ||||
|         "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2", | ||||
|         className | ||||
|       )} | ||||
| @@ -313,7 +313,7 @@ function SidebarInput({ | ||||
|     <Input | ||||
|       data-slot="sidebar-input" | ||||
|       data-sidebar="input" | ||||
|       className={cn("bg-background h-8 w-full shadow-none", className)} | ||||
|       className={cn(" h-8 w-full shadow-none", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| @@ -466,7 +466,7 @@ const sidebarMenuButtonVariants = cva( | ||||
|       variant: { | ||||
|         default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", | ||||
|         outline: | ||||
|           "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", | ||||
|           " shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", | ||||
|       }, | ||||
|       size: { | ||||
|         default: "h-8 text-sm", | ||||
| @@ -683,29 +683,29 @@ function SidebarMenuSubButton({ | ||||
| } | ||||
|  | ||||
| export { | ||||
| 	Sidebar, | ||||
| 	SidebarContent, | ||||
| 	SidebarFooter, | ||||
| 	SidebarGroup, | ||||
| 	SidebarGroupAction, | ||||
| 	SidebarGroupContent, | ||||
| 	SidebarGroupLabel, | ||||
| 	SidebarHeader, | ||||
| 	SidebarInput, | ||||
| 	SidebarInset, | ||||
| 	SidebarMenu, | ||||
| 	SidebarMenuAction, | ||||
| 	SidebarMenuBadge, | ||||
| 	SidebarMenuButton, | ||||
| 	SidebarMenuItem, | ||||
| 	SidebarMenuSkeleton, | ||||
| 	SidebarMenuSub, | ||||
| 	SidebarMenuSubButton, | ||||
| 	SidebarMenuSubItem, | ||||
| 	SidebarProvider, | ||||
| 	SidebarRail, | ||||
| 	SidebarSeparator, | ||||
| 	SidebarTrigger, | ||||
| 	useSidebar | ||||
|     Sidebar, | ||||
|     SidebarContent, | ||||
|     SidebarFooter, | ||||
|     SidebarGroup, | ||||
|     SidebarGroupAction, | ||||
|     SidebarGroupContent, | ||||
|     SidebarGroupLabel, | ||||
|     SidebarHeader, | ||||
|     SidebarInput, | ||||
|     SidebarInset, | ||||
|     SidebarMenu, | ||||
|     SidebarMenuAction, | ||||
|     SidebarMenuBadge, | ||||
|     SidebarMenuButton, | ||||
|     SidebarMenuItem, | ||||
|     SidebarMenuSkeleton, | ||||
|     SidebarMenuSub, | ||||
|     SidebarMenuSubButton, | ||||
|     SidebarMenuSubItem, | ||||
|     SidebarProvider, | ||||
|     SidebarRail, | ||||
|     SidebarSeparator, | ||||
|     SidebarTrigger, | ||||
|     useSidebar | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as SliderPrimitive from "@radix-ui/react-slider" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -53,7 +53,7 @@ function Slider({ | ||||
|         <SliderPrimitive.Thumb | ||||
|           data-slot="slider-thumb" | ||||
|           key={index} | ||||
|           className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50" | ||||
|           className="border-primary  ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50" | ||||
|         /> | ||||
|       ))} | ||||
|     </SliderPrimitive.Root> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import * as SwitchPrimitive from "@radix-ui/react-switch" | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| @@ -21,7 +21,7 @@ function Switch({ | ||||
|       <SwitchPrimitive.Thumb | ||||
|         data-slot="switch-thumb" | ||||
|         className={cn( | ||||
|           "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0" | ||||
|           " dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0" | ||||
|         )} | ||||
|       /> | ||||
|     </SwitchPrimitive.Root> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user