From bfb8c529531641992bcd1f3ccf864253b76c8fd0 Mon Sep 17 00:00:00 2001 From: olayway Date: Wed, 1 Jun 2022 16:31:17 +0200 Subject: [PATCH] [site/hooks][m]: useHeadingsObserver simplified --- site/components/Heading.js | 4 +++ site/hooks/useHeadingsObserver.js | 49 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 site/hooks/useHeadingsObserver.js diff --git a/site/components/Heading.js b/site/components/Heading.js index 74b91fd..65cfde0 100644 --- a/site/components/Heading.js +++ b/site/components/Heading.js @@ -7,6 +7,10 @@ export const Heading = ({ level, observer }) => (props) => { * set by observer's `rootMargin` */ if (observer) { observer.observe(document.getElementById(props.id)); + + return () => { + observer.unobserve(document.getElementById(props.id)); + } } }); diff --git a/site/hooks/useHeadingsObserver.js b/site/hooks/useHeadingsObserver.js new file mode 100644 index 0000000..3df4acc --- /dev/null +++ b/site/hooks/useHeadingsObserver.js @@ -0,0 +1,49 @@ +import { useState, useEffect } from 'react'; + + +/* Creates an Intersection Observer to keep track of headings intersecting + * a section of the viewport defined by the rootMargin */ +const getIntersectionObserver = (callback) => { + if (typeof window !== 'undefined') { + return new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + callback(entry); + }); + }, + { + root: null, + rootMargin: "-65px 0% -85% 0%" // 65px is a navbar height + } + ); + } +}; + +const useHeadingsObserver = () => { + const [activeHeading, setActiveHeading] = useState(""); + const [observer, setObserver] = useState(getIntersectionObserver((entry) => { + if (entry.isIntersecting) { + setActiveHeading(entry.target.id); + } + })); + + /* On initial render activeHeading will be `null`, since the observer + * has not been instantiated yet. However, we still want to highlight + * the current heading in the ToC based on the current url. */ + useEffect(() => { + if (!activeHeading) { + return + } + + const tocLink = document.querySelector(`.toc-link[href="#${activeHeading}"]`) + tocLink.classList.add("active"); + + return () => { + tocLink.classList.remove("active") + } + }, [activeHeading]) + + return observer; +} + +export default useHeadingsObserver;