import { useEffect, useState } from "react"; import { useLocation } from "react-router"; import { t } from "@lingui/core/macro"; import { useAuth } from "../hooks/useAuth.ts"; import { useWS } from "../hooks/useWS.ts"; import { useChat } from "../hooks/useChat.ts"; /** * Floating chat button shown once the header has scrolled out of view, mirroring * {@link DumpFab}. Opens the single app-wide chat modal (owned by ChatProvider). * Only rendered for signed-in users. */ export function ChatFab() { const { user } = useAuth(); const { unreadChatCount } = useWS(); const { openChat } = useChat(); const location = useLocation(); const [headerHidden, setHeaderHidden] = useState(false); // Reveal the button once the header's bottom edge scrolls above the viewport. // Re-reading the header on every tick keeps this correct across navigation // and lazily-mounted pages, where the header element is replaced. useEffect(() => { if (!user) return; const update = () => { const header = document.querySelector(".app-header"); setHeaderHidden(!!header && header.getBoundingClientRect().bottom <= 0); }; update(); globalThis.addEventListener("scroll", update, { passive: true }); globalThis.addEventListener("resize", update); const ro = new ResizeObserver(update); ro.observe(document.body); return () => { globalThis.removeEventListener("scroll", update); globalThis.removeEventListener("resize", update); ro.disconnect(); }; }, [user, location.pathname]); if (!user) return null; return ( ); }