Leaflet Blog in Deno Fresh
1import { Footer } from "../components/footer.tsx";
2import type { ComponentChildren } from "preact";
3import { useEffect, useState } from "preact/hooks";
4import { useSignal } from "@preact/signals";
5
6export function Layout({ children }: { children: ComponentChildren }) {
7 const [isScrolled, setIsScrolled] = useState(false);
8 const [blogHovered, setBlogHovered] = useState(false);
9 const [aboutHovered, setAboutHovered] = useState(false);
10 const pathname = useSignal("");
11
12 const isActive = (href: string) => {
13 if (href === "/") {
14 return pathname.value === "/" || pathname.value.startsWith("/post/");
15 }
16 return pathname.value === href;
17 };
18
19 useEffect(() => {
20 const handleScroll = () => {
21 setIsScrolled(window.scrollY > 0);
22 };
23
24 const handlePathChange = () => {
25 pathname.value = window.location.pathname;
26 };
27
28 window.addEventListener("scroll", handleScroll);
29 window.addEventListener("popstate", handlePathChange);
30 handleScroll(); // Check initial scroll position
31 handlePathChange(); // Set initial path
32
33 return () => {
34 window.removeEventListener("scroll", handleScroll);
35 window.removeEventListener("popstate", handlePathChange);
36 };
37 }, []);
38
39 return (
40 <div class="flex flex-col min-h-dvh">
41 <nav class="w-full sticky top-0 z-50 backdrop-blur-sm transition-[padding,border-color] duration-200">
42 <div class="relative">
43 <div
44 class="absolute inset-x-0 bottom-0 h-2 diagonal-pattern opacity-0 transition-opacity duration-300"
45 style={{ opacity: isScrolled ? 0.25 : 0 }}
46 />
47 <div class="max-w-screen-2xl mx-auto px-8 py-5 flex justify-between items-center">
48 <div class="flex items-center gap-7">
49 <a href="/" class="font-serif text-xl">
50 knotbin
51 </a>
52 <div class="h-4 w-px bg-slate-200 dark:bg-slate-700"></div>
53 <div class="text-base flex items-center gap-7">
54 <a
55 href="/"
56 class="relative group"
57 data-current={isActive("/")}
58 data-hovered={blogHovered}
59 onMouseEnter={() => setBlogHovered(true)}
60 onMouseLeave={() => setBlogHovered(false)}
61 >
62 <span class="opacity-50 group-hover:opacity-100 group-data-[current=true]:opacity-100 transition-opacity">
63 blog
64 </span>
65 <div class="absolute bottom-0 left-0 w-full h-px bg-current scale-x-0 group-hover:scale-x-100 group-data-[current=true]:scale-x-100 transition-transform duration-300 ease-in-out group-hover:origin-left group-data-[hovered=false]:origin-right" />
66 </a>
67 <a
68 href="/about"
69 class="relative group"
70 data-current={isActive("/about")}
71 data-hovered={aboutHovered}
72 onMouseEnter={() => setAboutHovered(true)}
73 onMouseLeave={() => setAboutHovered(false)}
74 >
75 <span class="opacity-50 group-hover:opacity-100 group-data-[current=true]:opacity-100 transition-opacity">
76 about
77 </span>
78 <div class="absolute bottom-0 left-0 w-full h-px bg-current scale-x-0 group-hover:scale-x-100 group-data-[current=true]:scale-x-100 transition-transform duration-300 ease-in-out group-hover:origin-left group-data-[hovered=false]:origin-right" />
79 </a>
80 </div>
81 </div>
82 </div>
83 </div>
84 </nav>
85
86 <main class="flex-1">{children}</main>
87
88 <Footer />
89 </div>
90 );
91}