Skip to content

Commit 2d35b03

Browse files
committed
Aggregate all FAQ pages on one page as per design
1 parent 19428aa commit 2d35b03

File tree

3 files changed

+113
-6
lines changed

3 files changed

+113
-6
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"use client"
2+
3+
import { type ReactNode, useRef, useLayoutEffect, useState } from "react"
4+
5+
function slugify(text: string): string {
6+
return String(text)
7+
.toLowerCase()
8+
.replace(/[^a-z0-9]+/g, "-")
9+
.replace(/(^-|-$)/g, "")
10+
}
11+
12+
function FaqH1({ id, children }: { id?: string; children?: ReactNode }) {
13+
const slug = id ?? slugify(String(children))
14+
return (
15+
<h2
16+
id={slug}
17+
className="typography-h2 mb-4 mt-8 scroll-mt-24 text-neu-900 first:mt-0"
18+
>
19+
<a href={`#${slug}`} className="hover:underline">
20+
{children}
21+
</a>
22+
</h2>
23+
)
24+
}
25+
26+
function FaqH2({ id, children }: { id?: string; children?: ReactNode }) {
27+
const slug = id ?? slugify(String(children))
28+
return (
29+
<details className="group border-b border-neu-100 dark:border-neu-200 [&:first-of-type]:border-t">
30+
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 py-4 [&::-webkit-details-marker]:hidden">
31+
<h3 id={slug} className="typography-body-lg text-neu-900">
32+
{children}
33+
</h3>
34+
<ChevronIcon className="size-5 shrink-0 text-neu-600 transition-transform group-open:rotate-180" />
35+
</summary>
36+
</details>
37+
)
38+
}
39+
40+
function ChevronIcon({ className }: { className?: string }) {
41+
return (
42+
<svg
43+
xmlns="http://www.w3.org/2000/svg"
44+
viewBox="0 0 24 24"
45+
fill="none"
46+
stroke="currentColor"
47+
strokeWidth={2}
48+
strokeLinecap="round"
49+
strokeLinejoin="round"
50+
className={className}
51+
>
52+
<polyline points="6 9 12 15 18 9" />
53+
</svg>
54+
)
55+
}
56+
57+
export function FaqAggregator({ children }: { children: ReactNode }) {
58+
const containerRef = useRef<HTMLDivElement>(null)
59+
const [, forceUpdate] = useState(0)
60+
61+
useLayoutEffect(() => {
62+
const container = containerRef.current
63+
if (!container) return
64+
65+
const details = container.querySelectorAll("details")
66+
details.forEach(detail => {
67+
const answerContent: Node[] = []
68+
let sibling = detail.nextSibling
69+
70+
while (sibling) {
71+
const next = sibling.nextSibling
72+
if (sibling instanceof Element) {
73+
if (sibling.tagName === "DETAILS" || sibling.tagName === "H2") break
74+
answerContent.push(sibling)
75+
} else if (
76+
sibling.nodeType === Node.TEXT_NODE &&
77+
sibling.textContent?.trim()
78+
) {
79+
answerContent.push(sibling)
80+
}
81+
sibling = next
82+
}
83+
84+
if (answerContent.length > 0) {
85+
const wrapper = document.createElement("div")
86+
wrapper.className = "pb-4"
87+
answerContent.forEach(node => wrapper.appendChild(node))
88+
detail.appendChild(wrapper)
89+
}
90+
})
91+
92+
forceUpdate(n => n + 1)
93+
}, [])
94+
95+
return <div ref={containerRef}>{children}</div>
96+
}
97+
98+
export const faqMdxComponents = {
99+
h1: FaqH1,
100+
h2: FaqH2,
101+
}

src/pages/faq/_meta.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
export default {
22
index: {
33
title: "FAQ",
4+
theme: {
5+
toc: false,
6+
sidebar: false,
7+
},
48
},
59
"getting-started": { display: "hidden" },
610
general: { display: "hidden" },

src/pages/faq/index.mdx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import Foundation from "./foundation.mdx"
99

1010
<h1 className="typography-h1">Frequently Asked Questions</h1>
1111

12-
<GettingStarted components={faqMdxComponents} />
13-
<General components={faqMdxComponents} />
14-
<BestPractices components={faqMdxComponents} />
15-
<Specification components={faqMdxComponents} />
16-
<Frontend components={faqMdxComponents} />
17-
<Foundation components={faqMdxComponents} />
12+
<FaqAggregator>
13+
<GettingStarted components={faqMdxComponents} />
14+
<General components={faqMdxComponents} />
15+
<BestPractices components={faqMdxComponents} />
16+
<Specification components={faqMdxComponents} />
17+
<Frontend components={faqMdxComponents} />
18+
<Foundation components={faqMdxComponents} />
19+
</FaqAggregator>

0 commit comments

Comments
 (0)