update page header render to use css instead of state

This commit is contained in:
jeffvli
2025-11-22 04:08:38 -08:00
parent b159dd4452
commit 1c5212d756
3 changed files with 89 additions and 28 deletions
@@ -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<HTMLDivElement>,
) => {
const { windowBarStyle } = useWindowSettings();
const containerRef = useRef(null);
const [isPastOffset, setIsPastOffset] = useState(false);
const isInView = useInView({
current: pageHeaderProps?.target?.current,
});
const containerRef = useRef<HTMLDivElement | null>(null);
const scrollHandlerRef = useRef<null | number>(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 && <div className={styles.dragContainer} />}
{shouldShowHeader && (
{!noHeader && pageHeaderProps && (
<PageHeader
animated
isHidden={false}
position="absolute"
scrollContainerRef={containerRef}
{...pageHeaderProps}
/>
)}
@@ -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 {
@@ -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<HTMLDivElement | null>;
target?: RefObject<HTMLElement | null>;
}
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 (
<>
<Flex
className={styles.container}
data-visible="false"
ref={ref}
style={{ height, position: position as CSSProperties['position'] }}
{...props}