mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-06-28 15:30:22 +08:00
fix(web): small ui-changes
This commit is contained in:
parent
0257342947
commit
3499605fb7
@ -52,19 +52,14 @@ export const dynamic = "force-static"
|
|||||||
export default async function IconsPage() {
|
export default async function IconsPage() {
|
||||||
const icons = await getIconsArray()
|
const icons = await getIconsArray()
|
||||||
return (
|
return (
|
||||||
<div className="isolate overflow-hidden">
|
<div className="isolate overflow-hidden p-2 mx-auto max-w-7xl">
|
||||||
<div className="py-8">
|
|
||||||
<div className="space-y-4 mb-8 mx-auto max-w-7xl">
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">Browse icons</h1>
|
<h1 className="text-3xl font-bold">Browse icons</h1>
|
||||||
<p className="text-muted-foreground">Search through our collection of {icons.length} beautiful icons.</p>
|
<p className="text-muted-foreground">Search through our collection of {icons.length} beautiful icons.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IconSearch icons={icons} />
|
<IconSearch icons={icons} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,12 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { REPO_PATH } from "@/constants"
|
import { REPO_PATH } from "@/constants"
|
||||||
import { motion } from "framer-motion"
|
import { ExternalLink } from "lucide-react"
|
||||||
import { ExternalLink, Heart } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useState } from "react"
|
import { HeartEasterEgg } from "./heart"
|
||||||
|
|
||||||
// Pre-define unique IDs for animations to avoid using array indices as keys
|
|
||||||
const HOVER_HEART_IDS = [
|
|
||||||
"hover-heart-1",
|
|
||||||
"hover-heart-2",
|
|
||||||
"hover-heart-3",
|
|
||||||
"hover-heart-4",
|
|
||||||
"hover-heart-5",
|
|
||||||
"hover-heart-6",
|
|
||||||
"hover-heart-7",
|
|
||||||
"hover-heart-8",
|
|
||||||
]
|
|
||||||
const BURST_HEART_IDS = ["burst-heart-1", "burst-heart-2", "burst-heart-3", "burst-heart-4", "burst-heart-5"]
|
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
const [isHeartHovered, setIsHeartHovered] = useState(false)
|
|
||||||
const [isHeartFilled, setIsHeartFilled] = useState(false)
|
|
||||||
|
|
||||||
// Toggle heart fill state and add extra mini hearts on click
|
|
||||||
const handleHeartClick = () => {
|
|
||||||
setIsHeartFilled(!isHeartFilled)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t py-4 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="absolute inset-0 bg-background bg-gradient-to-r from-primary/[0.03] via-transparent to-primary/[0.03]" />
|
||||||
|
|
||||||
<div className="container mx-auto mb-2 px-4 md:px-6 relative z-10">
|
<div className="container mx-auto mb-2 px-4 md:px-6 relative z-10">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
|
||||||
@ -53,117 +29,9 @@ export function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<div className="flex flex-col gap-3">
|
||||||
className="flex flex-col gap-3"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ duration: 0.5, delay: 0.2 }}
|
|
||||||
>
|
|
||||||
<h3 className="font-bold text-lg text-foreground/90">Community</h3>
|
<h3 className="font-bold text-lg text-foreground/90">Community</h3>
|
||||||
<div className="text-sm flex flex-wrap items-center gap-1.5 leading-relaxed">
|
<HeartEasterEgg />
|
||||||
Made with{" "}
|
|
||||||
<div className="relative inline-block">
|
|
||||||
<motion.div
|
|
||||||
className="cursor-pointer"
|
|
||||||
onMouseEnter={() => setIsHeartHovered(true)}
|
|
||||||
onMouseLeave={() => setIsHeartHovered(false)}
|
|
||||||
onClick={handleHeartClick}
|
|
||||||
whileTap={{ scale: 0.85 }}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
animate={{
|
|
||||||
scale: isHeartFilled ? [1, 1.3, 1] : 1,
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: isHeartFilled ? 0.4 : 0,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heart
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Easter egg mini hearts */}
|
|
||||||
{isHeartHovered && (
|
|
||||||
<>
|
|
||||||
{HOVER_HEART_IDS.map((id, i) => (
|
|
||||||
<motion.div
|
|
||||||
key={id}
|
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
scale: [0, 1, 0.8],
|
|
||||||
opacity: [0, 1, 0],
|
|
||||||
x: [0, (i % 2 === 0 ? 1 : -1) * Math.random() * 20],
|
|
||||||
y: [0, -Math.random() * 30],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 0.8 + Math.random() * 0.5,
|
|
||||||
ease: "easeOut",
|
|
||||||
delay: Math.random() * 0.2,
|
|
||||||
}}
|
|
||||||
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" : ""}`} />
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Subtle particle glow */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
scale: [0, 3],
|
|
||||||
opacity: [0, 0.3, 0],
|
|
||||||
}}
|
|
||||||
transition={{ duration: 0.6, ease: "easeOut" }}
|
|
||||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 rounded-full bg-rose-500/20 pointer-events-none"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Heart fill animation extras */}
|
|
||||||
{isHeartFilled && (
|
|
||||||
<>
|
|
||||||
{/* Radiating circles on heart fill */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ scale: 0.5, opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
scale: [0.5, 2.5],
|
|
||||||
opacity: [0.5, 0],
|
|
||||||
}}
|
|
||||||
transition={{ duration: 0.6, ease: "easeOut" }}
|
|
||||||
className="absolute left-1/2 top-1/2 w-3 h-3 rounded-full bg-rose-500/30 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Extra burst of mini hearts when filled */}
|
|
||||||
{BURST_HEART_IDS.map((id, i) => (
|
|
||||||
<motion.div
|
|
||||||
key={id}
|
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
|
||||||
animate={{
|
|
||||||
scale: [0, 1, 0.8],
|
|
||||||
opacity: [0, 1, 0],
|
|
||||||
x: [0, Math.cos((i * Math.PI) / 2.5) * 25],
|
|
||||||
y: [0, Math.sin((i * Math.PI) / 2.5) * 25],
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 0.6,
|
|
||||||
ease: "easeOut",
|
|
||||||
}}
|
|
||||||
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 " fill="#f43f5e" />
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>{" "}
|
|
||||||
by Homarr Labs and the open source community.
|
|
||||||
</div>
|
|
||||||
<Link
|
<Link
|
||||||
href={REPO_PATH}
|
href={REPO_PATH}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -173,7 +41,7 @@ export function Footer() {
|
|||||||
Contribute to this project
|
Contribute to this project
|
||||||
<ExternalLink className="h-3.5 w-3.5 flex-shrink-0" />
|
<ExternalLink className="h-3.5 w-3.5 flex-shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -12,12 +12,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/t
|
|||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
return (
|
return (
|
||||||
<motion.header
|
<div className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50">
|
||||||
className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
|
|
||||||
initial={{ y: -20, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
||||||
>
|
|
||||||
<div className="px-4 md:px-12 flex items-center justify-between h-16 md:h-18">
|
<div className="px-4 md:px-12 flex items-center justify-between h-16 md:h-18">
|
||||||
<div className="flex items-center gap-2 md:gap-6">
|
<div className="flex items-center gap-2 md:gap-6">
|
||||||
<Link href="/" className="text-lg md:text-xl font-bold group hidden md:block">
|
<Link href="/" className="text-lg md:text-xl font-bold group hidden md:block">
|
||||||
@ -54,6 +49,6 @@ export function Header() {
|
|||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.header>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
121
web/src/components/heart.tsx
Normal file
121
web/src/components/heart.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Heart } from "lucide-react"
|
||||||
|
|
||||||
|
import { motion } from "framer-motion"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
export function HeartEasterEgg() {
|
||||||
|
const [isHeartHovered, setIsHeartHovered] = useState(false)
|
||||||
|
const [isHeartFilled, setIsHeartFilled] = useState(false)
|
||||||
|
|
||||||
|
const handleHeartClick = () => {
|
||||||
|
setIsHeartFilled(!isHeartFilled)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-sm flex flex-wrap items-center gap-1.5 leading-relaxed">
|
||||||
|
Made with{" "}
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<motion.div
|
||||||
|
className="cursor-pointer"
|
||||||
|
onMouseEnter={() => setIsHeartHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHeartHovered(false)}
|
||||||
|
onClick={handleHeartClick}
|
||||||
|
whileTap={{ scale: 0.85 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
scale: isHeartFilled ? [1, 1.3, 1] : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: isHeartFilled ? 0.4 : 0,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Heart
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Easter egg mini hearts */}
|
||||||
|
{isHeartHovered && (
|
||||||
|
<>
|
||||||
|
{[...Array(8)].map((_, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0, 1, 0.8],
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
x: [0, (i % 2 === 0 ? 1 : -1) * Math.random() * 20],
|
||||||
|
y: [0, -Math.random() * 30],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.8 + Math.random() * 0.5,
|
||||||
|
ease: "easeOut",
|
||||||
|
delay: Math.random() * 0.2,
|
||||||
|
}}
|
||||||
|
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" : ""}`} />
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Subtle particle glow */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0, 3],
|
||||||
|
opacity: [0, 0.3, 0],
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||||
|
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 rounded-full bg-rose-500/20 pointer-events-none"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Heart fill animation extras */}
|
||||||
|
{isHeartFilled && (
|
||||||
|
<>
|
||||||
|
{/* Radiating circles on heart fill */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.5, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0.5, 2.5],
|
||||||
|
opacity: [0.5, 0],
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||||
|
className="absolute left-1/2 top-1/2 w-3 h-3 rounded-full bg-rose-500/30 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Extra burst of mini hearts when filled */}
|
||||||
|
{[...Array(8)].map((_, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0, 1, 0.8],
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
x: [0, Math.cos((i * Math.PI) / 2.5) * 25],
|
||||||
|
y: [0, Math.sin((i * Math.PI) / 2.5) * 25],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.6,
|
||||||
|
ease: "easeOut",
|
||||||
|
}}
|
||||||
|
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 " fill="#f43f5e" />
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>{" "}
|
||||||
|
by Homarr Labs and the open source community.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -11,7 +11,7 @@ export function IconCard({
|
|||||||
}: {
|
}: {
|
||||||
name: string
|
name: string
|
||||||
data: Icon
|
data: Icon
|
||||||
matchedAlias?: string | null
|
matchedAlias?: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<MagicCard className="rounded-md shadow-md">
|
<MagicCard className="rounded-md shadow-md">
|
||||||
|
@ -9,13 +9,10 @@ interface IconsGridProps {
|
|||||||
|
|
||||||
export function IconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) {
|
export function IconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<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">
|
<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.slice(0, 120).map(({ name, data }) => (
|
{filteredIcons.slice(0, 120).map(({ name, data }) => (
|
||||||
<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name] || null} />
|
<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name]} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{filteredIcons.length > 120 && <p className="text-sm text-muted-foreground">And {filteredIcons.length - 120} more...</p>}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user