Leaflet Blog in Deno Fresh
at main 4.6 kB view raw
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}