Skip to content

Commit 2d7ef40

Browse files
committed
Update table-of-contents.tsx
1 parent 1563aa1 commit 2d7ef40

File tree

1 file changed

+60
-58
lines changed

1 file changed

+60
-58
lines changed
Lines changed: 60 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,92 @@
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";
55
import {
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

1212
interface TableOfContentsProps {
13-
tableOfContents: string
14-
className?: string
13+
tableOfContents: string;
14+
className?: string;
1515
}
1616

1717
const 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

Comments
 (0)