import { Sections } from "@/i18n/translations"; import { useLayoutEffect } from "react"; export const linksOrder: Array = [ "about", "tickets", "cfp", "details", "contact", ] /** * Those links need to be reverted to account for the smallest section at the bottom. * This way the intersection still pops the event at correct time, but now * we account for 'contact' too! */ const reversedLinks = linksOrder.toReversed(); export function useColorSections(parent: React.RefObject, previous: React.RefObject, forceIgnore: React.MutableRefObject) { const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); useLayoutEffect(() => { if (prefersReducedMotion.matches) { return; } if (parent.current === null) return; const options = { root: null, rootMargin: "80px 0px 0px 0px", // Top 60% of viewport should matter threshold: 0.4, // Adjust the visibility threshold as needed }; const sections = linksOrder.map(value => document.getElementById(value)); const subs = linksOrder.reduce((acc, value) => { acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!; return acc; }, {} as Record); const links = linksOrder.reduce((acc, value) => { acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!; return acc; }, {} as Record); // Set of currently intersecting sections by ID. let intersecting: Set = new Set; function setCurrentUnderline(id: typeof linksOrder[number]) { for (const sub of Object.values(subs)) { sub.classList.remove('scale-x-100'); } for (const link of Object.values(links)) { link.classList.remove('text-primary'); } previous.current = id; subs[previous.current]?.classList.add('scale-x-100'); links[previous.current]?.classList.add('text-primary'); } const observer = new IntersectionObserver((entries) => { if (forceIgnore.current) { window.onscrollend = () => { forceIgnore.current = false } return; } // Update intersection set based on diff. const startedIntersecting: Set = new Set; const stoppedIntersecting: Set = new Set; for (const entry of entries) { if (entry.isIntersecting) { startedIntersecting.add(entry.target.id); } else { stoppedIntersecting.add(entry.target.id); } } intersecting = intersecting.difference(stoppedIntersecting); intersecting = intersecting.union(startedIntersecting); // Act upon intersection set to find the lowest intersecting section - // that's our 'active' section. for (const id of reversedLinks) { if (intersecting.has(id)) { setCurrentUnderline(id); break; } } }, options); sections.forEach(section => { if (section) { observer.observe(section); } }); return () => { observer.disconnect() }; }, [forceIgnore, parent, prefersReducedMotion.matches, previous]); // Initialize the colors once useLayoutEffect(() => { if (prefersReducedMotion.matches) { return; } const sub = document.querySelector('[data-sub="' + linksOrder[0] + '"]'); const link = document.querySelector('[href="#' + linksOrder[0] + '"]'); if (sub && link) { sub.classList.add('scale-x-100'); link.classList.add('text-primary'); } }, [prefersReducedMotion.matches]); }