Compare commits
2 Commits
a396118f86
...
106233c01f
Author | SHA1 | Date | |
---|---|---|---|
106233c01f | |||
99ab34b2ac |
@ -76,7 +76,7 @@ const DocsPage = async ({
|
||||
</Breadcrumb>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2.5 justify-between">
|
||||
<div className="flex flex-col">
|
||||
<CustomMDX source={page.content} />
|
||||
</div>
|
||||
|
@ -8,32 +8,32 @@ import remarkGfm from "remark-gfm";
|
||||
*/
|
||||
const components = {
|
||||
h1: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={1} className="text-4xl">
|
||||
<Heading as="h1" size={1} className="text-4xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h2: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={2} className="text-3xl">
|
||||
<Heading as="h2" size={2} className="text-3xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h3: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={3} className="text-2xl">
|
||||
<Heading as="h3" size={3} className="text-2xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h4: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={4} className="text-xl">
|
||||
<Heading as="h4" size={4} className="text-xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h5: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={5} className="text-lg">
|
||||
<Heading as="h5" size={5} className="text-lg">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h6: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={5} className="text-md">
|
||||
<Heading as="h6" size={6} className="text-md">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
@ -89,15 +89,31 @@ export const CustomMDX = (props: any): ReactElement => (
|
||||
* @return the heading jsx
|
||||
*/
|
||||
const Heading = ({
|
||||
as: Component,
|
||||
className,
|
||||
size,
|
||||
children,
|
||||
}: {
|
||||
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
className: string;
|
||||
size: number;
|
||||
children: ReactNode;
|
||||
}): ReactElement => (
|
||||
<h1 className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
}): ReactElement => {
|
||||
const id: string | undefined =
|
||||
typeof children === "string" ? slugify(children) : undefined;
|
||||
return (
|
||||
<Component
|
||||
id={id}
|
||||
className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const slugify = (text: string): string =>
|
||||
text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.replace(/[\s_-]+/g, "-")
|
||||
.trim();
|
||||
|
@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { ReactElement, useEffect, useState } from "react";
|
||||
import { ReactElement, useEffect, useRef, useState } from "react";
|
||||
import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Header = {
|
||||
id: string;
|
||||
@ -12,6 +13,10 @@ type Header = {
|
||||
|
||||
const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
const [headers, setHeaders] = useState<Header[]>([]);
|
||||
const [activeHeader, setActiveHeader] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const observerRef = useRef<IntersectionObserver | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
// Regular expression to match markdown headers
|
||||
@ -33,10 +38,41 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
setHeaders(extractedHeaders);
|
||||
}, [page.content]);
|
||||
|
||||
useEffect(() => {
|
||||
// Cleanup existing observer
|
||||
if (observerRef.current) {
|
||||
observerRef.current.disconnect();
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach((entry: IntersectionObserverEntry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveHeader(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: "0px 0px -80% 0px", threshold: 0.1 }
|
||||
);
|
||||
observerRef.current = observer;
|
||||
|
||||
// Observe all header elements
|
||||
headers.forEach((header: Header) => {
|
||||
const element: HTMLElement | null = document.getElementById(
|
||||
header.id
|
||||
);
|
||||
if (element) {
|
||||
observer.observe(element);
|
||||
}
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [headers]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
<div className="w-44 flex flex-col gap-2 text-sm select-none">
|
||||
{/* Title */}
|
||||
<div className="flex gap-2.5 items-center select-none">
|
||||
<div className="flex gap-2.5 items-center">
|
||||
<Bars3CenterLeftIcon className="w-5 h-5" />
|
||||
<h1>On This Page</h1>
|
||||
</div>
|
||||
@ -46,10 +82,17 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
|
||||
{headers.map((header: Header) => (
|
||||
<li
|
||||
key={header.id}
|
||||
className="opacity-65 hover:opacity-80 transition-all transform-gpu"
|
||||
className={cn(
|
||||
"hover:opacity-80 transition-all transform-gpu",
|
||||
activeHeader === header.id
|
||||
? "font-semibold text-primary"
|
||||
: "opacity-65"
|
||||
)}
|
||||
style={{ marginLeft: `${(header.level - 1) * 16}px` }}
|
||||
>
|
||||
<Link href={`#${header.id}`}>{header.text}</Link>
|
||||
<Link href={`#${header.id}`} draggable={false}>
|
||||
{header.text}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
Reference in New Issue
Block a user