1
1
'use client'
2
2
3
- import { type FC , useEffect } from 'react'
3
+ import { useEffect , useState } from 'react'
4
4
import { useContext } from 'use-context-selector'
5
+ import { useTranslation } from 'react-i18next'
6
+ import { RiListUnordered } from '@remixicon/react'
5
7
import TemplateEn from './template/template.en.mdx'
6
8
import TemplateZh from './template/template.zh.mdx'
7
9
import I18n from '@/context/i18n'
@@ -10,25 +12,106 @@ import { LanguagesSupported } from '@/i18n/language'
10
12
type DocProps = {
11
13
apiBaseUrl : string
12
14
}
13
- const Doc : FC < DocProps > = ( {
14
- apiBaseUrl,
15
- } ) => {
15
+
16
+ const Doc = ( { apiBaseUrl } : DocProps ) => {
16
17
const { locale } = useContext ( I18n )
18
+ const { t } = useTranslation ( )
19
+ const [ toc , setToc ] = useState < Array < { href : string ; text : string } > > ( [ ] )
20
+ const [ isTocExpanded , setIsTocExpanded ] = useState ( false )
17
21
22
+ // Set initial TOC expanded state based on screen width
18
23
useEffect ( ( ) => {
19
- const hash = location . hash
20
- if ( hash )
21
- document . querySelector ( hash ) ?. scrollIntoView ( )
24
+ const mediaQuery = window . matchMedia ( '(min-width: 1280px)' )
25
+ setIsTocExpanded ( mediaQuery . matches )
22
26
} , [ ] )
23
27
28
+ // Extract TOC from article content
29
+ useEffect ( ( ) => {
30
+ const extractTOC = ( ) => {
31
+ const article = document . querySelector ( 'article' )
32
+ if ( article ) {
33
+ const headings = article . querySelectorAll ( 'h2' )
34
+ const tocItems = Array . from ( headings ) . map ( ( heading ) => {
35
+ const anchor = heading . querySelector ( 'a' )
36
+ if ( anchor ) {
37
+ return {
38
+ href : anchor . getAttribute ( 'href' ) || '' ,
39
+ text : anchor . textContent || '' ,
40
+ }
41
+ }
42
+ return null
43
+ } ) . filter ( ( item ) : item is { href : string ; text : string } => item !== null )
44
+ setToc ( tocItems )
45
+ }
46
+ }
47
+
48
+ setTimeout ( extractTOC , 0 )
49
+ } , [ locale ] )
50
+
51
+ // Handle TOC item click
52
+ const handleTocClick = ( e : React . MouseEvent < HTMLAnchorElement > , item : { href : string ; text : string } ) => {
53
+ e . preventDefault ( )
54
+ const targetId = item . href . replace ( '#' , '' )
55
+ const element = document . getElementById ( targetId )
56
+ if ( element ) {
57
+ const scrollContainer = document . querySelector ( '.scroll-container' )
58
+ if ( scrollContainer ) {
59
+ const headerOffset = - 40
60
+ const elementTop = element . offsetTop - headerOffset
61
+ scrollContainer . scrollTo ( {
62
+ top : elementTop ,
63
+ behavior : 'smooth' ,
64
+ } )
65
+ }
66
+ }
67
+ }
68
+
24
69
return (
25
- < article className = 'mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl' >
26
- {
27
- locale !== LanguagesSupported [ 1 ]
70
+ < div className = "flex" >
71
+ < div className = { `fixed right-16 top-32 z-10 transition-all ${ isTocExpanded ? 'w-64' : 'w-10' } ` } >
72
+ { isTocExpanded
73
+ ? (
74
+ < nav className = "toc w-full bg-gray-50 p-4 rounded-lg shadow-md max-h-[calc(100vh-150px)] overflow-y-auto" >
75
+ < div className = "flex justify-between items-center mb-4" >
76
+ < h3 className = "text-lg font-semibold" > { t ( 'appApi.develop.toc' ) } </ h3 >
77
+ < button
78
+ onClick = { ( ) => setIsTocExpanded ( false ) }
79
+ className = "text-gray-500 hover:text-gray-700"
80
+ >
81
+ ✕
82
+ </ button >
83
+ </ div >
84
+ < ul className = "space-y-2" >
85
+ { toc . map ( ( item , index ) => (
86
+ < li key = { index } >
87
+ < a
88
+ href = { item . href }
89
+ className = "text-gray-600 hover:text-gray-900 hover:underline transition-colors duration-200"
90
+ onClick = { e => handleTocClick ( e , item ) }
91
+ >
92
+ { item . text }
93
+ </ a >
94
+ </ li >
95
+ ) ) }
96
+ </ ul >
97
+ </ nav >
98
+ )
99
+ : (
100
+ < button
101
+ onClick = { ( ) => setIsTocExpanded ( true ) }
102
+ className = "w-10 h-10 bg-gray-50 rounded-full shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors duration-200"
103
+ >
104
+ < RiListUnordered className = "w-6 h-6" />
105
+ </ button >
106
+ ) }
107
+ </ div >
108
+ < article className = 'mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl' >
109
+ { locale !== LanguagesSupported [ 1 ]
28
110
? < TemplateEn apiBaseUrl = { apiBaseUrl } />
29
111
: < TemplateZh apiBaseUrl = { apiBaseUrl } />
30
- }
31
- </ article >
112
+ }
113
+ </ article >
114
+ </ div >
32
115
)
33
116
}
34
117
0 commit comments