"use client";
import * as React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { cn } from "@/lib/utils";
interface ExpandableCardProps {
title: string;
src: string;
description: string;
children?: React.ReactNode;
className?: string;
[key: string]: any;
}
export default function ExpandableCard({
title,
src,
description,
children,
className,
...props
}: ExpandableCardProps) {
const [active, setActive] = React.useState(false);
const localRef = React.useRef<HTMLDivElement>(null);
const id = React.useId();
React.useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setActive(false);
}
};
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (localRef.current && !localRef.current.contains(event.target as Node)) {
setActive(false);
}
};
window.addEventListener("keydown", onKeyDown);
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("touchstart", handleClickOutside);
return () => {
window.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("touchstart", handleClickOutside);
};
}, []);
return (
<>
<AnimatePresence>
{active && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-white/50 dark:bg-black/50 backdrop-blur-md h-full w-full z-10"
/>
)}
</AnimatePresence>
<AnimatePresence>
{active && (
<div className="fixed inset-0 grid place-items-center z-[100] mt-16">
<motion.div
layoutId={`card-${title}-${id}`}
ref={localRef}
className={cn(
"w-full max-w-[850px] h-full flex flex-col overflow-auto [scrollbar-width:none] [-ms-overflow-style:none] [-webkit-overflow-scrolling:touch] before:content-[''] before:pointer-events-none before:absolute before:inset-0 before:bg-gradient-to-t from-white dark:from-black to-20% before:z-10 shadow-sm dark:shadow-none sm:rounded-t-md bg-white dark:bg-black",
className
)}
{...props}
>
<motion.div layoutId={`image-${title}-${id}`}>
<img
src={src}
alt={title}
className="w-full h-80 lg:h-80 object-cover object-center"
/>
</motion.div>
<div className="relative h-full">
<div className="flex justify-between items-center p-8 h-auto">
<div>
<motion.p
layoutId={`description-${description}-${id}`}
className="text-zinc-500 dark:text-zinc-400 text-lg mb-0.5 font-medium"
>
{description}
</motion.p>
<motion.h3
layoutId={`title-${title}-${id}`}
className="font-bold text-neutral-700 dark:text-neutral-200 text-4xl"
>
{title}
</motion.h3>
</div>
<motion.button
layoutId={`button-${title}-${id}`}
className="h-9 w-9 shrink-0 flex items-center justify-center rounded-full bg-white dark:bg-black text-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-950 dark:text-white border border-neutral-200 dark:border-neutral-700 transition-colors"
onClick={() => setActive(false)}
>
<motion.div
animate={{ rotate: active ? 45 : 0 }}
transition={{ duration: 0.4 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-plus"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
</motion.div>
</motion.button>
</div>
<div className="relative px-8">
<motion.div
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="text-zinc-500 dark:text-zinc-400 text-base pb-10 flex flex-col items-start gap-4 overflow-auto "
>
{children}
</motion.div>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
<motion.div
layoutId={`card-${title}-${id}`}
onClick={() => setActive(true)}
className={cn(
"p-3 flex flex-col justify-between items-center bg-white shadow-sm dark:shadow-none dark:bg-black rounded-md cursor-pointer border border-neutral-100 dark:border-neutral-800",
className
)}
>
<div className="flex gap-4 flex-col">
<motion.div layoutId={`image-${title}-${id}`}>
<img
src={src}
alt={title}
className="w-64 h-56 rounded object-cover object-center"
/>
</motion.div>
<div className="flex justify-between items-center">
<div className="flex flex-col">
<motion.p
layoutId={`description-${description}-${id}`}
className="text-zinc-500 dark:text-zinc-400 md:text-left text-sm font-medium"
>
{description}
</motion.p>
<motion.h3
layoutId={`title-${title}-${id}`}
className="text-neutral-800 dark:text-neutral-200 md:text-left font-semibold"
>
{title}
</motion.h3>
</div>
<motion.button
layoutId={`button-${title}-${id}`}
className="h-8 w-8 shrink-0 flex items-center justify-center rounded-full bg-white dark:bg-black text-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-950 dark:text-white border border-neutral-200 dark:border-neutral-700 transition-colors"
>
<motion.div
animate={{ rotate: active ? 45 : 0 }}
transition={{ duration: 0.4 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-plus"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
</motion.div>
</motion.button>
</div>
</div>
</motion.div>
</>
);
}