diff --git a/app/components/site/Navbar.tsx b/app/components/site/Navbar.tsx index 51af37c..3da7c71 100644 --- a/app/components/site/Navbar.tsx +++ b/app/components/site/Navbar.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { AnimatePresence, motion } from "framer-motion"; import { Menu, X } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useLayoutEffect, useRef, useState, type MouseEvent, type PointerEvent } from "react"; import { cn } from "../util/cn"; import { FlarialLogo } from "./FlarialLogo"; @@ -15,15 +15,44 @@ const NAV = [ { href: "/faq", label: "FAQ" }, ]; +const docsStorageKey = "flarial:last-docs-article"; +const docsSlugs = new Set([ + "what-is-flarial", + "usage", + "compatibility", + "configs", + "modules-list", + "flarial-nametag-icon", + "module-blocking", + "scripting-api", +]); + +function getRememberedDocsHref() { + if (typeof window === "undefined") { + return "/docs"; + } + + const slug = window.localStorage.getItem(docsStorageKey); + return slug && docsSlugs.has(slug) ? `/docs/${slug}/` : "/docs"; +} + export function Navbar({ onOpenPalette: _ = () => {} }: { onOpenPalette?: () => void } = {}) { const pathname = usePathname(); const isHome = pathname === "/"; + const navRef = useRef(null); + const navItemRefs = useRef<(HTMLAnchorElement | null)[]>([]); + const lastTouchToggleAtRef = useRef(0); /* Two thresholds: a small one for the bg-blur tint, a bigger one (home only) for the slide-in reveal once the visitor leaves the hero. */ const [scrolled, setScrolled] = useState(false); const [revealed, setRevealed] = useState(!isHome); const [mobile, setMobile] = useState(false); + const [docsHref, setDocsHref] = useState("/docs"); + const [indicator, setIndicator] = useState({ x: 0, width: 0, opacity: 0 }); + const activeIndex = NAV.findIndex( + (item) => pathname === item.href || (item.href !== "/" && pathname?.startsWith(item.href)), + ); useEffect(() => { const onScroll = () => { @@ -43,15 +72,64 @@ export function Navbar({ onOpenPalette: _ = () => {} }: { onOpenPalette?: () => useEffect(() => { setMobile(false); + setDocsHref(getRememberedDocsHref()); }, [pathname]); + useEffect(() => { + setDocsHref(getRememberedDocsHref()); + }, []); + + useLayoutEffect(() => { + const updateIndicator = () => { + const activeItem = activeIndex >= 0 ? navItemRefs.current[activeIndex] : null; + if (!activeItem) { + setIndicator((current) => ({ ...current, opacity: 0 })); + return; + } + + setIndicator({ + x: activeItem.offsetLeft, + width: activeItem.offsetWidth, + opacity: 1, + }); + }; + + updateIndicator(); + window.addEventListener("resize", updateIndicator); + return () => window.removeEventListener("resize", updateIndicator); + }, [activeIndex]); + + const toggleMobileMenu = () => { + setMobile((v) => !v); + }; + + const handleMobilePointerDown = (event: PointerEvent) => { + if (event.pointerType !== "touch" && event.pointerType !== "pen") { + return; + } + + event.preventDefault(); + event.stopPropagation(); + lastTouchToggleAtRef.current = Date.now(); + toggleMobileMenu(); + }; + + const handleMobileClick = (event: MouseEvent) => { + event.stopPropagation(); + if (Date.now() - lastTouchToggleAtRef.current < 700) { + return; + } + + toggleMobileMenu(); + }; + return ( {} }: { onOpenPalette?: () => Flarial -