212 lines
6.0 KiB
JavaScript
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;
|