mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-06-28 23:40:21 +08:00
Virtualize (kind of) the list
This commit is contained in:
parent
b5e2cca8d9
commit
e90d3c4b7f
@ -399,11 +399,8 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
<span>{getSortLabel(sortOption)}</span>
|
<span>{getSortLabel(sortOption)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 }) => (
|
<IconsGrid filteredIcons={filteredIcons} matchedAliases={matchedAliases} />
|
||||||
<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name] || null} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@ -419,7 +416,6 @@ function IconCard({
|
|||||||
data: Icon
|
data: Icon
|
||||||
matchedAlias?: string | null
|
matchedAlias?: string | null
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MagicCard className="rounded-md shadow-md">
|
<MagicCard className="rounded-md shadow-md">
|
||||||
<Link prefetch={false} href={`/icons/${name}`} className="group flex flex-col items-center p-3 sm:p-4 cursor-pointer">
|
<Link prefetch={false} href={`/icons/${name}`} className="group flex flex-col items-center p-3 sm:p-4 cursor-pointer">
|
||||||
@ -440,3 +436,21 @@ function IconCard({
|
|||||||
</MagicCard>
|
</MagicCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IconsGridProps {
|
||||||
|
filteredIcons: { name: string; data: Icon }[]
|
||||||
|
matchedAliases: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) {
|
||||||
|
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">
|
||||||
|
{filteredIcons.slice(0, 120).map(({ name, data }) => (
|
||||||
|
<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name] || null} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{filteredIcons.length > 120 && <p className="text-sm text-muted-foreground">And {filteredIcons.length - 120} more...</p>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
"use client";
|
"use client"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card"
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input"
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
import { motion, useAnimation, useInView } from "framer-motion";
|
import { motion, useAnimation, useInView } from "framer-motion"
|
||||||
import { DollarSign, Heart, Search, Star } from "lucide-react";
|
import { DollarSign, Heart, Search, Star } from "lucide-react"
|
||||||
import Link from "next/link";
|
import Link from "next/link"
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { AuroraText } from "./magicui/aurora-text";
|
import { AuroraText } from "./magicui/aurora-text"
|
||||||
import { InteractiveHoverButton } from "./magicui/interactive-hover-button";
|
import { InteractiveHoverButton } from "./magicui/interactive-hover-button"
|
||||||
|
|
||||||
interface IconCardProps {
|
interface IconCardProps {
|
||||||
name: string;
|
name: string
|
||||||
imageUrl: string;
|
imageUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function IconCard({ name, imageUrl }: IconCardProps) {
|
function IconCard({ name, imageUrl }: IconCardProps) {
|
||||||
@ -22,11 +22,9 @@ function IconCard({ name, imageUrl }: IconCardProps) {
|
|||||||
<div className="w-16 h-16 flex items-center justify-center">
|
<div className="w-16 h-16 flex items-center justify-center">
|
||||||
<img src={imageUrl} alt={name} className="max-w-full max-h-full" />
|
<img src={imageUrl} alt={name} className="max-w-full max-h-full" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-center text-muted-foreground group-hover:text-foreground transition-colors">
|
<p className="text-sm text-center text-muted-foreground group-hover:text-foreground transition-colors">{name}</p>
|
||||||
{name}
|
|
||||||
</p>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ElegantShape({
|
function ElegantShape({
|
||||||
@ -39,28 +37,28 @@ function ElegantShape({
|
|||||||
mobileWidth,
|
mobileWidth,
|
||||||
mobileHeight,
|
mobileHeight,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string
|
||||||
delay?: number;
|
delay?: number
|
||||||
width?: number;
|
width?: number
|
||||||
height?: number;
|
height?: number
|
||||||
rotate?: number;
|
rotate?: number
|
||||||
gradient?: string;
|
gradient?: string
|
||||||
mobileWidth?: number;
|
mobileWidth?: number
|
||||||
mobileHeight?: number;
|
mobileHeight?: number
|
||||||
}) {
|
}) {
|
||||||
const controls = useAnimation();
|
const controls = useAnimation()
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false)
|
||||||
const ref = useRef(null);
|
const ref = useRef(null)
|
||||||
const isInView = useInView(ref, { once: true, amount: 0.1 });
|
const isInView = useInView(ref, { once: true, amount: 0.1 })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkMobile = () => {
|
const checkMobile = () => {
|
||||||
setIsMobile(window.innerWidth < 768);
|
setIsMobile(window.innerWidth < 768)
|
||||||
};
|
}
|
||||||
checkMobile();
|
checkMobile()
|
||||||
window.addEventListener("resize", checkMobile);
|
window.addEventListener("resize", checkMobile)
|
||||||
return () => window.removeEventListener("resize", checkMobile);
|
return () => window.removeEventListener("resize", checkMobile)
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInView) {
|
if (isInView) {
|
||||||
@ -77,9 +75,9 @@ function ElegantShape({
|
|||||||
ease: [0.23, 0.86, 0.39, 0.96],
|
ease: [0.23, 0.86, 0.39, 0.96],
|
||||||
opacity: { duration: 1.2 },
|
opacity: { duration: 1.2 },
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}, [controls, delay, isInView, rotate]);
|
}, [controls, delay, isInView, rotate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -121,14 +119,11 @@ function ElegantShape({
|
|||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeroSection({
|
export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars: number }) {
|
||||||
totalIcons,
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
stars,
|
|
||||||
}: { totalIcons: number; stars: number }) {
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative my-20 w-full flex items-center justify-center overflow-hidden">
|
<div className="relative my-20 w-full flex items-center justify-center overflow-hidden">
|
||||||
@ -193,40 +188,26 @@ export function HeroSection({
|
|||||||
|
|
||||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
<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">
|
<div className="max-w-4xl mx-auto text-center flex flex-col gap-4">
|
||||||
<Link
|
<Link prefetch href="https://github.com/homarr-labs" target="_blank" rel="noopener noreferrer" className="mx-auto">
|
||||||
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">
|
<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
|
<Heart
|
||||||
// Filled when hovered
|
// Filled when hovered
|
||||||
className="h-4 w-4 text-primary group-hover:fill-primary transition-all duration-300"
|
className="h-4 w-4 text-primary group-hover:fill-primary transition-all duration-300"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-foreground/70 tracking-wide">
|
<span className="text-sm text-foreground/70 tracking-wide">Made with love by Homarr Labs</span>
|
||||||
Made with love by Homarr Labs
|
|
||||||
</span>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<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">
|
<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
|
Your definitive source for
|
||||||
<br />
|
<br />
|
||||||
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>
|
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>dashboard icons</AuroraText>
|
||||||
dashboard icons
|
|
||||||
</AuroraText>
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<motion.div
|
<motion.div custom={2} className="motion-safe:motion-preset-slide-up motion-duration-1500">
|
||||||
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">
|
<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 ">{totalIcons}</span>{" "}
|
A collection of <span className="font-medium ">{totalIcons}</span> curated icons for services, applications and tools,
|
||||||
curated icons for services, applications and tools, designed
|
designed specifically for dashboards and app directories.
|
||||||
specifically for dashboards and app directories.
|
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -234,11 +215,7 @@ export function HeroSection({
|
|||||||
custom={3}
|
custom={3}
|
||||||
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"
|
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
|
<form action="/icons" method="GET" className="relative w-full max-w-md group">
|
||||||
action="/icons"
|
|
||||||
method="GET"
|
|
||||||
className="relative w-full max-w-md group"
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
name="q"
|
name="q"
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -258,9 +235,7 @@ export function HeroSection({
|
|||||||
</form>
|
</form>
|
||||||
<div className="flex gap-3 md:gap-4 flex-wrap justify-center">
|
<div className="flex gap-3 md:gap-4 flex-wrap justify-center">
|
||||||
<Link href="/icons">
|
<Link href="/icons">
|
||||||
<InteractiveHoverButton className="rounded-md bg-input/30">
|
<InteractiveHoverButton className="rounded-md bg-input/30">Explore icons</InteractiveHoverButton>
|
||||||
Explore icons
|
|
||||||
</InteractiveHoverButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/homarr-labs/dashboard-icons"
|
href="https://github.com/homarr-labs/dashboard-icons"
|
||||||
@ -272,9 +247,7 @@ export function HeroSection({
|
|||||||
<div>
|
<div>
|
||||||
<p>Give us a star</p>
|
<p>Give us a star</p>
|
||||||
<Star className="h-4 w-4 ml-1 text-yellow-500 fill-yellow-500" />
|
<Star className="h-4 w-4 ml-1 text-yellow-500 fill-yellow-500" />
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">{stars}</span>
|
||||||
{stars}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
@ -294,5 +267,5 @@ export function HeroSection({
|
|||||||
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-background/80 pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-transparent to-background/80 pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user