integratedhub/islands/tiles/FloatingTileButtons.tsx
2026-01-20 09:02:26 -06:00

99 lines
3.2 KiB
TypeScript

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<FloatingTileButtonsProps> = (
{ ids },
) => {
const [notVisibleTop, setNotVisibleTop] = useState<string[]>([]);
const [notVisibleBottom, setNotVisibleBottom] = useState<string[]>([]);
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 && (
<div class="fixed top-6 right-4 z-50 flex flex-col gap-2 sm:hidden">
{ids.filter(({ id }) => notVisibleTop.includes(id)).map((
{ id, label },
) => (
<button
key={id}
type="button"
class="bg-blue-500 hover:bg-blue-600 text-white rounded-full shadow-lg p-3 transition-all"
onClick={() =>
globalThis.document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
})}
>
{label}
</button>
))}
</div>
)}
{/* Bottom IDs Section */}
{notVisibleBottom.length > 0 && (
<div class="fixed bottom-6 right-4 z-50 flex flex-col gap-2 sm:hidden">
{ids.filter(({ id }) => notVisibleBottom.includes(id)).map((
{ id, label },
) => (
<button
key={id}
type="button"
class="bg-blue-500 hover:bg-blue-600 text-white rounded-full shadow-lg p-3 transition-all"
onClick={() =>
globalThis.document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
})}
>
{label}
</button>
))}
</div>
)}
</>
);
};
export default FloatingTileButtons;