Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit fdf7155

Browse files
Update scrollbar to change on heading scroll rather than scroll percent (#626)
1 parent cfbb50f commit fdf7155

File tree

3 files changed

+65
-41
lines changed

3 files changed

+65
-41
lines changed

src/app/[[...slug]]/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Prose } from '@/components/Prose'
33
import React from 'react'
44
import { allDocs } from '@/content'
55
import DocToc from '@/components/layout/DocToC'
6-
import { Feedback } from '@/components/Feedback'
76
import { Button } from '@/components/ui/button'
87
import { GitHubIcon } from '@/components/icons/GitHubIcon'
98
import Breadcrumbs from '@/components/Breadcrumbs'

src/components/layout/DocToC.tsx

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,84 @@
11
'use client'
22

33
import type { Doc } from '@/content'
4-
import React, { useRef } from 'react'
5-
import { Badge } from '../ui/badge'
4+
import { useRef, useLayoutEffect, useState, useEffect } from 'react'
65
import Link from 'next/link'
7-
import { FaFacebook, FaFilePdf, FaLinkedin } from 'react-icons/fa'
86
import { DocTracingBeam } from './DocTracingBeam'
9-
import { BASE_URL } from '@/lib/constants'
10-
import { title } from 'radash'
117
import { Button } from '../ui/button'
128
import { GitHubIcon } from '../icons/GitHubIcon'
13-
14-
interface Props {
15-
doc: Doc
16-
articleRef?: React.RefObject<HTMLDivElement>
17-
}
9+
import { useMotionValue } from 'framer-motion'
10+
import { cn } from '@/lib/utils'
1811

1912
interface Toc {
2013
url: string
2114
value: string
2215
depth: number
2316
}
2417

25-
const DocToC: React.FC<Props> = ({ doc }) => {
26-
const articleRef = useRef<HTMLDivElement>(null)
18+
const DocToC = ({ doc }: { doc: Doc }) => {
19+
const initial = 14
20+
const sectionSize = 28
21+
const offset = 10
22+
23+
const y1 = useMotionValue(0)
24+
const y2 = useMotionValue(0)
25+
26+
const [activeIndex, setActiveIndex] = useState(-1)
27+
28+
useEffect(() => {
29+
const headings = document
30+
.querySelectorAll<HTMLHeadingElement>('h2.md-content-header')
31+
.values()
32+
.toArray()
33+
34+
const options = {
35+
root: null,
36+
rootMargin: '0px',
37+
threshold: 1, // Adjust based on when you want to highlight
38+
}
39+
40+
const observer = new IntersectionObserver((entries) => {
41+
entries.forEach((entry) => {
42+
if (entry.isIntersecting) {
43+
const index = headings.findIndex(
44+
(heading) => heading.textContent === entry.target.textContent,
45+
)
46+
47+
y2.set(initial + (index * sectionSize + offset))
48+
49+
setActiveIndex(index)
50+
}
51+
})
52+
}, options)
53+
54+
headings.forEach((h2) => observer.observe(h2))
55+
56+
return () => {
57+
headings.forEach((h2) => observer.unobserve(h2))
58+
}
59+
}, [])
2760

2861
return (
29-
<div className="hidden h-full min-w-52 md:block" ref={articleRef}>
62+
<div className="hidden h-full min-w-52 md:block">
3063
<aside className="sticky top-[calc(var(--header-height)+1px+2rem)] max-h-[calc(100vh-var(--header-height)-3rem)] min-w-40 space-y-6">
3164
{doc.toc.length ? (
3265
<div className="relative flex flex-col">
3366
<p className="mb-2 font-mono text-sm uppercase dark:text-zinc-300">
3467
On this page
3568
</p>
36-
<DocTracingBeam targetRef={articleRef}>
69+
<DocTracingBeam y1={y1} y2={y2}>
3770
<ol className="flex flex-col gap-y-1 pl-4 text-sm font-medium">
3871
{doc.toc.map((item: Toc, i: number) => {
3972
return (
4073
<li key={item.url + i}>
4174
<Link
4275
href={item.url}
43-
className="text-2xs text-muted-foreground transition-colors hover:text-zinc-900 dark:hover:text-zinc-200"
76+
className={cn(
77+
'text-2xs',
78+
activeIndex === i
79+
? 'text-zinc-900 dark:text-zinc-200'
80+
: 'text-muted-foreground transition-colors hover:text-zinc-900 dark:hover:text-zinc-200',
81+
)}
4482
>
4583
{item.value}
4684
</Link>

src/components/layout/DocTracingBeam.tsx

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
'use client'
22

33
import React, { useEffect, useRef, useState } from 'react'
4-
import { motion, useTransform, useScroll, useSpring } from 'framer-motion'
4+
import {
5+
motion,
6+
useTransform,
7+
useSpring,
8+
useMotionValue,
9+
MotionValue,
10+
} from 'framer-motion'
511
import { cn } from '@/lib/utils'
612

7-
// TODO this should set the height based off the h2 offset heights, not current scroll progress
813
export const DocTracingBeam = ({
914
children,
1015
className,
11-
targetRef,
16+
y1,
17+
y2,
1218
}: {
1319
children: React.ReactNode
1420
className?: string
15-
targetRef: React.RefObject<HTMLDivElement>
21+
y1: MotionValue<number>
22+
y2: MotionValue<number>
1623
}) => {
17-
const { scrollYProgress } = useScroll({
18-
target: targetRef,
19-
offset: ['start start', 'end end'],
20-
})
21-
2224
const contentRef = useRef<HTMLDivElement>(null)
2325
const [svgHeight, setSvgHeight] = useState(0)
2426

@@ -28,21 +30,6 @@ export const DocTracingBeam = ({
2830
}
2931
}, [])
3032

31-
const y1 = useSpring(
32-
useTransform(scrollYProgress, [0, 0.8], [0, svgHeight]),
33-
{
34-
stiffness: 500,
35-
damping: 90,
36-
},
37-
)
38-
const y2 = useSpring(
39-
useTransform(scrollYProgress, [0, 1], [0, svgHeight - 210]),
40-
{
41-
stiffness: 500,
42-
damping: 90,
43-
},
44-
)
45-
4633
return (
4734
<motion.div
4835
className={cn('relative mx-auto h-full w-full max-w-4xl', className)}
@@ -81,7 +68,7 @@ export const DocTracingBeam = ({
8168
x1="0"
8269
x2="0"
8370
y1={y1} // set y1 for gradient
84-
y2={y2} // set y2 for gradient
71+
y2={useSpring(y2)} // set y2 for gradient
8572
>
8673
<stop stopColor="var(--primary-500)" stopOpacity="0"></stop>
8774
<stop stopColor="var(--primary-500)"></stop>

0 commit comments

Comments
 (0)