diff --git a/frontend/app/[topic]/TopicLayout.tsx b/frontend/app/[topic]/TopicLayout.tsx index c393bc4..36ae1df 100644 --- a/frontend/app/[topic]/TopicLayout.tsx +++ b/frontend/app/[topic]/TopicLayout.tsx @@ -36,16 +36,20 @@ export default function TopicLayout({ const router = useRouter(); const searchInputRef = useRef(null); const [docSearch, setDocSearch] = useState(""); - const [domHeadings, setDomHeadings] = useState>([]); + const [domHeadings, setDomHeadings] = useState< + Array<{ id: string; title: string; depth: number }> + >([]); useEffect(() => { - const article = document.querySelector('article'); - const headingNodes = article ? Array.from(article.querySelectorAll('h2[id], h3[id]')) : []; + const article = document.querySelector("article"); + const headingNodes = article + ? Array.from(article.querySelectorAll("h2[id], h3[id]")) + : []; const nextHeadings = headingNodes.map((node) => ({ id: node.id, title: node.textContent?.trim() || node.id, - depth: node.tagName === 'H3' ? 3 : 2, + depth: node.tagName === "H3" ? 3 : 2, })); setDomHeadings(nextHeadings); @@ -59,7 +63,9 @@ export default function TopicLayout({ if (!hasSearchQuery) return []; const sectionMatches = activeHeadings - .filter((heading) => heading.title.toLowerCase().includes(normalizedQuery)) + .filter((heading) => + heading.title.toLowerCase().includes(normalizedQuery), + ) .map((heading) => ({ id: heading.id, title: heading.title, @@ -71,7 +77,10 @@ export default function TopicLayout({ .filter((item) => { const title = (item.title || item.id || "").toLowerCase(); const description = (item.description || "").toLowerCase(); - return title.includes(normalizedQuery) || description.includes(normalizedQuery); + return ( + title.includes(normalizedQuery) || + description.includes(normalizedQuery) + ); }) .map((item) => ({ id: item.id, @@ -84,12 +93,21 @@ export default function TopicLayout({ }, [activeHeadings, hasSearchQuery, normalizedQuery, topics]); const outlineGroups = useMemo(() => { - const groups: Array<{ id: string; title: string; items: Array<{ id: string; title: string }> }> = []; - let activeGroup: { id: string; title: string; items: Array<{ id: string; title: string }> } | null = null; + const groups: Array<{ + id: string; + title: string; + items: Array<{ id: string; title: string }>; + }> = []; + let activeGroup: { + id: string; + title: string; + items: Array<{ id: string; title: string }>; + } | null = null; for (const heading of activeHeadings) { const headingQuery = heading.title.toLowerCase(); - const matchesQuery = !hasSearchQuery || headingQuery.includes(normalizedQuery); + const matchesQuery = + !hasSearchQuery || headingQuery.includes(normalizedQuery); if (heading.depth === 2) { if (hasSearchQuery && !matchesQuery) continue; @@ -109,11 +127,16 @@ export default function TopicLayout({ return groups; }, [activeHeadings, hasSearchQuery, normalizedQuery]); - const [expandedOutline, setExpandedOutline] = useState>({}); + const [expandedOutline, setExpandedOutline] = useState< + Record + >({}); const [activeOutlineId, setActiveOutlineId] = useState(null); useEffect(() => { - const ids = outlineGroups.flatMap((group) => [group.id, ...group.items.map((item) => item.id)]); + const ids = outlineGroups.flatMap((group) => [ + group.id, + ...group.items.map((item) => item.id), + ]); if (ids.length === 0) { setActiveOutlineId(null); return; @@ -133,7 +156,9 @@ export default function TopicLayout({ } } - setActiveOutlineId((previous) => previous === currentId ? previous : currentId); + setActiveOutlineId((previous) => + previous === currentId ? previous : currentId, + ); }; updateActiveOutline(); @@ -171,8 +196,16 @@ export default function TopicLayout({ }, [outlineGroups]); const sectionGroups = useMemo(() => { - const groups: Array<{ id: string; title: string; items: Array<{ id: string; title: string }> }> = []; - let activeGroup: { id: string; title: string; items: Array<{ id: string; title: string }> } | null = null; + const groups: Array<{ + id: string; + title: string; + items: Array<{ id: string; title: string }>; + }> = []; + let activeGroup: { + id: string; + title: string; + items: Array<{ id: string; title: string }>; + } | null = null; for (const heading of headings) { if (heading.depth === 2) { @@ -189,7 +222,9 @@ export default function TopicLayout({ return groups; }, [headings]); - const [expandedSections, setExpandedSections] = useState>({}); + const [expandedSections, setExpandedSections] = useState< + Record + >({}); useEffect(() => { setExpandedSections((previous) => { @@ -204,15 +239,21 @@ export default function TopicLayout({ }, [sectionGroups]); const theme = { - page: darkMode ? "bg-[#050505] text-[#F5F5F5]" : "bg-[#F5F3EE] text-[#111111]", + page: darkMode + ? "bg-[#050505] text-[#F5F5F5]" + : "bg-[#F5F3EE] text-[#111111]", panel: darkMode ? "bg-[#050505]" : "bg-[#F5F3EE]", border: darkMode ? "border-[#1A1A1A]" : "border-[#DFE1DA]", muted: darkMode ? "text-[#8A8A8A]" : "text-[#666C73]", - soft: darkMode ? "bg-[#0D0D0D] text-[#F5F5F5]" : "bg-[#F0EFEA] text-[#111111]", + soft: darkMode + ? "bg-[#0D0D0D] text-[#F5F5F5]" + : "bg-[#F0EFEA] text-[#111111]", active: darkMode ? "bg-[#111111] text-white" : "bg-white text-black", accent: darkMode ? "text-zinc-100" : "text-zinc-800", hoverAccent: darkMode ? "hover:text-white" : "hover:text-zinc-900", - input: darkMode ? "border-[#1D1D1D] bg-[#0B0B0B]" : "border-[#DDE0D7] bg-[#F7F6F3]", + input: darkMode + ? "border-[#1D1D1D] bg-[#0B0B0B]" + : "border-[#DDE0D7] bg-[#F7F6F3]", sidebar: darkMode ? "bg-[#050505]" : "bg-[#F2F0EA]", navItem: darkMode ? "text-[#F5F5F5]" : "text-[#111111]", }; @@ -220,7 +261,8 @@ export default function TopicLayout({ const getTopicIcon = (topicId: string) => { const value = topicId.toLowerCase(); - if (value.includes("javascript") || value.includes("js")) return TerminalSquare; + if (value.includes("javascript") || value.includes("js")) + return TerminalSquare; if (value.includes("python")) return Braces; if (value.includes("react")) return Workflow; if (value.includes("cpp") || value.includes("c++")) return Code2; @@ -230,13 +272,44 @@ export default function TopicLayout({ const getSectionIcon = (title: string) => { const value = title.toLowerCase(); - if (value.includes("getting") || value.includes("overview") || value.includes("introduction")) return BookOpenText; - if (value.includes("variable") || value.includes("data") || value.includes("types")) return Braces; - if (value.includes("console") || value.includes("terminal")) return TerminalSquare; - if (value.includes("function") || value.includes("module") || value.includes("async")) return Code2; - if (value.includes("security") || value.includes("protection") || value.includes("rules")) return ShieldCheck; - if (value.includes("best practice") || value.includes("tips") || value.includes("patterns")) return Sparkles; - if (value.includes("workflow") || value.includes("lifecycle") || value.includes("reconciliation")) return Workflow; + if ( + value.includes("getting") || + value.includes("overview") || + value.includes("introduction") + ) + return BookOpenText; + if ( + value.includes("variable") || + value.includes("data") || + value.includes("types") + ) + return Braces; + if (value.includes("console") || value.includes("terminal")) + return TerminalSquare; + if ( + value.includes("function") || + value.includes("module") || + value.includes("async") + ) + return Code2; + if ( + value.includes("security") || + value.includes("protection") || + value.includes("rules") + ) + return ShieldCheck; + if ( + value.includes("best practice") || + value.includes("tips") || + value.includes("patterns") + ) + return Sparkles; + if ( + value.includes("workflow") || + value.includes("lifecycle") || + value.includes("reconciliation") + ) + return Workflow; return BookOpenText; }; @@ -247,75 +320,96 @@ export default function TopicLayout({ if (!target) return; const headerOffset = 88; - const top = target.getBoundingClientRect().top + window.scrollY - headerOffset; + const top = + target.getBoundingClientRect().top + window.scrollY - headerOffset; window.scrollTo({ top, behavior: "smooth" }); }; return ( -
-
-
-
-
- R_ +
+
+
+
+
+ + R_ +
-
+
RefMe docs
-
-
-
- +
+
+
+ setDocSearch(event.target.value)} - aria-label='Search docs' - placeholder='Search topics and sections...' - className='w-full bg-transparent text-sm outline-none placeholder:text-current placeholder:opacity-50' + aria-label="Search docs" + placeholder="Search topics and sections..." + className="w-full bg-transparent text-sm outline-none placeholder:text-current placeholder:opacity-50" /> {docSearch ? ( ) : ( - ⌘K + + ⌘K + )}
{hasSearchQuery && ( -
+
{searchResults.length > 0 ? ( -
+
{searchResults.map((result) => ( ))}
@@ -330,29 +424,31 @@ export default function TopicLayout({
-
-