1- import React , { useEffect , useState , useCallback , useRef } from "react"
2- import { ChevronRight , List } from "lucide-react"
3- import { ScrollArea } from "@/components/ui/scroll-area"
4- import { Separator } from "@/components/ui/separator"
1+ import React , { useEffect , useState , useCallback , useRef } from "react" ;
2+ import { ChevronRight , List } from "lucide-react" ;
3+ import { ScrollArea } from "@/components/ui/scroll-area" ;
4+ import { Separator } from "@/components/ui/separator" ;
55import {
66 Collapsible ,
77 CollapsibleContent ,
88 CollapsibleTrigger ,
9- } from "@/components/ui/collapsible"
10- import { Button } from "@/components/ui/button"
9+ } from "@/components/ui/collapsible" ;
10+ import { Button } from "@/components/ui/button" ;
1111
1212interface TableOfContentsProps {
13- tableOfContents : string
14- className ?: string
13+ tableOfContents : string ;
14+ className ?: string ;
1515}
1616
1717const TableOfContents : React . FC < TableOfContentsProps > = ( {
1818 tableOfContents,
1919 className = "" ,
2020} ) => {
21- const [ activeId , setActiveId ] = useState < string > ( "" )
22- const [ isOpen , setIsOpen ] = useState ( false )
23- const [ isMobile , setIsMobile ] = useState ( false )
24- const tocRef = useRef < HTMLDivElement > ( null )
21+ const [ activeId , setActiveId ] = useState < string > ( "" ) ;
22+ const [ isOpen , setIsOpen ] = useState ( false ) ;
23+ const [ isMobile , setIsMobile ] = useState ( false ) ;
24+ const tocRef = useRef < HTMLDivElement > ( null ) ;
2525
2626 // 목차 링크 클릭 이벤트 처리
2727 const handleTocClick = useCallback (
2828 ( e : React . MouseEvent < HTMLDivElement > ) => {
29- const target = e . target as HTMLElement
30- const link = target . closest ( 'a[href^="#"]' ) as HTMLAnchorElement
29+ const target = e . target as HTMLElement ;
30+ const link = target . closest ( 'a[href^="#"]' ) as HTMLAnchorElement ;
3131
3232 if ( link ) {
33- e . preventDefault ( )
34- const href = link . getAttribute ( "href" )
33+ e . preventDefault ( ) ;
34+ const href = link . getAttribute ( "href" ) ;
3535 if ( href ) {
36- const id = decodeURIComponent ( href . replace ( "#" , "" ) )
37- const element = document . getElementById ( id )
36+ const id = decodeURIComponent ( href . replace ( "#" , "" ) ) ;
37+ const element = document . getElementById ( id ) ;
3838
3939 if ( element ) {
40- const offsetTop = element . offsetTop - 100
40+ const offsetTop = element . offsetTop - 100 ;
4141 window . scrollTo ( {
4242 top : offsetTop ,
4343 behavior : "smooth" ,
44- } )
44+ } ) ;
4545 }
4646
4747 // 모바일에서는 클릭 후 접기
4848 if ( isMobile ) {
49- setIsOpen ( false )
49+ setIsOpen ( false ) ;
5050 }
5151 }
5252 }
5353 } ,
5454 [ isMobile ]
55- )
55+ ) ;
5656
5757 // 화면 크기 감지
5858 useEffect ( ( ) => {
5959 const checkIsMobile = ( ) => {
60- setIsMobile ( window . innerWidth < 768 )
60+ setIsMobile ( window . innerWidth < 768 ) ;
6161 // 데스크톱에서는 항상 열어두기
6262 if ( window . innerWidth >= 768 ) {
63- setIsOpen ( true )
63+ setIsOpen ( true ) ;
6464 }
65- }
65+ } ;
6666
67- checkIsMobile ( )
68- window . addEventListener ( "resize" , checkIsMobile )
69- return ( ) => window . removeEventListener ( "resize" , checkIsMobile )
70- } , [ ] )
67+ checkIsMobile ( ) ;
68+ window . addEventListener ( "resize" , checkIsMobile ) ;
69+ return ( ) => window . removeEventListener ( "resize" , checkIsMobile ) ;
70+ } , [ ] ) ;
7171
7272 // IntersectionObserver를 이용한 현재 섹션 하이라이트
7373 useEffect ( ( ) => {
7474 if ( ! tableOfContents || ! tableOfContents . trim ( ) ) {
75- return
75+ return ;
7676 }
7777
7878 // IntersectionObserver 참조 저장
79- let observer : IntersectionObserver | null = null
79+ let observer : IntersectionObserver | null = null ;
8080
8181 // DOM이 완전히 로드된 후 실행하기 위한 약간의 지연
8282 const timer = setTimeout ( ( ) => {
8383 // 모든 헤딩 요소를 미리 찾기
8484 const headings = Array . from (
8585 document . querySelectorAll ( "h1, h2, h3, h4, h5, h6" )
86- )
86+ ) ;
8787
8888 if ( ! headings . length ) {
89- return
89+ return ;
9090 }
9191
9292 // 매우 간단한 IntersectionObserver 설정
@@ -95,69 +95,71 @@ const TableOfContents: React.FC<TableOfContentsProps> = ({
9595 // 현재 보이는 헤딩들
9696 const visibleHeadings = entries
9797 . filter ( entry => entry . isIntersecting )
98- . sort ( ( a , b ) => a . boundingClientRect . top - b . boundingClientRect . top )
98+ . sort (
99+ ( a , b ) => a . boundingClientRect . top - b . boundingClientRect . top
100+ ) ;
99101
100102 if ( visibleHeadings . length > 0 ) {
101- const activeHeading = visibleHeadings [ 0 ] ! . target as HTMLElement
102- setActiveId ( activeHeading . id )
103+ const activeHeading = visibleHeadings [ 0 ] ! . target as HTMLElement ;
104+ setActiveId ( activeHeading . id ) ;
103105 }
104106 } ,
105107 {
106108 // 매우 간단한 설정
107109 rootMargin : "-100px 0px -50% 0px" ,
108110 threshold : 0.1 ,
109111 }
110- )
112+ ) ;
111113
112114 // 모든 헤딩 관찰
113115 headings . forEach ( heading => {
114116 if ( heading . id ) {
115- observer ?. observe ( heading )
117+ observer ?. observe ( heading ) ;
116118 }
117- } )
118- } , 100 ) // 100ms 지연
119+ } ) ;
120+ } , 100 ) ; // 100ms 지연
119121
120122 return ( ) => {
121- clearTimeout ( timer )
123+ clearTimeout ( timer ) ;
122124 if ( observer ) {
123- observer . disconnect ( )
125+ observer . disconnect ( ) ;
124126 }
125- }
126- } , [ tableOfContents ] )
127+ } ;
128+ } , [ tableOfContents ] ) ;
127129
128130 // 활성 링크 스타일 업데이트
129131 useEffect ( ( ) => {
130- if ( ! tocRef . current ) return
132+ if ( ! tocRef . current ) return ;
131133
132- const links = tocRef . current . querySelectorAll ( 'a[href^="#"]' )
134+ const links = tocRef . current . querySelectorAll ( 'a[href^="#"]' ) ;
133135
134136 links . forEach ( link => {
135- const href = link . getAttribute ( "href" )
137+ const href = link . getAttribute ( "href" ) ;
136138 if ( href ) {
137- const id = decodeURIComponent ( href . replace ( "#" , "" ) )
139+ const id = decodeURIComponent ( href . replace ( "#" , "" ) ) ;
138140 if ( id === activeId ) {
139- link . classList . add ( "toc-active" )
141+ link . classList . add ( "toc-active" ) ;
140142 } else {
141- link . classList . remove ( "toc-active" )
143+ link . classList . remove ( "toc-active" ) ;
142144 }
143145 }
144- } )
145- } , [ activeId ] )
146+ } ) ;
147+ } , [ activeId ] ) ;
146148
147149 if ( ! tableOfContents || ! tableOfContents . trim ( ) ) {
148- return null
150+ return null ;
149151 }
150152
151153 const TocContent = ( ) => (
152- < ScrollArea className = "h-full max-h-[60vh] pr-3" >
154+ < ScrollArea className = "h-full pr-3" >
153155 < div
154156 ref = { tocRef }
155157 className = "toc-content"
156158 dangerouslySetInnerHTML = { { __html : tableOfContents } }
157159 onClick = { handleTocClick }
158160 />
159161 </ ScrollArea >
160- )
162+ ) ;
161163
162164 return (
163165 < div className = { `sticky top-24 ${ className } ` } >
@@ -197,7 +199,7 @@ const TableOfContents: React.FC<TableOfContentsProps> = ({
197199 </ div >
198200 ) }
199201 </ div >
200- )
201- }
202+ ) ;
203+ } ;
202204
203- export default TableOfContents
205+ export default TableOfContents ;
0 commit comments