openpanel/documentation/src/refine-theme/blog-toc.tsx
2024-02-05 10:23:04 +01:00

140 lines
3.7 KiB
TypeScript

import React from "react";
import clsx from "clsx";
import { useLocation, useHistory } from "@docusaurus/router";
import { Tags } from "@site/src/components/blog";
export const BlogTOCItem = ({
id,
value,
level,
activeId,
onIdChange,
}: {
id: string;
value: string;
level: number;
activeId: string;
onIdChange?: (id: string) => void;
}) => {
React.useEffect(() => {
const targetElement = document.getElementById(id);
if (targetElement) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
onIdChange(id);
}
});
},
{
rootMargin: "0px 0px -80% 0px",
},
);
observer.observe(targetElement);
return () => {
observer.unobserve(targetElement);
};
}
}, [id]);
return (
<a
href={`#${id}`}
className={clsx(
"refine-toc-item",
level === 2 && "pl-3",
level === 3 && "pl-7",
level === 4 && "pl-11",
"py-2 pr-3",
activeId === id &&
` dark:bg-gray-700 bg-gray-100 text-gray-900 dark:text-gray-0 hover:text-gray-900`,
activeId !== id &&
`dark:text-gray-500 hover:text-gray-900 text-gray-500`,
"rounded-lg",
"transition-colors duration-200 ease-in-out",
"block",
"text-sm",
"leading-6",
"no-underline hover:no-underline",
)}
dangerouslySetInnerHTML={{
__html: value,
}}
/>
);
};
export const BlogTOC = (props) => {
const history = useHistory();
const location = useLocation();
const { toc, hasTOC, activeId, setActiveId } = useTOC(props.toc);
const onIdChange = (id) => {
if (id !== `${location.hash ?? ""}`.replace("#", "")) {
setActiveId(id);
window.history.replaceState({}, "", `#${id}`);
}
};
return (
<div
className={clsx(
"hidden blog-md:block",
"w-full",
"sticky right-0 top-[67px]",
"max-w-[280px]",
"overflow-auto",
"h-[calc(100vh-67px)]",
"px-3",
"blog-md:py-16",
"border-l dark:border-l-gray-700 border-l-gray-100",
!hasTOC && "invisible",
)}
>
<Tags />
<ul className="list-none m-0 p-0">
{toc.map((item) => {
return (
<li key={item.id}>
<BlogTOCItem
id={item.id}
value={item.value}
activeId={activeId}
level={item.level}
onIdChange={onIdChange}
/>
</li>
);
})}
</ul>
</div>
);
};
export const useTOC = (toc) => {
const { hash } = useLocation();
const baseActiveId = `${hash}`.replace("#", "");
const [activeId, setActiveId] = React.useState<string | undefined>(
baseActiveId,
);
React.useEffect(() => {
setActiveId(baseActiveId);
}, [baseActiveId]);
const hasTOC = toc?.length > 0;
return {
toc,
activeId,
setActiveId,
hasTOC,
};
};