1
+ 'use client' ;
2
+
3
+ import { useEffect , useState } from 'react' ;
4
+
5
+ interface Heading {
6
+ id : string ;
7
+ text : string ;
8
+ level : number ;
9
+ }
10
+
11
+ export default function TableOfContents ( ) {
12
+ const [ headings , setHeadings ] = useState < Heading [ ] > ( [ ] ) ;
13
+ const [ activeId , setActiveId ] = useState < string > ( '' ) ;
14
+
15
+ useEffect ( ( ) => {
16
+ const elements = Array . from ( document . querySelectorAll ( 'h2, h3, h4' ) ) ;
17
+ const items = elements . map ( ( element ) => ( {
18
+ id : element . id ,
19
+ text : element . textContent || '' ,
20
+ level : Number ( element . tagName . charAt ( 1 ) ) ,
21
+ } ) ) ;
22
+ setHeadings ( items ) ;
23
+
24
+ const observer = new IntersectionObserver (
25
+ ( entries ) => {
26
+ entries . forEach ( ( entry ) => {
27
+ if ( entry . isIntersecting ) {
28
+ setActiveId ( entry . target . id ) ;
29
+ }
30
+ } ) ;
31
+ } ,
32
+ { rootMargin : '-20% 0px -80% 0px' }
33
+ ) ;
34
+
35
+ elements . forEach ( ( elem ) => observer . observe ( elem ) ) ;
36
+ return ( ) => observer . disconnect ( ) ;
37
+ } , [ ] ) ;
38
+
39
+ if ( headings . length === 0 ) return null ;
40
+
41
+ return (
42
+ < nav className = "sticky top-24 max-h-[calc(100vh-6rem)] overflow-auto p-4 rounded-lg bg-base-200" >
43
+ < h2 className = "text-lg font-semibold mb-4" > Table of Contents</ h2 >
44
+ < ul className = "space-y-2" >
45
+ { headings . map ( ( heading ) => (
46
+ < li
47
+ key = { heading . id }
48
+ style = { { marginLeft : `${ ( heading . level - 2 ) * 1 } rem` } }
49
+ >
50
+ < a
51
+ href = { `#${ heading . id } ` }
52
+ className = { `block hover:text-primary transition-colors ${ activeId === heading . id ? 'text-primary font-medium' : ''
53
+ } `}
54
+ onClick = { ( e ) => {
55
+ e . preventDefault ( ) ;
56
+ document . querySelector ( `#${ heading . id } ` ) ?. scrollIntoView ( {
57
+ behavior : 'smooth' ,
58
+ } ) ;
59
+ } }
60
+ >
61
+ { heading . text }
62
+ </ a >
63
+ </ li >
64
+ ) ) }
65
+ </ ul >
66
+ </ nav >
67
+ ) ;
68
+ }
0 commit comments