import { FunctionalComponent } from "preact"; import { useEffect, useState } from "preact/hooks"; interface FloatingTileButtonsProps { ids: { id: string; label: string }[]; } const isNotVisibleTop = (elem: HTMLElement | null) => { if (!elem) return false; const rect = elem.getBoundingClientRect(); // If the bottom is above the top of the viewport (with threshold), it's not visible at the top return rect.bottom - 100 < 0; }; const isNotVisibleBottom = (elem: HTMLElement | null) => { if (!elem) return false; const rect = elem.getBoundingClientRect(); // If the top is below the bottom of the viewport (with threshold), it's not visible at the bottom return rect.top + 100 > (globalThis.innerHeight || globalThis.document.documentElement.clientHeight); }; const FloatingTileButtons: FunctionalComponent = ( { ids }, ) => { const [notVisibleTop, setNotVisibleTop] = useState([]); const [notVisibleBottom, setNotVisibleBottom] = useState([]); useEffect(() => { const checkTiles = () => { const topList = ids.filter(({ id }) => { const elem = globalThis.document.getElementById(id); return isNotVisibleTop(elem); }).map(({ id }) => id); const bottomList = ids.filter(({ id }) => { const elem = globalThis.document.getElementById(id); return !topList.includes(id) && isNotVisibleBottom(elem); }).map(({ id }) => id); setNotVisibleTop(topList); setNotVisibleBottom(bottomList); }; checkTiles(); globalThis.addEventListener("scroll", checkTiles, { passive: true }); globalThis.addEventListener("resize", checkTiles); return () => { globalThis.removeEventListener("scroll", checkTiles); globalThis.removeEventListener("resize", checkTiles); }; }, [ids]); return ( <> {/* Top IDs Section */} {notVisibleTop.length > 0 && (
{ids.filter(({ id }) => notVisibleTop.includes(id)).map(( { id, label }, ) => ( ))}
)} {/* Bottom IDs Section */} {notVisibleBottom.length > 0 && (
{ids.filter(({ id }) => notVisibleBottom.includes(id)).map(( { id, label }, ) => ( ))}
)} ); }; export default FloatingTileButtons;