Leaflet Blog in Deno Fresh
1"use client"; 2 3import { useEffect, useRef, useState } from "preact/hooks"; 4import { ComWhtwndBlogEntry } from "npm:@atcute/client/whitewind"; 5 6import { cx } from "../lib/cx.ts"; 7 8import { PostInfo } from "./post-info.tsx"; 9import { Title } from "./typography.tsx"; 10 11export function PostListItem({ 12 post, 13 rkey, 14}: { 15 post: ComWhtwndBlogEntry.Record; 16 rkey: string; 17}) { 18 const [isHovered, setIsHovered] = useState(false); 19 const [isLeaving, setIsLeaving] = useState(false); 20 const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); 21 22 // Clean up any timeouts on unmount 23 useEffect(() => { 24 return () => { 25 if (timeoutRef.current) { 26 clearTimeout(timeoutRef.current); 27 } 28 }; 29 }, []); 30 31 const handleMouseEnter = () => { 32 if (timeoutRef.current) { 33 clearTimeout(timeoutRef.current); 34 } 35 setIsLeaving(false); 36 setIsHovered(true); 37 }; 38 39 const handleMouseLeave = () => { 40 setIsLeaving(true); 41 timeoutRef.current = setTimeout(() => { 42 setIsHovered(false); 43 setIsLeaving(false); 44 }, 300); // Match the animation duration 45 }; 46 47 return ( 48 <> 49 {isHovered && ( 50 <div 51 className={cx( 52 "fixed inset-0 pointer-events-none z-0 overflow-hidden flex items-center", 53 isLeaving ? "animate-fade-out" : "animate-fade-in", 54 )} 55 > 56 <div className="absolute whitespace-nowrap animate-marquee font-serif font-medium uppercase overflow-visible flex items-center justify-center leading-none"> 57 {Array(10).fill(post.title).join(" · ")} 58 </div> 59 </div> 60 )} 61 <a 62 href={`/post/${rkey}`} 63 className="w-full group" 64 onMouseEnter={handleMouseEnter} 65 onMouseLeave={handleMouseLeave} 66 > 67 <article className="w-full flex flex-row border-b items-stretch relative transition-color backdrop-blur-sm hover:bg-slate-700/5 dark:hover:bg-slate-200/10"> 68 <div className="w-1.5 diagonal-pattern shrink-0 opacity-20 group-hover:opacity-100 transition-opacity" /> 69 <div className="flex-1 py-2 px-4 z-10 relative"> 70 <Title className="text-lg" level="h3"> 71 {post.title} 72 </Title> 73 <PostInfo 74 content={post.content} 75 createdAt={post.createdAt} 76 className="text-xs mt-1" 77 > 78 </PostInfo> 79 </div> 80 </article> 81 </a> 82 </> 83 ); 84}