/[sudobot]/trunk/docs/components/MDX/TableOfContents.tsx
ViewVC logotype

Contents of /trunk/docs/components/MDX/TableOfContents.tsx

Parent Directory Parent Directory | Revision Log Revision Log


Revision 632 - (show annotations)
Thu Oct 10 17:53:11 2024 UTC (5 months, 2 weeks ago) by rakinar2
File MIME type: application/typescript
File size: 3332 byte(s)
chore: synchronize

1 "use client";
2
3 import useActualPathname from "@/hooks/useActualPathname";
4 import { FC, useEffect, useRef, useState } from "react";
5
6 const selector = ":is(h1, h2, h3, h4, h5, h6)[id]";
7
8 export default function TableOfContents({
9 as,
10 }: {
11 as?: keyof JSX.IntrinsicElements | FC;
12 }) {
13 const [headings, setHeadings] = useState<
14 {
15 id: string;
16 title: string;
17 }[]
18 >([]);
19 const observer = useRef<IntersectionObserver>();
20 const [activeId, setActiveId] = useState("");
21 const Root = as ?? "div";
22 const pathname = useActualPathname();
23
24 useEffect(() => {
25 const headingElements = Array.from(
26 document.querySelectorAll(selector) as Iterable<HTMLElement>,
27 );
28
29 if (
30 headingElements.length > 1 &&
31 headingElements[0]?.tagName === "H1"
32 ) {
33 headingElements.shift();
34 }
35
36 const headings = headingElements.map(element => ({
37 title: element.innerText.replaceAll("&amp;", "&"),
38 id: element.id,
39 }));
40
41 setHeadings(headings);
42 setActiveId(headings[0].id);
43 }, [pathname]);
44
45 useEffect(() => {
46 observer.current = new IntersectionObserver(
47 entries => {
48 for (const entry of entries) {
49 if (entry?.isIntersecting) {
50 setActiveId(entry.target.id);
51 }
52 }
53 },
54 {
55 rootMargin: "-30% 0% -30% 0%",
56 },
57 );
58
59 const elements = document.querySelectorAll(selector);
60 elements.forEach(element => observer.current?.observe(element));
61
62 return () => {
63 observer.current?.disconnect();
64 setActiveId("");
65 };
66 }, [pathname]);
67
68 const onlyOne = headings.length === 1;
69
70 return (
71 <Root>
72 <h4 className="pl-[15px] mb-3 mt-4 uppercase font-bold tracking-wider text-[15px]">
73 On this page
74 </h4>
75 <ul className="list-none pr-2.5">
76 {headings.map(heading => (
77 <li key={heading.id}>
78 <a
79 className={`my-2 block pl-[15px] ${
80 activeId === heading.id || onlyOne
81 ? "text-blue-500 after:[content:'●'] after:ml-2 after:inline-block after:text-blue-500"
82 : "hover:text-blue-500"
83 }`}
84 href={`#${heading.id}`}
85 onClick={event => {
86 event.preventDefault();
87
88 const element = document.getElementById(
89 heading.id,
90 );
91
92 element?.scrollIntoView({
93 behavior: "smooth",
94 block: "center",
95 inline: "center",
96 });
97 }}
98 >
99 {heading.title}
100 </a>
101 </li>
102 ))}
103 </ul>
104 </Root>
105 );
106 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26