144 lines
3.4 KiB
TypeScript
144 lines
3.4 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { AnimatePresence, motion } from "motion/react";
|
|
|
|
import { useState } from "react";
|
|
import { colors } from "@/lib/colors";
|
|
|
|
export const HoverEffect = ({
|
|
items,
|
|
className,
|
|
}: {
|
|
items: {
|
|
title: string;
|
|
description: string;
|
|
link?: string;
|
|
}[];
|
|
className?: string;
|
|
}) => {
|
|
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"grid grid-cols-1 md:grid-cols-2 gap-6",
|
|
className
|
|
)}
|
|
>
|
|
{items.map((item, idx) => {
|
|
const key = item?.link ?? item.title ?? idx;
|
|
const cardContent = (
|
|
<>
|
|
<AnimatePresence>
|
|
{hoveredIndex === idx && (
|
|
<motion.span
|
|
className="absolute inset-0 h-full w-full block rounded-3xl"
|
|
style={{ backgroundColor: `${colors.secondary}33` }}
|
|
layoutId="hoverBackground"
|
|
initial={{ opacity: 0 }}
|
|
animate={{
|
|
opacity: 1,
|
|
transition: { duration: 0.15 },
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
transition: { duration: 0.15, delay: 0.2 },
|
|
}}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
<Card>
|
|
<CardTitle>{item.title}</CardTitle>
|
|
<CardDescription>{item.description}</CardDescription>
|
|
</Card>
|
|
</>
|
|
);
|
|
|
|
if (item?.link) {
|
|
return (
|
|
<a
|
|
key={key}
|
|
href={item.link}
|
|
className="relative group block p-2 h-full w-full"
|
|
onMouseEnter={() => setHoveredIndex(idx)}
|
|
onMouseLeave={() => setHoveredIndex(null)}
|
|
>
|
|
{cardContent}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={key}
|
|
className="relative group block p-2 h-full w-full"
|
|
onMouseEnter={() => setHoveredIndex(idx)}
|
|
onMouseLeave={() => setHoveredIndex(null)}
|
|
>
|
|
{cardContent}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const Card = ({
|
|
className,
|
|
children,
|
|
}: {
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"rounded-3xl h-full w-full overflow-hidden border backdrop-blur-sm transition-all duration-300 group-hover:-translate-y-1 group-hover:shadow-lg relative z-20",
|
|
className
|
|
)}
|
|
style={{
|
|
background: `linear-gradient(135deg, ${colors.background}F2, ${colors.secondary}26)`,
|
|
borderColor: `${colors.secondary}55`,
|
|
}}
|
|
>
|
|
<div className="relative z-50 p-6">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
export const CardTitle = ({
|
|
className,
|
|
children,
|
|
}: {
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}) => {
|
|
return (
|
|
<h4
|
|
className={cn("text-xl font-semibold tracking-wide", className)}
|
|
style={{ color: colors.primary }}
|
|
>
|
|
{children}
|
|
</h4>
|
|
);
|
|
};
|
|
export const CardDescription = ({
|
|
className,
|
|
children,
|
|
}: {
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}) => {
|
|
return (
|
|
<p
|
|
className={cn(
|
|
"mt-3 leading-relaxed text-base",
|
|
className
|
|
)}
|
|
style={{ color: colors.secondary }}
|
|
>
|
|
{children}
|
|
</p>
|
|
);
|
|
};
|