"use client"; import { useEffect, useRef, useState } from "preact/hooks"; import { cx } from "../lib/cx.ts"; import { Title } from "./typography.tsx"; interface Project { id: string; title: string; description: string; technologies: string[]; url: string; demo?: string; year: string; status: "active" | "completed" | "maintained" | "archived"; } export function ProjectListItem({ project }: { project: Project }) { const [isHovered, setIsHovered] = useState(false); const [isLeaving, setIsLeaving] = useState(false); const timeoutRef = useRef | null>(null); // Clean up any timeouts on unmount useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const handleMouseEnter = () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } setIsLeaving(false); setIsHovered(true); }; const handleMouseLeave = () => { setIsLeaving(true); timeoutRef.current = setTimeout(() => { setIsHovered(false); setIsLeaving(false); }, 300); // Match animation duration }; const getStatusColor = (status: string) => { switch (status) { case "active": return "text-green-600 dark:text-green-400"; case "completed": return "text-blue-600 dark:text-blue-400"; case "maintained": return "text-yellow-600 dark:text-yellow-400"; case "archived": return "text-slate-500 dark:text-slate-400"; default: return "text-slate-600 dark:text-slate-300"; } }; return ( <> {isHovered && (
{Array(8).fill(project.title).join(" ยท ")}
)}
{project.title}
{project.year} {project.status}
{project.technologies.slice(0, 4).map((tech) => ( {tech} ))} {project.technologies.length > 4 && ( +{project.technologies.length - 4} more )}
); }