Refactor sticky nav hook/components

This commit is contained in:
Sam Becker 2025-04-03 18:10:43 -05:00
parent c959e88ebb
commit ffde3cedaf
3 changed files with 42 additions and 39 deletions

View File

@ -36,9 +36,8 @@ export default function Nav({
const showNav = !isPathSignIn(pathname); const showNav = !isPathSignIn(pathname);
const { const {
isNavSticky, classNameStickyContainer,
shouldHideStickyNav, classNameStickyNav,
shouldAnimateStickyNav,
} = useStickyNav(ref); } = useStickyNav(ref);
const renderLink = ( const renderLink = (
@ -63,9 +62,7 @@ export default function Nav({
return ( return (
<AppGrid <AppGrid
className={clsx( className={classNameStickyContainer}
isNavSticky && 'sticky top-0 z-10 pointer-events-none',
)}
classNameMain='pointer-events-auto' classNameMain='pointer-events-auto'
contentMain={ contentMain={
<AnimateItems <AnimateItems
@ -77,15 +74,11 @@ export default function Nav({
key="nav" key="nav"
ref={ref} ref={ref}
className={clsx( className={clsx(
'w-full', 'w-full flex items-center bg-main',
NAV_HEIGHT_CLASS,
// Enlarge nav to ensure it fully masks underlying content // Enlarge nav to ensure it fully masks underlying content
'md:w-[calc(100%+8px)] md:translate-x-[-4px] md:px-[4px]', 'md:w-[calc(100%+8px)] md:translate-x-[-4px] md:px-[4px]',
'flex items-center bg-main', classNameStickyNav,
shouldAnimateStickyNav && 'transition-transform duration-200',
shouldHideStickyNav
? 'translate-y-[-100%]'
: 'translate-y-0',
NAV_HEIGHT_CLASS,
)}> )}>
<ViewSwitcher <ViewSwitcher
currentSelection={switcherSelectionForPath()} currentSelection={switcherSelectionForPath()}

View File

@ -1,15 +1,16 @@
import useScrollDirection from '@/utility/useScrollDirection'; import useScrollDirection from '@/utility/useScrollDirection';
import { RefObject } from 'react'; import clsx from 'clsx/lite';
import { RefObject, useMemo } from 'react';
export default function useStickyNav( export default function useStickyNav(
ref: RefObject<HTMLElement | null>, ref: RefObject<HTMLElement | null>,
isEnabled = true, isEnabled = true,
) { ) {
const { scrollDirection, lastScrollY } = useScrollDirection(); const { scrollDirection, scrollY } = useScrollDirection();
const navHeight = ref.current?.clientHeight ?? 0; const navHeight = ref.current?.clientHeight ?? 0;
const hasScrolledPastNav = lastScrollY > navHeight; const hasScrolledPastNav = scrollY > navHeight;
const isNavSticky = isEnabled && ( const isNavSticky = isEnabled && (
hasScrolledPastNav || hasScrolledPastNav ||
@ -21,13 +22,20 @@ export default function useStickyNav(
scrollDirection === 'down'; scrollDirection === 'down';
const shouldAnimateStickyNav = const shouldAnimateStickyNav =
isNavSticky && isNavSticky && (
lastScrollY > navHeight * 2 || scrollY > navHeight * 2 ||
scrollDirection === 'up'; scrollDirection === 'up'
);
return { const classNames = useMemo(() => ({
isNavSticky, classNameStickyContainer: clsx(
shouldHideStickyNav, isNavSticky && 'sticky top-0 z-10 pointer-events-none',
shouldAnimateStickyNav, ),
}; classNameStickyNav: clsx(
shouldHideStickyNav ? 'translate-y-[-100%]' : 'translate-y-0',
shouldAnimateStickyNav && 'transition-transform duration-200',
),
}), [isNavSticky, shouldAnimateStickyNav, shouldHideStickyNav]);
return classNames;
}; };

View File

@ -1,26 +1,28 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState } from 'react';
export default function useScrollDirection() { export default function useScrollDirection() {
const [scrollDirection, setScrollDirection] = useState<'up' | 'down'>('down'); const [scrollInfo, setScrollInfo] = useState({
scrollDirection: 'down' as 'up' | 'down',
const lastScrollY = useRef(0); scrollY: 0,
});
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const pageHeight = const pageHeight = (
document.documentElement.scrollHeight - window.innerHeight; document.documentElement.scrollHeight -
setScrollDirection(( window.innerHeight
window.scrollY >= lastScrollY.current || );
lastScrollY.current > pageHeight setScrollInfo(prev => ({
) ? 'down' : 'up'); scrollDirection: (
lastScrollY.current = window.scrollY; window.scrollY >= prev.scrollY ||
prev.scrollY > pageHeight
) ? 'down' : 'up',
scrollY: window.scrollY,
}));
}; };
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, []); }, []);
return { return scrollInfo;
scrollDirection,
lastScrollY: lastScrollY.current,
};
} }