2 Commits

Author SHA1 Message Date
106233c01f highlight active section in on this page
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 57s
Took 33 seconds
2024-10-06 20:21:41 -04:00
99ab34b2ac fix heading mdx components always being h1, and not having an id
Took 19 minutes
2024-10-06 20:21:08 -04:00
3 changed files with 76 additions and 17 deletions

View File

@ -76,7 +76,7 @@ const DocsPage = async ({
</Breadcrumb> </Breadcrumb>
{/* Content */} {/* Content */}
<div className="flex justify-between"> <div className="flex gap-2.5 justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
<CustomMDX source={page.content} /> <CustomMDX source={page.content} />
</div> </div>

View File

@ -8,32 +8,32 @@ import remarkGfm from "remark-gfm";
*/ */
const components = { const components = {
h1: ({ children }: { children: ReactNode }): ReactElement => ( h1: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={1} className="text-4xl"> <Heading as="h1" size={1} className="text-4xl">
{children} {children}
</Heading> </Heading>
), ),
h2: ({ children }: { children: ReactNode }): ReactElement => ( h2: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={2} className="text-3xl"> <Heading as="h2" size={2} className="text-3xl">
{children} {children}
</Heading> </Heading>
), ),
h3: ({ children }: { children: ReactNode }): ReactElement => ( h3: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={3} className="text-2xl"> <Heading as="h3" size={3} className="text-2xl">
{children} {children}
</Heading> </Heading>
), ),
h4: ({ children }: { children: ReactNode }): ReactElement => ( h4: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={4} className="text-xl"> <Heading as="h4" size={4} className="text-xl">
{children} {children}
</Heading> </Heading>
), ),
h5: ({ children }: { children: ReactNode }): ReactElement => ( h5: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={5} className="text-lg"> <Heading as="h5" size={5} className="text-lg">
{children} {children}
</Heading> </Heading>
), ),
h6: ({ children }: { children: ReactNode }): ReactElement => ( h6: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={5} className="text-md"> <Heading as="h6" size={6} className="text-md">
{children} {children}
</Heading> </Heading>
), ),
@ -89,15 +89,31 @@ export const CustomMDX = (props: any): ReactElement => (
* @return the heading jsx * @return the heading jsx
*/ */
const Heading = ({ const Heading = ({
as: Component,
className, className,
size, size,
children, children,
}: { }: {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
className: string; className: string;
size: number; size: number;
children: ReactNode; children: ReactNode;
}): ReactElement => ( }): ReactElement => {
<h1 className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}> const id: string | undefined =
{children} typeof children === "string" ? slugify(children) : undefined;
</h1> 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();

View File

@ -1,8 +1,9 @@
"use client"; "use client";
import { ReactElement, useEffect, useState } from "react"; import { ReactElement, useEffect, useRef, useState } from "react";
import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline"; import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline";
import Link from "next/link"; import Link from "next/link";
import { cn } from "@/lib/utils";
type Header = { type Header = {
id: string; id: string;
@ -12,6 +13,10 @@ type Header = {
const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => { const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
const [headers, setHeaders] = useState<Header[]>([]); const [headers, setHeaders] = useState<Header[]>([]);
const [activeHeader, setActiveHeader] = useState<string | undefined>(
undefined
);
const observerRef = useRef<IntersectionObserver | undefined>(undefined);
useEffect(() => { useEffect(() => {
// Regular expression to match markdown headers // Regular expression to match markdown headers
@ -33,10 +38,41 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
setHeaders(extractedHeaders); setHeaders(extractedHeaders);
}, [page.content]); }, [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 ( return (
<div className="flex flex-col gap-2 text-sm"> <div className="w-44 flex flex-col gap-2 text-sm select-none">
{/* Title */} {/* 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" /> <Bars3CenterLeftIcon className="w-5 h-5" />
<h1>On This Page</h1> <h1>On This Page</h1>
</div> </div>
@ -46,10 +82,17 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
{headers.map((header: Header) => ( {headers.map((header: Header) => (
<li <li
key={header.id} 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` }} style={{ marginLeft: `${(header.level - 1) * 16}px` }}
> >
<Link href={`#${header.id}`}>{header.text}</Link> <Link href={`#${header.id}`} draggable={false}>
{header.text}
</Link>
</li> </li>
))} ))}
</ul> </ul>