2025-10-11 19:04:27 +01:00

212 lines
6.0 KiB
JavaScript

import { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { Layout } from "antd";
import ContentRenderer from "./ContentRenderer";
import HeaderLogo from "./HeaderLogo";
import ScrollIcon from "../icons/ScrollIcon";
import { useMediaQuery } from "react-responsive";
import ImageCarousel from "./ImageCarousel";
import CloseButton from "./CloseButton";
import MenuButton from "./MenuButton";
import { useSettingsContext } from "../contexts/SettingsContext";
const { Content } = Layout;
const Page = ({
pageData,
theme = null,
visible = true,
id = 0,
showClose = false,
isSubPage = false,
showMenu = true,
onClose = () => {},
}) => {
const isLargeMobile = useMediaQuery({ maxWidth: 1200 });
const isMobile = useMediaQuery({ maxWidth: 800 });
const mobileImage =
isMobile &&
!pageData.showProperties &&
!pageData.showContactForm &&
!pageData.hideMobileImage;
const contentRef = useRef(null);
const [isImageShrunk, setIsImageShrunk] = useState(false);
const [safariBlurToggle, setSafariBlurToggle] = useState(false);
const shrinkDistance = 1;
const settings = useSettingsContext();
const themes = settings?.themes || [];
if (theme == null) {
theme = settings?.globalThemes?.page || settings?.themes[0];
}
// Reset scroll position and image state when visible becomes false
useEffect(() => {
if (!visible && contentRef.current) {
contentRef.current.scrollTop = 0;
setIsImageShrunk(false);
}
}, [visible]);
// SafariBlurToggle animation when isImageShrunk changes
useEffect(() => {
if (!mobileImage) return;
let animationId;
let timeoutId;
const startTime = Date.now();
const duration = 750; // 0.75 seconds
const animate = () => {
const elapsed = Date.now() - startTime;
if (elapsed < duration) {
setSafariBlurToggle((prev) => !prev);
animationId = requestAnimationFrame(animate);
} else {
// Stop toggling after 0.75s
setSafariBlurToggle(false);
}
};
// Start the animation
animationId = requestAnimationFrame(animate);
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [isImageShrunk, mobileImage]);
useEffect(() => {
if (!contentRef.current || !mobileImage) return;
const handleScroll = () => {
// Don't shrink image if page is not visible
if (!visible) return;
const el = contentRef.current;
const scrollTop = el.scrollTop;
const scrollHeight = el.scrollHeight;
const clientHeight = el.clientHeight;
// Check if the container can be scrolled (is overflowing)
const canScroll = scrollHeight > clientHeight;
const shouldShrink = scrollTop > shrinkDistance;
// If container can't be scrolled, keep image shrunk if it's already shrunk
if (!canScroll && isImageShrunk) {
return; // Keep the image shrunk
}
// If container can be scrolled, allow image to un-shrink when at top
if (canScroll) {
if (scrollTop <= shrinkDistance) {
setIsImageShrunk(false);
} else if (shouldShrink) {
setIsImageShrunk(true);
}
} else if (shouldShrink) {
// If container can't be scrolled but we're past threshold, shrink
setIsImageShrunk(true);
}
};
const el = contentRef.current;
el.addEventListener("scroll", handleScroll);
return () => {
el.removeEventListener("scroll", handleScroll);
};
}, [mobileImage, visible, isImageShrunk]);
return (
<div
className={
!isMobile
? "th-page-container"
: "th-page-container th-page-container-mobile"
}
style={{
"--th-backgroundColor": theme?.backgroundColor,
"--th-textColor": theme?.textColor,
background: "var(--th-backgroundColor)",
}}
>
<div
className={`th-header${visible == false ? " th-header-hidden" : ""}${
isLargeMobile ? " th-header-mobile" : ""
}${mobileImage ? " th-header-float" : ""}`}
style={{
"--th-textColor":
pageData?.invertHeader && isMobile
? theme?.backgroundColor
: theme?.textColor,
}}
>
<HeaderLogo
themes={themes}
currentTheme={pageData.theme}
large={id === 0 && !isSubPage}
/>
{showClose && <CloseButton onClick={onClose} />}
{showMenu && id != 0 && !isSubPage && (
<MenuButton isInverted={pageData.invertHeader && isMobile} />
)}
</div>
{mobileImage && (
<div
className={`th-page-mobile-image-wrapper${
visible == false ? ` th-page-mobile-image-wrapper-hidden` : ``
}${isImageShrunk ? ` th-page-mobile-image-wrapper-shrunk` : ``}`}
>
<div
className={`th-top-gradient ${safariBlurToggle ? "refresh" : ""}`}
></div>
<ImageCarousel page={pageData} className="th-mobile-image" />
</div>
)}
<Content
className={`th-page-content${
visible == false ? " th-page-content-hidden" : ""
}${isLargeMobile ? " th-page-content-mobile" : ""}`}
ref={contentRef}
>
<div className="th-content-container-wrapper">
<div
className={`th-content-container ${
mobileImage ? " th-content-container-mobile-image" : ""
}`}
>
<ContentRenderer content={pageData?.content} />
</div>
</div>
</Content>
{pageData?.showScroll == true && <ScrollIcon visible={visible} />}
</div>
);
};
Page.propTypes = {
id: PropTypes.number,
pageData: PropTypes.object,
theme: PropTypes.shape({
backgroundColor: PropTypes.any,
textColor: PropTypes.any,
}),
isSubPage: PropTypes.bool,
showClose: PropTypes.bool,
showMenu: PropTypes.bool,
themes: PropTypes.any,
visible: PropTypes.bool,
onClose: PropTypes.func,
};
export default Page;