import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { ArrowUp } from "lucide-react"; import { useTranslation } from "react-i18next"; // A floating "back to top" button that fades in once the current page's scroll container is // scrolled past a threshold, and smooth-scrolls it back to the top. Every page scrolls inside its // own
(the normal pages share App's; the channel page has its own), // so it targets the live
and re-binds whenever `dep` changes (i.e. on navigation). Portaled // to so its fixed position is viewport-relative regardless of transformed ancestors. export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; threshold?: number }) { const { t } = useTranslation(); const [show, setShow] = useState(false); const scrollerRef = useRef(null); useEffect(() => { const el = document.querySelector("main"); scrollerRef.current = el; if (!el) { setShow(false); return; } const onScroll = () => setShow(el.scrollTop > threshold); onScroll(); // a freshly-bound page starts at the top → hidden el.addEventListener("scroll", onScroll, { passive: true }); return () => el.removeEventListener("scroll", onScroll); }, [dep, threshold]); const toTop = () => scrollerRef.current?.scrollTo({ top: 0, behavior: "smooth" }); // No aria-label/title attributes: ad-block "annoyance" cosmetic filters (e.g. Brave Shields) // commonly hide floating corner buttons via attribute selectors like [aria-label="Back to top"]. // The accessible name comes from a visually-hidden child instead, which those selectors can't match. return createPortal( , document.body ); }