Make nav sticky for top-level pages

This commit is contained in:
Sam Becker 2025-03-22 22:15:43 -05:00
parent 1060a5d061
commit 805a4193a7
4 changed files with 83 additions and 1 deletions

View File

@ -10,6 +10,7 @@ import {
isPathAdmin,
isPathFeed,
isPathGrid,
isPathTopLevel,
isPathProtected,
isPathSignIn,
} from '@/app/paths';
@ -19,6 +20,8 @@ import {
HAS_DEFINED_SITE_DESCRIPTION,
SITE_DESCRIPTION,
} from './config';
import { useRef } from 'react';
import useStickyNav from './useStickyNav';
const NAV_HEIGHT_CLASS = HAS_DEFINED_SITE_DESCRIPTION
? 'min-h-[4rem] sm:min-h-[5rem]'
@ -29,9 +32,17 @@ export default function Nav({
}: {
siteDomainOrTitle: string;
}) {
const pathname = usePathname();
const ref = useRef<HTMLElement>(null);
const pathname = usePathname();
const showNav = !isPathSignIn(pathname);
const isHome = isPathTopLevel(pathname);
const {
isNavSticky,
shouldHideStickyNav,
shouldAnimateStickyNav,
} = useStickyNav(ref, isHome);
const renderLink = (
text: string,
@ -55,6 +66,9 @@ export default function Nav({
return (
<AppGrid
className={clsx(
isNavSticky && 'sticky top-0 z-10',
)}
contentMain={
<AnimateItems
animateOnFirstLoadOnly
@ -63,8 +77,14 @@ export default function Nav({
items={showNav
? [<nav
key="nav"
ref={ref}
className={clsx(
'flex items-center w-full',
'bg-main',
shouldAnimateStickyNav && 'transition-transform duration-200',
shouldHideStickyNav
? 'translate-y-[-100%]'
: 'translate-y-0',
NAV_HEIGHT_CLASS,
)}>
<ViewSwitcher

View File

@ -249,12 +249,20 @@ export const isPathFocalLengthPhoto = (pathname = '') =>
export const checkPathPrefix = (pathname = '', prefix: string) =>
pathname.toLowerCase().startsWith(prefix);
export const isPathRoot = (pathname?: string) =>
pathname === PATH_ROOT;
export const isPathGrid = (pathname?: string) =>
checkPathPrefix(pathname, PATH_GRID);
export const isPathFeed = (pathname?: string) =>
checkPathPrefix(pathname, PATH_FEED);
export const isPathTopLevel = (pathname?: string) =>
isPathRoot(pathname)||
isPathGrid(pathname) ||
isPathFeed(pathname);
export const isPathSignIn = (pathname?: string) =>
checkPathPrefix(pathname, PATH_SIGN_IN);

33
src/app/useStickyNav.ts Normal file
View File

@ -0,0 +1,33 @@
import useScrollDirection from '@/utility/useScrollDirection';
import { RefObject } from 'react';
export default function useStickyNav(
ref: RefObject<HTMLElement | null>,
isEnabled = true,
) {
const { scrollDirection, lastScrollY } = useScrollDirection();
const navHeight = ref.current?.clientHeight ?? 0;
const hasScrolledPastNav = lastScrollY > navHeight;
const isNavSticky = isEnabled && (
hasScrolledPastNav ||
scrollDirection === 'up'
);
const shouldHideStickyNav =
isNavSticky &&
scrollDirection === 'down';
const shouldAnimateStickyNav =
isNavSticky &&
lastScrollY > navHeight * 2 ||
scrollDirection === 'up';
return {
isNavSticky,
shouldHideStickyNav,
shouldAnimateStickyNav,
};
};

View File

@ -0,0 +1,21 @@
import { useEffect, useState } from 'react';
export default function useScrollDirection() {
const [lastScrollY, setLastScrollY] = useState(0);
const [scrollDirection, setScrollDirection] = useState<'up' | 'down'>('up');
useEffect(() => {
const handleScroll = () => {
setScrollDirection(window.scrollY > lastScrollY ? 'down' : 'up');
setLastScrollY(window.scrollY);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [lastScrollY]);
return {
scrollDirection,
lastScrollY,
};
}