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}