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