- "frontend/src/pages/TechniquePage.tsx" - "frontend/src/components/TableOfContents.tsx" - "frontend/src/App.css" GSD-Task: S04/T01
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
/**
|
|
* Table of Contents for v2 technique pages with nested sections.
|
|
*
|
|
* Renders a nested list of anchor links matching the H2/H3 section structure.
|
|
* Uses slugified headings as IDs for scroll targeting.
|
|
*/
|
|
|
|
import type { BodySectionV2 } from "../api/public-client";
|
|
|
|
export function slugify(text: string): string {
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, "-")
|
|
.replace(/^-|-$/g, "");
|
|
}
|
|
|
|
interface TableOfContentsProps {
|
|
sections: BodySectionV2[];
|
|
}
|
|
|
|
export default function TableOfContents({ sections }: TableOfContentsProps) {
|
|
if (sections.length === 0) return null;
|
|
|
|
return (
|
|
<nav className="technique-toc" aria-label="Table of contents">
|
|
<h3 className="technique-toc__title">On this page</h3>
|
|
<ul className="technique-toc__list">
|
|
{sections.map((section) => {
|
|
const sectionSlug = slugify(section.heading);
|
|
return (
|
|
<li key={sectionSlug} className="technique-toc__item">
|
|
<a href={`#${sectionSlug}`} className="technique-toc__link">
|
|
{section.heading}
|
|
</a>
|
|
{section.subsections.length > 0 && (
|
|
<ul className="technique-toc__sublist">
|
|
{section.subsections.map((sub) => {
|
|
const subSlug = `${sectionSlug}--${slugify(sub.heading)}`;
|
|
return (
|
|
<li key={subSlug} className="technique-toc__subitem">
|
|
<a
|
|
href={`#${subSlug}`}
|
|
className="technique-toc__sublink"
|
|
>
|
|
{sub.heading}
|
|
</a>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</nav>
|
|
);
|
|
}
|