Skip to content

Commit

Permalink
add toc
Browse files Browse the repository at this point in the history
  • Loading branch information
thejackshelton committed Dec 21, 2024
1 parent b581495 commit dfc8fef
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 44 deletions.
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"autoprefixer": "^10.4.19",
"clsx": "^2.1.1",
"eslint": "8.57.0",
"eslint-plugin-qwik": "^2.0.0-alpha.4",
"postcss": "^8.4.39",
Expand All @@ -44,6 +45,7 @@
"rehype-pretty-code": "^0.14.0",
"sass": "^1.83.0",
"shiki": "^1.24.3",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.6",
"typescript": "5.4.5",
"unist-util-visit": "^5.0.0",
Expand Down
17 changes: 17 additions & 0 deletions docs/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docs/src/components/Aside/Aside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Aside = component$(() => {

return (
<div
class={`fixed top-20 mt-8 flex h-[calc(100%-12rem)] flex-col overflow-hidden overflow-y-auto px-6 text-xl text-black dark:text-white`}
class={`fixed top-20 mt-8 flex h-full flex-col overflow-hidden overflow-y-auto px-6 text-black dark:text-white`}
>
{(menu?.items || []).map(({ text, items }, idx) => {
const [title, href] = text.split("|");
Expand All @@ -15,7 +15,7 @@ export const Aside = component$(() => {
<li>
{href ? (
<a
class="mb-2 block rounded bg-blue-700 px-4 py-1 text-base font-bold uppercase text-white no-underline"
class="mb-2 block rounded bg-blue-700 dark:bg-blue-600 px-4 py-1 text-base font-bold uppercase text-white no-underline"
href={href}
>
{title}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GitHubIcon } from "../Icons/GitHubIcon";

export const Footer = component$(() => {
return (
<footer class="flex border-t-[2px] border-slate-200 bg-white px-6 py-4 dark:border-slate-800 dark:bg-black">
<footer class="flex border-t-[2px] border-slate-200 px-6 py-4 dark:border-slate-800 bg-[#F8F8FF] dark:bg-[#0D0F12] mt-6">
<div class="grid w-full grid-cols-12">
<div class="col-span-4" />
<div class="col-span-4" />
Expand Down
4 changes: 2 additions & 2 deletions docs/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export const Header = component$<Props>(({ links = [], showMenu = true }) => {
const showAsideSig = useSignal(false);
return (
<header
class={`fixed top-0 z-10 h-20 w-full border-b-[2px] border-slate-200 bg-white dark:border-slate-800 dark:bg-black`}
class={`fixed top-0 z-10 h-20 w-full border-b-[2px] border-zinc-100 bg-[#F8F8FF] dark:border-zinc-900 dark:bg-[#0D0F12]`}
>
<div class="grid h-full max-w-[1200px] grid-cols-12 px-6">
<div class="grid h-full max-w-[1376px] grid-cols-12 px-6">
<div class="col-span-3 flex items-center sm:col-span-4">
{showMenu && (
<button
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/MdxComponents/MdxComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const components: Record<string, any> = {
() => {
return (
<div class="relative">
<pre class="mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg border bg-zinc-950 p-6 text-white dark:bg-zinc-900">
<pre class="mb-4 mt-6 max-h-[650px] overflow-x-auto rounded-lg bg-zinc-950 p-6 text-white dark:bg-zinc-950">
<Slot />
</pre>
</div>
Expand Down
217 changes: 189 additions & 28 deletions docs/src/components/Toc/Toc.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,192 @@
import { component$, useSignal } from "@qwik.dev/core";
import { useContent } from "@qwik.dev/router";
import { cn } from "~/utils/cn";
import { component$, useSignal, $, useOnWindow } from '@qwik.dev/core';
import { ContentHeading } from '@qwik.dev/router';

export const Toc = component$(() => {
const { headings } = useContent();
const lastScrollIdSig = useSignal("");
return (
<div class="fixed flex w-full flex-col px-6 pt-28 text-xl text-black dark:text-white">
<span class="mb-2 text-lg font-bold uppercase">On this page</span>
{(headings || []).map(({ text, id }, idx) => (
<ul
key={idx}
class="border-l-[4px] py-1 pl-4 hover:border-blue-700"
onMouseOver$={() => {
if (lastScrollIdSig.value !== id) {
const el = document.querySelector(`#${id}`);
if (el) {
el.scrollIntoView({ behavior: "smooth" });
lastScrollIdSig.value = id;
}
}
}}
>
<li class="text-base hover:text-blue-500">
<a href={`#${id}`}>{text}</a>
export const TOC = component$(
({ headings }: { headings: ContentHeading[] }) => {
if (headings.length === 0) {
return null;
}
return (
<div class="space-y-2 sticky top-24 max-h-[calc(80vh)] p-1 dark:text-white text-black hidden xl:block">
<div class="font-medium">On This Page</div>
<TableOfContents headings={headings} />
</div>
);
},
);

type TableOfContentsProps = { headings: ContentHeading[] };

interface Node extends ContentHeading {
children: Node[];
activeItem: string;
}
type Tree = Array<Node>;

const TableOfContents = component$<TableOfContentsProps>(({ headings }) => {
const sanitizedHeadings = headings.map(({ text, id, level }) => ({ text, id, level }));
const itemIds = headings.map(({ id }) => id);
const activeHeading = useActiveItem(itemIds);
const tree = buildTree(sanitizedHeadings);
const fixStartingBug: Node = { ...tree, children: [tree] };
return <RecursiveList tree={fixStartingBug} activeItem={activeHeading.value ?? ''} />;
});

function deltaToStrg(
currNode: Node,
nextNode: Node,
): 'same level' | 'down one level' | 'up one level' | 'upwards discontinuous' {
const delta = currNode.level - nextNode.level;
if (delta > 1) {
return 'upwards discontinuous';
}
if (delta === 1) {
return 'up one level';
}
if (delta === 0) {
return 'same level';
}
if (delta === -1) {
return 'down one level';
}

throw new Error(
`bad headings: are downwards discontinous from: #${currNode.id} to #${nextNode.id} bc from ${currNode.level} to ${nextNode.level}`,
);
}

function buildTree(nodes: ContentHeading[]) {
let currNode = nodes[0] as Node;
currNode.children = [];
const tree = [currNode];
const childrenMap = new Map<number, Tree>();
childrenMap.set(currNode.level, currNode.children);
for (let index = 1; index < nodes.length; index++) {
const nextNode = nodes[index] as Node;
nextNode.children = [];
childrenMap.set(nextNode.level, nextNode.children);
const deltaStrg = deltaToStrg(currNode, nextNode);
switch (deltaStrg) {
case 'upwards discontinuous': {
const delta = currNode.level - nextNode.level;
if (childrenMap.has(delta - 1)) {
const nthParent = childrenMap.get(delta - 1);
nthParent?.push(nextNode);
}
break;
}
case 'up one level': {
const grandParent = childrenMap.get(currNode.level - 2);
grandParent?.push(nextNode);
break;
}
case 'same level': {
const parent = childrenMap.get(currNode.level - 1);
parent?.push(nextNode);
break;
}
case 'down one level': {
currNode.children.push(nextNode);
break;
}
default:
break;
}
currNode = nextNode;
}
return tree[0];
}

type RecursiveListProps = {
tree: Node;
activeItem: string;
limit?: number;
};

const RecursiveList = component$<RecursiveListProps>(
({ tree, activeItem, limit = 3 }) => {
return tree?.children?.length && tree.level < limit ? (
<ul class={cn('m-0 list-none', { 'pl-4': tree.level !== 1 })}>
{tree.children.map((childNode) => (
<li key={childNode.id} class="mt-0 list-none pt-2">
<Anchor node={childNode} activeItem={activeItem} />
{childNode.children.length > 0 && (
<RecursiveList tree={childNode} activeItem={activeItem} />
)}
</li>
</ul>
))}
</div>
))}
</ul>
) : null;
},
);

const useActiveItem = (itemIds: string[]) => {
const activeId = useSignal<string>();

useOnWindow(
'scroll',
$(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
activeId.value = entry.target.id;
}
});
},
{ rootMargin: '0% 0% -85% 0%' },
);

itemIds.forEach((id) => {
const element = document.getElementById(id);
if (element) {
observer.observe(element);
}
});

return () => {
itemIds.forEach((id) => {
const element = document.getElementById(id);
if (element) {
observer.unobserve(element);
}
});
};
}),
);
});

return activeId;
};

type AnchorProps = {
node: Node;
activeItem: string;
};

const Anchor = component$<AnchorProps>(({ node, activeItem }) => {
const isActive = node.id === activeItem;
return (
<a
href={`#${node.id}`}
onClick$={[
$(() => {
const element = document.getElementById(node.id);
if (element) {
const navbarHeight = 90;
const position =
element.getBoundingClientRect().top + window.scrollY - navbarHeight;
window.scrollTo({ top: position, behavior: 'auto' });
}
}),
]}
class={cn(
node.level > 2 && 'ml-2',
'inline-block no-underline transition-colors',
isActive ? 'text-blue-500 dark:text-blue-300' : 'text-muted-foreground',
)}
>
{node.text}
</a>
);
});
9 changes: 5 additions & 4 deletions docs/src/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@
*/

html {
height: 100%;
&.dark {
@apply bg-black;
}
@apply bg-[#F8F8FF];
}

.dark {
@apply bg-[#0D0F12]
}

body {
Expand Down
2 changes: 1 addition & 1 deletion docs/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default component$(() => {
)}
<RouterHead />
</head>
<body lang="en" class="m-auto max-w-[1200px]">
<body lang="en" class="m-auto max-w-[1376px]">
<RouterOutlet />
{!isDev && <ServiceWorkerRegister />}
</body>
Expand Down
Loading

0 comments on commit dfc8fef

Please sign in to comment.