diff --git a/src/renderer/components/native-scroll-area/native-scroll-area.tsx b/src/renderer/components/native-scroll-area/native-scroll-area.tsx index 7fdec8b82..cfdcc8a8c 100644 --- a/src/renderer/components/native-scroll-area/native-scroll-area.tsx +++ b/src/renderer/components/native-scroll-area/native-scroll-area.tsx @@ -1,6 +1,5 @@ -import { useInView } from 'motion/react'; import { useOverlayScrollbars } from 'overlayscrollbars-react'; -import { CSSProperties, forwardRef, ReactNode, Ref, useEffect, useRef, useState } from 'react'; +import { CSSProperties, forwardRef, ReactNode, Ref, useEffect, useRef } from 'react'; import styles from './native-scroll-area.module.css'; @@ -25,12 +24,7 @@ export const NativeScrollArea = forwardRef( ref: Ref, ) => { const { windowBarStyle } = useWindowSettings(); - const containerRef = useRef(null); - const [isPastOffset, setIsPastOffset] = useState(false); - - const isInView = useInView({ - current: pageHeaderProps?.target?.current, - }); + const containerRef = useRef(null); const scrollHandlerRef = useRef(null); @@ -43,26 +37,23 @@ export const NativeScrollArea = forwardRef( } scrollHandlerRef.current = requestAnimationFrame(() => { - if (noHeader) { - return setIsPastOffset(true); + if (noHeader || !pageHeaderProps) { + return; } - if (pageHeaderProps?.target || !pageHeaderProps?.offset) { - return setIsPastOffset(true); + const scrollElement = e?.target as HTMLDivElement; + if (!scrollElement || !containerRef.current) { + return; } - const offset = pageHeaderProps?.offset; - const scrollTop = (e?.target as HTMLDivElement)?.scrollTop; + const offset = pageHeaderProps.offset || 0; + const scrollTop = scrollElement.scrollTop; - if (scrollTop > offset && isPastOffset === false) { - return setIsPastOffset(true); + if (scrollTop > offset) { + containerRef.current.setAttribute('data-scrolled', 'true'); + } else { + containerRef.current.setAttribute('data-scrolled', 'false'); } - - if (scrollTop <= offset && isPastOffset === true) { - return setIsPastOffset(false); - } - - return null; }); }, }, @@ -81,21 +72,22 @@ export const NativeScrollArea = forwardRef( useEffect(() => { if (containerRef.current) { initialize(containerRef.current as HTMLDivElement); + if (!noHeader && pageHeaderProps) { + containerRef.current.setAttribute('data-scrolled', 'false'); + } } - }, [initialize]); + }, [initialize, noHeader, pageHeaderProps]); const mergedRef = useMergedRef(ref, containerRef); - const shouldShowHeader = !noHeader && isPastOffset && !isInView; - return ( <> {windowBarStyle === Platform.WEB &&
} - {shouldShowHeader && ( + {!noHeader && pageHeaderProps && ( )} diff --git a/src/renderer/components/page-header/page-header.module.css b/src/renderer/components/page-header/page-header.module.css index 04603d4f8..07aff5bae 100644 --- a/src/renderer/components/page-header/page-header.module.css +++ b/src/renderer/components/page-header/page-header.module.css @@ -1,8 +1,26 @@ .container { position: relative; z-index: 190; + visibility: hidden; width: 100%; height: 65px; + pointer-events: none; + opacity: 0; + transition: + opacity 0.2s ease-in-out, + visibility 0.2s ease-in-out; +} + +.container[data-visible='true'] { + visibility: visible; + pointer-events: auto; + opacity: 1; +} + +.container.visible { + visibility: visible; + pointer-events: auto; + opacity: 1; } .header { diff --git a/src/renderer/components/page-header/page-header.tsx b/src/renderer/components/page-header/page-header.tsx index 3bc69f0ab..4a0e36e89 100644 --- a/src/renderer/components/page-header/page-header.tsx +++ b/src/renderer/components/page-header/page-header.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx'; +import { useInView } from 'motion/react'; import { AnimatePresence, motion, Variants } from 'motion/react'; -import { CSSProperties, ReactNode, useRef } from 'react'; +import { CSSProperties, ReactNode, RefObject, useEffect, useRef } from 'react'; import styles from './page-header.module.css'; @@ -18,6 +19,8 @@ export interface PageHeaderProps height?: string; isHidden?: boolean; position?: string; + scrollContainerRef?: RefObject; + target?: RefObject; } const variants: Variants = { @@ -39,6 +42,8 @@ export const PageHeader = ({ height, isHidden, position, + scrollContainerRef, + target, ...props }: PageHeaderProps) => { const ref = useRef(null); @@ -46,10 +51,56 @@ export const PageHeader = ({ const { windowBarStyle } = useWindowSettings(); const { mode } = useAppTheme(); + const isInView = useInView({ + current: target?.current || null, + }); + + useEffect(() => { + const headerElement = ref.current as HTMLElement | null; + const scrollContainer = scrollContainerRef?.current; + + if (!scrollContainerRef) { + if (headerElement) { + headerElement.setAttribute('data-visible', isHidden ? 'false' : 'true'); + } + return undefined; + } + + if (!scrollContainer || !headerElement) { + if (headerElement) { + headerElement.setAttribute('data-visible', 'false'); + } + return undefined; + } + + const updateVisibility = () => { + const dataScrolled = scrollContainer.getAttribute('data-scrolled'); + const isScrolled = dataScrolled === 'true'; + const shouldShow = isScrolled && !isInView; + + if (shouldShow) { + headerElement.setAttribute('data-visible', 'true'); + } else { + headerElement.setAttribute('data-visible', 'false'); + } + }; + + updateVisibility(); + + const observer = new MutationObserver(updateVisibility); + observer.observe(scrollContainer, { + attributeFilter: ['data-scrolled'], + attributes: true, + }); + + return () => observer.disconnect(); + }, [isInView, scrollContainerRef, isHidden]); + return ( <>