diff --git a/src/components/Layout/Toc.tsx b/src/components/Layout/Toc.tsx index e2e2169fd53..5fcfc51c645 100644 --- a/src/components/Layout/Toc.tsx +++ b/src/components/Layout/Toc.tsx @@ -13,12 +13,56 @@ import cx from 'classnames'; import {useTocHighlight} from './useTocHighlight'; import type {Toc} from '../MDX/TocContext'; +let animationFrameId: number; + +const smoothScrollTo = (targetId: string) => { + const element = document.getElementById(targetId); + if (!element) return; + + (window as any).__isAutoScrolling = true; + + const headerOffset = 84; + const startPosition = window.scrollY; + const elementPosition = element.getBoundingClientRect().top; + const targetPosition = startPosition + elementPosition - headerOffset; + const distance = targetPosition - startPosition; + const duration = 300; + let startTime: number | null = null; + + const easeInOutCubic = (t: number): number => { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; + }; + + const animation = (currentTime: number) => { + if (startTime === null) startTime = currentTime; + const timeElapsed = currentTime - startTime; + const progress = Math.min(timeElapsed / duration, 1); + const easedProgress = easeInOutCubic(progress); + + window.scrollTo(0, startPosition + distance * easedProgress); + + if (progress < 1) { + animationFrameId = requestAnimationFrame(animation); + } else { + (window as any).__isAutoScrolling = false; + } + }; + + cancelAnimationFrame(animationFrameId); + animationFrameId = requestAnimationFrame(animation); +}; + +const getAnchorIdFromUrl = (url: string) => { + return url.startsWith('#') ? url.substring(1) : url; +}; + export function Toc({headings}: {headings: Toc}) { const {currentIndex} = useTocHighlight(); // TODO: We currently have a mismatch between the headings in the document // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). // Select the max TOC item we have here for now, but remove this after the fix. const selectedIndex = Math.min(currentIndex, headings.length - 1); + return (