From 794441374d85d5738e5625283e8a1b2a2d2794f2 Mon Sep 17 00:00:00 2001 From: Todd Riley Date: Wed, 17 Jul 2024 14:59:50 -0400 Subject: [PATCH 1/5] `offsetHeight`, `offsetTop`, and `getBoundingClientRect` properties were always 0 on mobile when using `document.getElementById`. Use the `useLayoutEffect` hook, a ref, and grab the measurement every time the menu opens instead. --- next/src/components/Navigation/NavGroup.tsx | 23 ++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/next/src/components/Navigation/NavGroup.tsx b/next/src/components/Navigation/NavGroup.tsx index e3a15fd..335ce88 100644 --- a/next/src/components/Navigation/NavGroup.tsx +++ b/next/src/components/Navigation/NavGroup.tsx @@ -1,15 +1,19 @@ -import { useIsInsideMobileNavigation } from '@/components/MobileNavigation' +import { + useIsInsideMobileNavigation, + useMobileNavigationStore, +} from '@/components/MobileNavigation' import { useSectionStore } from '@/components/SectionProvider' import { lora, spectral } from '@/lib/fonts' +import { remToPx } from '@/lib/remToPx' import { useInitialValue } from '@/lib/useInitialValue' import clsx from 'clsx/lite' import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' import { useRouter } from 'next/router' +import { useLayoutEffect, useRef, useState } from 'react' import { NavigationGroup } from './config' import NavLink from './NavLink' import { VisibleSectionHighlight } from './VisibleSectionHighlight' -import { remToPx } from '@/lib/remToPx' interface NavGroupProps { group: NavigationGroup @@ -25,15 +29,20 @@ export function NavGroup({ group, className }: NavGroupProps) { [useRouter(), useSectionStore((s: any) => s.sections)], isInsideMobileNavigation, ) + let { isOpen } = useMobileNavigationStore() let isActiveGroup = group.group.href === router.pathname || (group.links && group.links.findIndex((link) => link.href === router.pathname) !== -1) - const groupTitleHeight = document.getElementById( - `${group.group.href}-link`, - )?.offsetHeight + const groupTitleRef = useRef(null) + const [groupTitleHeight, setGroupTitleHeight] = useState(0) + + useLayoutEffect(() => { + const { height } = groupTitleRef.current!.getBoundingClientRect() + setGroupTitleHeight(height) + }, [isOpen]) return (
  • @@ -42,7 +51,7 @@ export function NavGroup({ group, className }: NavGroupProps) { className="text-xs font-semibold text-zinc-900 dark:text-white" >
      From 4e4c5e914e3c4c4087087c1637de8732ab642cd1 Mon Sep 17 00:00:00 2001 From: Todd Riley Date: Wed, 17 Jul 2024 16:14:36 -0400 Subject: [PATCH 2/5] This works sometimes. --- next/src/components/Navigation/NavGroup.tsx | 38 ++++++++++--- .../Navigation/VisibleSectionHighlight.tsx | 54 ++++++------------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/next/src/components/Navigation/NavGroup.tsx b/next/src/components/Navigation/NavGroup.tsx index 335ce88..c1ecfa2 100644 --- a/next/src/components/Navigation/NavGroup.tsx +++ b/next/src/components/Navigation/NavGroup.tsx @@ -10,7 +10,7 @@ import clsx from 'clsx/lite' import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' import { useRouter } from 'next/router' -import { useLayoutEffect, useRef, useState } from 'react' +import { useLayoutEffect, useEffect, useRef, useState } from 'react' import { NavigationGroup } from './config' import NavLink from './NavLink' import { VisibleSectionHighlight } from './VisibleSectionHighlight' @@ -38,10 +38,25 @@ export function NavGroup({ group, className }: NavGroupProps) { const groupTitleRef = useRef(null) const [groupTitleHeight, setGroupTitleHeight] = useState(0) + const sectionRefMap = useRef(new Map()) + const [sectionIdsToBoxes, setSectionIdsToBoxes] = useState<{ + [key: string]: { height: number; top: number } + }>({}) useLayoutEffect(() => { - const { height } = groupTitleRef.current!.getBoundingClientRect() - setGroupTitleHeight(height) + if (isInsideMobileNavigation && !isOpen) return + setGroupTitleHeight(groupTitleRef.current!.offsetHeight) + const _sectionIdsToBoxes: { + [key: string]: { height: number; top: number } + } = {} + for (const key of sectionRefMap.current.keys()) { + const sectionSpan = sectionRefMap.current.get(key)! + _sectionIdsToBoxes[key] = { + height: sectionSpan.offsetHeight, + top: sectionSpan.offsetTop, + } + } + setSectionIdsToBoxes(_sectionIdsToBoxes) }, [isOpen]) return ( @@ -61,8 +76,8 @@ export function NavGroup({ group, className }: NavGroupProps) {
      - {isActiveGroup && ( - + {isActiveGroup && (!isInsideMobileNavigation || isOpen) && ( + )}
      { + if (el && link.href === router.pathname) { + sectionRefMap.current.set('_top', el!) + } + }} href={link.href} active={link.href === router.pathname} > @@ -98,7 +117,12 @@ export function NavGroup({ group, className }: NavGroupProps) { }} > {sections.map((section: any) => ( -
    • +
    • { + if (el) sectionRefMap.current.set(section.id, el!) + }} + key={section.id} + > s.sections), - useSectionStore((s: any) => s.visibleSections), - ], + let visibleSections = useInitialValue( + useSectionStore((s: any) => s.visibleSections), useIsInsideMobileNavigation(), ) - const [uselessCounter, setUselessCounter] = useState(0) - let height = 0 - let top = 0 + const [height, setHeight] = useState(0) + const [top, setTop] = useState(0) - function renderAgain() { - setTimeout(() => { - setUselessCounter(uselessCounter + 1) - }, 100) - } - - const activeNavItem = document.getElementById(`${pathname}-link`) - if (activeNavItem) { - if (visibleSections[0] === '_top' || visibleSections.length === 0) { - height = activeNavItem.offsetHeight - top = activeNavItem.offsetTop - if (top === 0) { - renderAgain() + useLayoutEffect(() => { + let _height: number = 0 + if (visibleSections.length && Object.keys(sectionIdsToBoxes).length > 0) { + const _top = sectionIdsToBoxes[visibleSections[0]].top + for (let i = 0; i < visibleSections.length; i++) { + _height += sectionIdsToBoxes[visibleSections[i]].height } - } else { - top = document.getElementById(`${visibleSections[0]}-li`)!.offsetTop - } - for (let i = 0; i < visibleSections.length; i++) { - if (visibleSections[i] === '_top') continue - height += document.getElementById( - `${visibleSections[i]}-li`, - )!.offsetHeight + _height += 8 + setHeight(_height) + setTop(_top) } - height += 8 - } else { - // Wait a bit and force a re-render. `activeNavItem` should exist - renderAgain() - } + }, [sectionIdsToBoxes, visibleSections]) return ( Date: Wed, 17 Jul 2024 16:26:47 -0400 Subject: [PATCH 3/5] Delete the old approach entirely. --- next/src/components/Navigation/NavGroup.tsx | 62 ++----------------- .../Navigation/VisibleSectionHighlight.tsx | 61 ------------------ 2 files changed, 5 insertions(+), 118 deletions(-) delete mode 100644 next/src/components/Navigation/VisibleSectionHighlight.tsx diff --git a/next/src/components/Navigation/NavGroup.tsx b/next/src/components/Navigation/NavGroup.tsx index c1ecfa2..823a769 100644 --- a/next/src/components/Navigation/NavGroup.tsx +++ b/next/src/components/Navigation/NavGroup.tsx @@ -1,7 +1,4 @@ -import { - useIsInsideMobileNavigation, - useMobileNavigationStore, -} from '@/components/MobileNavigation' +import { useIsInsideMobileNavigation } from '@/components/MobileNavigation' import { useSectionStore } from '@/components/SectionProvider' import { lora, spectral } from '@/lib/fonts' import { remToPx } from '@/lib/remToPx' @@ -10,10 +7,8 @@ import clsx from 'clsx/lite' import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' import { useRouter } from 'next/router' -import { useLayoutEffect, useEffect, useRef, useState } from 'react' import { NavigationGroup } from './config' import NavLink from './NavLink' -import { VisibleSectionHighlight } from './VisibleSectionHighlight' interface NavGroupProps { group: NavigationGroup @@ -29,36 +24,11 @@ export function NavGroup({ group, className }: NavGroupProps) { [useRouter(), useSectionStore((s: any) => s.sections)], isInsideMobileNavigation, ) - let { isOpen } = useMobileNavigationStore() - let isActiveGroup = group.group.href === router.pathname || (group.links && group.links.findIndex((link) => link.href === router.pathname) !== -1) - const groupTitleRef = useRef(null) - const [groupTitleHeight, setGroupTitleHeight] = useState(0) - const sectionRefMap = useRef(new Map()) - const [sectionIdsToBoxes, setSectionIdsToBoxes] = useState<{ - [key: string]: { height: number; top: number } - }>({}) - - useLayoutEffect(() => { - if (isInsideMobileNavigation && !isOpen) return - setGroupTitleHeight(groupTitleRef.current!.offsetHeight) - const _sectionIdsToBoxes: { - [key: string]: { height: number; top: number } - } = {} - for (const key of sectionRefMap.current.keys()) { - const sectionSpan = sectionRefMap.current.get(key)! - _sectionIdsToBoxes[key] = { - height: sectionSpan.offsetHeight, - top: sectionSpan.offsetTop, - } - } - setSectionIdsToBoxes(_sectionIdsToBoxes) - }, [isOpen]) - return (
    • -
      - - {isActiveGroup && (!isInsideMobileNavigation || isOpen) && ( - - )} - -
      +
      +
        {group.links?.map((link) => ( - { - if (el && link.href === router.pathname) { - sectionRefMap.current.set('_top', el!) - } - }} - href={link.href} - active={link.href === router.pathname} - > + {link.title} @@ -117,12 +70,7 @@ export function NavGroup({ group, className }: NavGroupProps) { }} > {sections.map((section: any) => ( -
      • { - if (el) sectionRefMap.current.set(section.id, el!) - }} - key={section.id} - > +
      • s.visibleSections), - useIsInsideMobileNavigation(), - ) - - const [height, setHeight] = useState(0) - const [top, setTop] = useState(0) - - useLayoutEffect(() => { - let _height: number = 0 - if (visibleSections.length && Object.keys(sectionIdsToBoxes).length > 0) { - const _top = sectionIdsToBoxes[visibleSections[0]].top - for (let i = 0; i < visibleSections.length; i++) { - _height += sectionIdsToBoxes[visibleSections[i]].height - } - _height += 8 - setHeight(_height) - setTop(_top) - } - }, [sectionIdsToBoxes, visibleSections]) - - return ( - - ) -} From 037307746cd2a023b1cb93ff914dedc409c98d39 Mon Sep 17 00:00:00 2001 From: Todd Riley Date: Wed, 17 Jul 2024 16:39:02 -0400 Subject: [PATCH 4/5] Use conditionally visible absolutely positioned static background elements to highlight the currently active route. --- next/src/components/Navigation/NavGroup.tsx | 32 +++++++++++++++++++-- next/src/components/Navigation/NavLink.tsx | 2 +- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/next/src/components/Navigation/NavGroup.tsx b/next/src/components/Navigation/NavGroup.tsx index 823a769..4c0a5dc 100644 --- a/next/src/components/Navigation/NavGroup.tsx +++ b/next/src/components/Navigation/NavGroup.tsx @@ -38,9 +38,23 @@ export function NavGroup({ group, className }: NavGroupProps) { - {group.group.title} + {group.group.title}{' '} + {group.group.href === router.pathname && ( +
        + )}
        @@ -54,6 +68,20 @@ export function NavGroup({ group, className }: NavGroupProps) { > {link.title} + {link.href === router.pathname && ( +
        + )} {link.href === router.pathname && sections.length > 0 && ( diff --git a/next/src/components/Navigation/NavLink.tsx b/next/src/components/Navigation/NavLink.tsx index 7fd8355..ceb76f4 100644 --- a/next/src/components/Navigation/NavLink.tsx +++ b/next/src/components/Navigation/NavLink.tsx @@ -22,7 +22,7 @@ function NavLink( id={id} aria-current={active ? 'page' : undefined} className={clsx( - 'flex justify-between gap-2 py-1 pr-3 text-sm transition', + 'relative flex justify-between gap-2 py-1 pr-3 text-sm transition', isAnchorLink ? 'pl-7' : 'pl-4', active ? 'text-zinc-900 dark:text-white' From d2012eb2a180fdf936d5cfd8e3be334da76739ef Mon Sep 17 00:00:00 2001 From: Todd Riley Date: Wed, 17 Jul 2024 16:41:06 -0400 Subject: [PATCH 5/5] Delete unused import. --- next/src/components/Navigation/NavGroup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/next/src/components/Navigation/NavGroup.tsx b/next/src/components/Navigation/NavGroup.tsx index 4c0a5dc..3418645 100644 --- a/next/src/components/Navigation/NavGroup.tsx +++ b/next/src/components/Navigation/NavGroup.tsx @@ -1,7 +1,6 @@ import { useIsInsideMobileNavigation } from '@/components/MobileNavigation' import { useSectionStore } from '@/components/SectionProvider' import { lora, spectral } from '@/lib/fonts' -import { remToPx } from '@/lib/remToPx' import { useInitialValue } from '@/lib/useInitialValue' import clsx from 'clsx/lite' import { AnimatePresence, motion } from 'framer-motion'