1
- import { memo , useEffect , useMemo , useRef , useState } from 'react'
1
+ import { memo , useCallback , useEffect , useMemo , useRef } from 'react'
2
2
import { motion } from 'framer-motion'
3
+ import { atom , useAtom } from 'jotai'
3
4
import type { ITocItem } from './TocItem'
4
5
5
6
import { RightToLeftTransitionView } from '~/components/ui/transition/RightToLeftTransitionView'
6
7
import { throttle } from '~/lib/_'
7
8
import { useArticleElement } from '~/providers/article/article-element-provider'
8
9
import { clsxm } from '~/utils/helper'
10
+ import { springScrollToElement } from '~/utils/scroller'
9
11
10
12
import { TocItem } from './TocItem'
11
13
@@ -28,6 +30,7 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
28
30
...$article . querySelectorAll ( 'h1,h2,h3,h4,h5,h6' ) ,
29
31
] as HTMLHeadingElement [ ]
30
32
} , [ $article ] )
33
+
31
34
const toc : ITocItem [ ] = useMemo ( ( ) => {
32
35
return Array . from ( $headings ) . map ( ( el , idx ) => {
33
36
const depth = + el . tagName . slice ( 1 )
@@ -76,8 +79,18 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
76
79
[ toc ] ,
77
80
)
78
81
79
- const activeId = useActiveId ( $headings )
82
+ const [ activeId , setActiveId ] = useActiveId ( $headings )
80
83
84
+ const handleScrollTo = useCallback (
85
+ ( i : number , $el : HTMLElement | null , anchorId : string ) => {
86
+ if ( $el ) {
87
+ springScrollToElement ( $el , - 100 ) . then ( ( ) => {
88
+ setActiveId ( anchorId )
89
+ } )
90
+ }
91
+ } ,
92
+ [ ] ,
93
+ )
81
94
return (
82
95
< aside className = { clsxm ( 'st-toc z-[3]' , 'relative font-sans' , className ) } >
83
96
< ul
@@ -92,6 +105,7 @@ export const Toc: Component<TocProps> = ({ useAsWeight, className }) => {
92
105
isActive = { heading . anchorId === activeId }
93
106
key = { heading . title }
94
107
rootDepth = { rootDepth }
108
+ onClick = { handleScrollTo }
95
109
/>
96
110
)
97
111
} ) }
@@ -104,7 +118,7 @@ const MemoedItem = memo<{
104
118
isActive : boolean
105
119
heading : ITocItem
106
120
rootDepth : number
107
- onClick ?: ( i : number ) => void
121
+ onClick ?: ( i : number , $el : HTMLElement | null , anchorId : string ) => void
108
122
// containerRef: any
109
123
} > ( ( props ) => {
110
124
const {
@@ -151,8 +165,10 @@ const MemoedItem = memo<{
151
165
152
166
MemoedItem . displayName = 'MemoedItem'
153
167
168
+ const tocActiveIdAtom = atom < string | null > ( null )
169
+
154
170
function useActiveId ( $headings : HTMLHeadingElement [ ] ) {
155
- const [ activeId , setActiveId ] = useState < string | null > ( )
171
+ const [ activeId , setActiveId ] = useAtom ( tocActiveIdAtom )
156
172
useEffect ( ( ) => {
157
173
const observer = new IntersectionObserver (
158
174
( entries ) => {
@@ -177,5 +193,6 @@ function useActiveId($headings: HTMLHeadingElement[]) {
177
193
observer . disconnect ( )
178
194
}
179
195
} , [ $headings ] )
180
- return activeId
196
+
197
+ return [ activeId , setActiveId ] as const
181
198
}
0 commit comments