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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 632 - (hide 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 rakinar2 575 "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 rakinar2 632 <ul className="list-none pr-2.5">
76 rakinar2 575 {headings.map(heading => (
77     <li key={heading.id}>
78     <a
79 rakinar2 626 className={`my-2 block pl-[15px] ${
80 rakinar2 575 activeId === heading.id || onlyOne
81 rakinar2 626 ? "text-blue-500 after:[content:'●'] after:ml-2 after:inline-block after:text-blue-500"
82     : "hover:text-blue-500"
83 rakinar2 575 }`}
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