99 lines
3.1 KiB
TypeScript
99 lines
3.1 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="floating-jump-stack floating-jump-stack--top sm:hidden">
|
|
{ids.filter(({ id }) => notVisibleTop.includes(id)).map((
|
|
{ id, label },
|
|
) => (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
class="floating-jump-button"
|
|
onClick={() =>
|
|
globalThis.document.getElementById(id)?.scrollIntoView({
|
|
behavior: "smooth",
|
|
})}
|
|
>
|
|
{label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
{/* Bottom IDs Section */}
|
|
{notVisibleBottom.length > 0 && (
|
|
<div class="floating-jump-stack floating-jump-stack--bottom sm:hidden">
|
|
{ids.filter(({ id }) => notVisibleBottom.includes(id)).map((
|
|
{ id, label },
|
|
) => (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
class="floating-jump-button"
|
|
onClick={() =>
|
|
globalThis.document.getElementById(id)?.scrollIntoView({
|
|
behavior: "smooth",
|
|
})}
|
|
>
|
|
{label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default FloatingTileButtons;
|