60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useSyncExternalStore } from "react";
|
|
|
|
import { openAppDownload, useAppDownloadHref } from "@/components/download/download-dialog";
|
|
|
|
function subscribe(callback: () => void) {
|
|
window.addEventListener("scroll", callback, { passive: true });
|
|
window.addEventListener("resize", callback);
|
|
return () => {
|
|
window.removeEventListener("scroll", callback);
|
|
window.removeEventListener("resize", callback);
|
|
};
|
|
}
|
|
|
|
const getPastFirstScreen = () => window.scrollY > window.innerHeight;
|
|
const getServerSnapshot = () => false;
|
|
|
|
export function FloatingActions() {
|
|
const showToTop = useSyncExternalStore(subscribe, getPastFirstScreen, getServerSnapshot);
|
|
const downloadHref = useAppDownloadHref();
|
|
|
|
return (
|
|
<div data-component="floating-actions" className="fixed right-3 bottom-20 z-40 flex flex-col items-center gap-3 lg:right-6 lg:bottom-24 lg:gap-4">
|
|
<button
|
|
type="button"
|
|
aria-label="回到顶部"
|
|
aria-hidden={!showToTop}
|
|
tabIndex={showToTop ? 0 : -1}
|
|
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
|
className={`cursor-pointer size-11 transition-[opacity,translate] duration-300 lg:size-[60px] ${
|
|
showToTop ? "opacity-100" : "pointer-events-none translate-y-2 opacity-0"
|
|
}`}
|
|
>
|
|
<img src="/images/toTop.png" alt="" className="size-full" />
|
|
</button>
|
|
{downloadHref ? (
|
|
<a
|
|
href={downloadHref}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
aria-label="立即下载"
|
|
className="size-[52px] lg:size-[68px]"
|
|
>
|
|
<img src="/images/download.png" alt="" className="size-full" />
|
|
</a>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
aria-label="立即下载"
|
|
onClick={() => openAppDownload()}
|
|
className="size-[52px] cursor-pointer lg:size-[68px]"
|
|
>
|
|
<img src="/images/download.png" alt="" className="size-full" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|