Skip to content

Commit 7bedf39

Browse files
docs: add TOC + site changes (#655)
1 parent b581495 commit 7bedf39

File tree

11 files changed

+237
-44
lines changed

11 files changed

+237
-44
lines changed

docs/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@typescript-eslint/eslint-plugin": "7.16.1",
3737
"@typescript-eslint/parser": "7.16.1",
3838
"autoprefixer": "^10.4.19",
39+
"clsx": "^2.1.1",
3940
"eslint": "8.57.0",
4041
"eslint-plugin-qwik": "^2.0.0-alpha.4",
4142
"postcss": "^8.4.39",
@@ -44,6 +45,7 @@
4445
"rehype-pretty-code": "^0.14.0",
4546
"sass": "^1.83.0",
4647
"shiki": "^1.24.3",
48+
"tailwind-merge": "^2.5.5",
4749
"tailwindcss": "^3.4.6",
4850
"typescript": "5.4.5",
4951
"unist-util-visit": "^5.0.0",

docs/pnpm-lock.yaml

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/components/Aside/Aside.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const Aside = component$(() => {
66

77
return (
88
<div
9-
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`}
9+
class={`fixed top-20 mt-8 flex h-full flex-col overflow-hidden overflow-y-auto px-6 text-black dark:text-white`}
1010
>
1111
{(menu?.items || []).map(({ text, items }, idx) => {
1212
const [title, href] = text.split("|");
@@ -15,7 +15,7 @@ export const Aside = component$(() => {
1515
<li>
1616
{href ? (
1717
<a
18-
class="mb-2 block rounded bg-blue-700 px-4 py-1 text-base font-bold uppercase text-white no-underline"
18+
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"
1919
href={href}
2020
>
2121
{title}

docs/src/components/Footer/Footer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GitHubIcon } from "../Icons/GitHubIcon";
33

44
export const Footer = component$(() => {
55
return (
6-
<footer class="flex border-t-[2px] border-slate-200 bg-white px-6 py-4 dark:border-slate-800 dark:bg-black">
6+
<footer class="flex border-t-[2px] border-slate-200 px-6 py-4 dark:border-slate-800 bg-[#F8F8FF] dark:bg-[#0D0F12] mt-6">
77
<div class="grid w-full grid-cols-12">
88
<div class="col-span-4" />
99
<div class="col-span-4" />

docs/src/components/Header/Header.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export const Header = component$<Props>(({ links = [], showMenu = true }) => {
1515
const showAsideSig = useSignal(false);
1616
return (
1717
<header
18-
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`}
18+
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]`}
1919
>
20-
<div class="grid h-full max-w-[1200px] grid-cols-12 px-6">
20+
<div class="grid h-full max-w-[1376px] grid-cols-12 px-6">
2121
<div class="col-span-3 flex items-center sm:col-span-4">
2222
{showMenu && (
2323
<button

docs/src/components/MdxComponents/MdxComponents.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const components: Record<string, any> = {
77
() => {
88
return (
99
<div class="relative">
10-
<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">
10+
<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">
1111
<Slot />
1212
</pre>
1313
</div>

docs/src/components/Toc/Toc.tsx

+189-28
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,192 @@
1-
import { component$, useSignal } from "@qwik.dev/core";
2-
import { useContent } from "@qwik.dev/router";
1+
import { cn } from "~/utils/cn";
2+
import { component$, useSignal, $, useOnWindow } from '@qwik.dev/core';
3+
import { ContentHeading } from '@qwik.dev/router';
34

4-
export const Toc = component$(() => {
5-
const { headings } = useContent();
6-
const lastScrollIdSig = useSignal("");
7-
return (
8-
<div class="fixed flex w-full flex-col px-6 pt-28 text-xl text-black dark:text-white">
9-
<span class="mb-2 text-lg font-bold uppercase">On this page</span>
10-
{(headings || []).map(({ text, id }, idx) => (
11-
<ul
12-
key={idx}
13-
class="border-l-[4px] py-1 pl-4 hover:border-blue-700"
14-
onMouseOver$={() => {
15-
if (lastScrollIdSig.value !== id) {
16-
const el = document.querySelector(`#${id}`);
17-
if (el) {
18-
el.scrollIntoView({ behavior: "smooth" });
19-
lastScrollIdSig.value = id;
20-
}
21-
}
22-
}}
23-
>
24-
<li class="text-base hover:text-blue-500">
25-
<a href={`#${id}`}>{text}</a>
5+
export const TOC = component$(
6+
({ headings }: { headings: ContentHeading[] }) => {
7+
if (headings.length === 0) {
8+
return null;
9+
}
10+
return (
11+
<div class="space-y-2 sticky top-24 max-h-[calc(80vh)] p-1 dark:text-white text-black hidden xl:block">
12+
<div class="font-medium">On This Page</div>
13+
<TableOfContents headings={headings} />
14+
</div>
15+
);
16+
},
17+
);
18+
19+
type TableOfContentsProps = { headings: ContentHeading[] };
20+
21+
interface Node extends ContentHeading {
22+
children: Node[];
23+
activeItem: string;
24+
}
25+
type Tree = Array<Node>;
26+
27+
const TableOfContents = component$<TableOfContentsProps>(({ headings }) => {
28+
const sanitizedHeadings = headings.map(({ text, id, level }) => ({ text, id, level }));
29+
const itemIds = headings.map(({ id }) => id);
30+
const activeHeading = useActiveItem(itemIds);
31+
const tree = buildTree(sanitizedHeadings);
32+
const fixStartingBug: Node = { ...tree, children: [tree] };
33+
return <RecursiveList tree={fixStartingBug} activeItem={activeHeading.value ?? ''} />;
34+
});
35+
36+
function deltaToStrg(
37+
currNode: Node,
38+
nextNode: Node,
39+
): 'same level' | 'down one level' | 'up one level' | 'upwards discontinuous' {
40+
const delta = currNode.level - nextNode.level;
41+
if (delta > 1) {
42+
return 'upwards discontinuous';
43+
}
44+
if (delta === 1) {
45+
return 'up one level';
46+
}
47+
if (delta === 0) {
48+
return 'same level';
49+
}
50+
if (delta === -1) {
51+
return 'down one level';
52+
}
53+
54+
throw new Error(
55+
`bad headings: are downwards discontinous from: #${currNode.id} to #${nextNode.id} bc from ${currNode.level} to ${nextNode.level}`,
56+
);
57+
}
58+
59+
function buildTree(nodes: ContentHeading[]) {
60+
let currNode = nodes[0] as Node;
61+
currNode.children = [];
62+
const tree = [currNode];
63+
const childrenMap = new Map<number, Tree>();
64+
childrenMap.set(currNode.level, currNode.children);
65+
for (let index = 1; index < nodes.length; index++) {
66+
const nextNode = nodes[index] as Node;
67+
nextNode.children = [];
68+
childrenMap.set(nextNode.level, nextNode.children);
69+
const deltaStrg = deltaToStrg(currNode, nextNode);
70+
switch (deltaStrg) {
71+
case 'upwards discontinuous': {
72+
const delta = currNode.level - nextNode.level;
73+
if (childrenMap.has(delta - 1)) {
74+
const nthParent = childrenMap.get(delta - 1);
75+
nthParent?.push(nextNode);
76+
}
77+
break;
78+
}
79+
case 'up one level': {
80+
const grandParent = childrenMap.get(currNode.level - 2);
81+
grandParent?.push(nextNode);
82+
break;
83+
}
84+
case 'same level': {
85+
const parent = childrenMap.get(currNode.level - 1);
86+
parent?.push(nextNode);
87+
break;
88+
}
89+
case 'down one level': {
90+
currNode.children.push(nextNode);
91+
break;
92+
}
93+
default:
94+
break;
95+
}
96+
currNode = nextNode;
97+
}
98+
return tree[0];
99+
}
100+
101+
type RecursiveListProps = {
102+
tree: Node;
103+
activeItem: string;
104+
limit?: number;
105+
};
106+
107+
const RecursiveList = component$<RecursiveListProps>(
108+
({ tree, activeItem, limit = 3 }) => {
109+
return tree?.children?.length && tree.level < limit ? (
110+
<ul class={cn('m-0 list-none', { 'pl-4': tree.level !== 1 })}>
111+
{tree.children.map((childNode) => (
112+
<li key={childNode.id} class="mt-0 list-none pt-2">
113+
<Anchor node={childNode} activeItem={activeItem} />
114+
{childNode.children.length > 0 && (
115+
<RecursiveList tree={childNode} activeItem={activeItem} />
116+
)}
26117
</li>
27-
</ul>
28-
))}
29-
</div>
118+
))}
119+
</ul>
120+
) : null;
121+
},
122+
);
123+
124+
const useActiveItem = (itemIds: string[]) => {
125+
const activeId = useSignal<string>();
126+
127+
useOnWindow(
128+
'scroll',
129+
$(() => {
130+
const observer = new IntersectionObserver(
131+
(entries) => {
132+
entries.forEach((entry) => {
133+
if (entry.isIntersecting) {
134+
activeId.value = entry.target.id;
135+
}
136+
});
137+
},
138+
{ rootMargin: '0% 0% -85% 0%' },
139+
);
140+
141+
itemIds.forEach((id) => {
142+
const element = document.getElementById(id);
143+
if (element) {
144+
observer.observe(element);
145+
}
146+
});
147+
148+
return () => {
149+
itemIds.forEach((id) => {
150+
const element = document.getElementById(id);
151+
if (element) {
152+
observer.unobserve(element);
153+
}
154+
});
155+
};
156+
}),
30157
);
31-
});
158+
159+
return activeId;
160+
};
161+
162+
type AnchorProps = {
163+
node: Node;
164+
activeItem: string;
165+
};
166+
167+
const Anchor = component$<AnchorProps>(({ node, activeItem }) => {
168+
const isActive = node.id === activeItem;
169+
return (
170+
<a
171+
href={`#${node.id}`}
172+
onClick$={[
173+
$(() => {
174+
const element = document.getElementById(node.id);
175+
if (element) {
176+
const navbarHeight = 90;
177+
const position =
178+
element.getBoundingClientRect().top + window.scrollY - navbarHeight;
179+
window.scrollTo({ top: position, behavior: 'auto' });
180+
}
181+
}),
182+
]}
183+
class={cn(
184+
node.level > 2 && 'ml-2',
185+
'inline-block no-underline transition-colors',
186+
isActive ? 'text-blue-500 dark:text-blue-300' : 'text-muted-foreground',
187+
)}
188+
>
189+
{node.text}
190+
</a>
191+
);
192+
});

docs/src/global.scss

+5-4
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@
8484
*/
8585

8686
html {
87-
height: 100%;
88-
&.dark {
89-
@apply bg-black;
90-
}
87+
@apply bg-[#F8F8FF];
88+
}
89+
90+
.dark {
91+
@apply bg-[#0D0F12]
9192
}
9293

9394
body {

docs/src/root.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default component$(() => {
2828
)}
2929
<RouterHead />
3030
</head>
31-
<body lang="en" class="m-auto max-w-[1200px]">
31+
<body lang="en" class="m-auto max-w-[1376px]">
3232
<RouterOutlet />
3333
{!isDev && <ServiceWorkerRegister />}
3434
</body>

0 commit comments

Comments
 (0)