Skip to content

Commit

Permalink
feat(nx-dev): add TOC markdoc component for blog posts
Browse files Browse the repository at this point in the history
  • Loading branch information
juristr committed Dec 23, 2024
1 parent 90e12a7 commit 3e56486
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 deletions.
4 changes: 4 additions & 0 deletions nx-dev/ui-markdoc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import { pill } from './lib/tags/pill.schema';
import { fence } from './lib/nodes/fence.schema';
import { FenceWrapper } from './lib/nodes/fence-wrapper.component';
import { VideoPlayer, videoPlayer } from './lib/tags/video-player.component';
import { TableOfContents } from './lib/tags/table-of-contents.component';
import { tableOfContents } from './lib/tags/table-of-contents.schema';
// TODO fix this export
export { GithubRepository } from './lib/tags/github-repository.component';

Expand Down Expand Up @@ -92,6 +94,7 @@ export const getMarkdocCustomConfig = (
tab,
tabs,
'terminal-video': terminalVideo,
toc: tableOfContents,
tweet,
youtube,
'video-link': videoLink,
Expand Down Expand Up @@ -121,6 +124,7 @@ export const getMarkdocCustomConfig = (
SideBySide,
Tab,
Tabs,
TableOfContents,
TerminalVideo,
Tweet,
YouTube,
Expand Down
70 changes: 70 additions & 0 deletions nx-dev/ui-markdoc/src/lib/tags/table-of-contents.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';

import { useEffect, useState } from 'react';

interface TocItem {
id: string;
text: string;
level: number;
}

interface TableOfContentsProps {
maxDepth?: number;
}

export function TableOfContents({
maxDepth = 3,
}: TableOfContentsProps): JSX.Element {
const [headings, setHeadings] = useState<TocItem[]>([]);

useEffect(() => {
// Find the main content wrapper where markdown content is rendered
const content = document.querySelector('[data-document="main"]');
if (!content) return;

// Get all headings h1-h6 within the content
const headingElements = content.querySelectorAll('h1, h2, h3, h4, h5, h6');

const items: TocItem[] = Array.from(headingElements)
.map((heading) => {
const level = parseInt(heading.tagName[1]);
if (level > maxDepth) return null;

return {
id: heading.id,
text: heading.textContent || '',
level,
};
})
.filter((item): item is TocItem => item !== null);

setHeadings(items);
}, [maxDepth]);

if (headings.length === 0) {
return null;
}

return (
<nav className="toc not-prose mb-8 rounded-lg border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900/50">
<p className="mb-3 text-sm font-semibold text-slate-900 dark:text-slate-100">
Table of Contents
</p>
<ul className="space-y-2 text-sm">
{headings.map((heading) => (
<li
key={heading.id}
style={{ paddingLeft: `${(heading.level - 1) * 1}rem` }}
>
<a
href={`#${heading.id}`}
className="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
);
}
11 changes: 11 additions & 0 deletions nx-dev/ui-markdoc/src/lib/tags/table-of-contents.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Schema } from '@markdoc/markdoc';

export const tableOfContents: Schema = {
render: 'TableOfContents',
attributes: {
maxDepth: {
type: 'Number',
default: 3,
},
},
};

0 comments on commit 3e56486

Please sign in to comment.