From 183255b5b5661a246966ac92783a350713244a05 Mon Sep 17 00:00:00 2001 From: Adil Kairolla Date: Tue, 26 Nov 2024 13:13:49 +0500 Subject: [PATCH 1/6] feat: Add table of contents --- app/components/Doc.tsx | 15 +- app/components/DocContent.tsx | 157 ++++++++++++ app/components/DocPagination.tsx | 75 ++++++ app/components/DocsLayout.tsx | 225 +----------------- app/components/Markdown.tsx | 58 +++-- app/components/Partners.tsx | 74 ++++++ app/components/Toc.tsx | 71 ++++++ app/routes/$libraryId/$version.docs.$.tsx | 6 +- .../$version.docs.framework.$framework.$.tsx | 6 +- app/routes/$libraryId/$version.docs.tsx | 1 - app/routes/_libraries/blog.$.tsx | 2 +- tailwind.config.cjs | 3 + 12 files changed, 430 insertions(+), 263 deletions(-) create mode 100644 app/components/DocContent.tsx create mode 100644 app/components/DocPagination.tsx create mode 100644 app/components/Partners.tsx create mode 100644 app/components/Toc.tsx diff --git a/app/components/Doc.tsx b/app/components/Doc.tsx index 680102059..b1d587f6f 100644 --- a/app/components/Doc.tsx +++ b/app/components/Doc.tsx @@ -2,19 +2,14 @@ import { FaEdit } from 'react-icons/fa' import { DocTitle } from '~/components/DocTitle' import { Markdown } from '~/components/Markdown' -export function Doc({ - title, - content, - repo, - branch, - filePath, -}: { +type DocProps = { title: string - content: string repo: string branch: string filePath: string -}) { +} & ({ code: string; markup?: never } | { markup: string; code?: never }) + +export function Doc({ title, repo, branch, filePath, code, markup }: DocProps) { return (
{title ? {title} : null} @@ -22,7 +17,7 @@ export function Doc({
- +
diff --git a/app/components/DocContent.tsx b/app/components/DocContent.tsx new file mode 100644 index 000000000..964655602 --- /dev/null +++ b/app/components/DocContent.tsx @@ -0,0 +1,157 @@ +import * as React from 'react' +import { FaTimes } from 'react-icons/fa' +import { twMerge } from 'tailwind-merge' +import { useLoaderData, useMatches } from '@tanstack/react-router' +import { marked } from 'marked' +import markedAlert from 'marked-alert' +import { gfmHeadingId, getHeadingList } from 'marked-gfm-heading-id' +import { Toc } from '~/components/Toc' +import { Doc } from '~/components/Doc' +import { Carbon } from '~/components/Carbon' +import { Partners } from '~/components/Partners' +import { useMenuConfig } from '~/components/DocsLayout' +import { DocPagination } from '~/components/DocPagination' +import { DocsCalloutBytes } from '~/components/DocsCalloutBytes' +import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG' +import { Library } from '~/libraries' +import { useLocalStorage } from '~/utils/useLocalStorage' + +export function DocContent({ + title, + content, + branch, + filePath, + library, +}: { + title: string + content: string + branch: string + filePath: string + library: Library +}) { + const matches = useMatches() + const [showBytes, setShowBytes] = useLocalStorage('showBytes', true) + const { config } = useLoaderData({ from: '/$libraryId/$version/docs' }) + const menuConfig = useMenuConfig({ + config, + frameworks: library.frameworks, + repo: library.repo, + }) + + const { markup, headings } = React.useMemo(() => { + const markup = marked.use( + { gfm: true }, + gfmHeadingId(), + markedAlert() + )(content) as string + + const headings = getHeadingList() + + return { markup, headings } + }, [content]) + + const isExample = matches.some((d) => d.pathname.includes('/examples/')) + + return ( + <> +
+ + +
+
+
+ + + {headings.length ? ( + + ) : null} + +
+ {library.id === 'query' ? ( + + ) : ( + + )} +
+ +
+ +
+ This ad helps to keep us from burning out and rage-quitting OSS + just *that* much more, so chill. 😉 +
+
+
+
+ {showBytes ? ( +
+
+ {library.id === 'query' ? ( + + ) : ( + + )} + +
+
+ ) : ( + + )} + + ) +} diff --git a/app/components/DocPagination.tsx b/app/components/DocPagination.tsx new file mode 100644 index 000000000..02c10cf08 --- /dev/null +++ b/app/components/DocPagination.tsx @@ -0,0 +1,75 @@ +import * as React from 'react' +import { Link, useMatches } from '@tanstack/react-router' +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa' +import { last } from '~/utils/utils' +import { MenuItem } from '~/utils/config' + +type DocPaginationProps = { + config: MenuItem[] + colorFrom: string + colorTo: string + textColor: string +} + +export function DocPagination({ + config, + colorFrom, + colorTo, + textColor, +}: DocPaginationProps) { + const matches = useMatches() + + const flatMenu = React.useMemo( + () => config.flatMap((d) => d?.children), + [config] + ) + + const lastMatch = last(matches) + const docsMatch = matches.find((d) => d.pathname.includes('/docs')) + + const relativePathname = lastMatch.pathname.replace( + docsMatch!.pathname + '/', + '' + ) + + const index = flatMenu.findIndex((d) => d?.to === relativePathname) + const prevItem = flatMenu[index - 1] + const nextItem = flatMenu[index + 1] + + return ( +
+
+ {prevItem ? ( + +
+ + {prevItem.label} +
+ + ) : null} +
+
+ {nextItem ? ( + +
+ + {nextItem.label} + {' '} + +
+ + ) : null} +
+
+ ) +} diff --git a/app/components/DocsLayout.tsx b/app/components/DocsLayout.tsx index ad909667c..3b28159ae 100644 --- a/app/components/DocsLayout.tsx +++ b/app/components/DocsLayout.tsx @@ -1,34 +1,18 @@ import * as React from 'react' import { CgClose, CgMenuLeft } from 'react-icons/cg' -import { - FaArrowLeft, - FaArrowRight, - FaDiscord, - FaGithub, - FaTimes, -} from 'react-icons/fa' -import { - Link, - useMatches, - useNavigate, - useParams, -} from '@tanstack/react-router' +import { FaDiscord, FaGithub } from 'react-icons/fa' +import { Link, useNavigate, useParams } from '@tanstack/react-router' import { OramaSearchBox } from '@orama/react-components' -import { Carbon } from '~/components/Carbon' import { Select } from '~/components/Select' -import { useLocalStorage } from '~/utils/useLocalStorage' import { DocsLogo } from '~/components/DocsLogo' -import { last, capitalize } from '~/utils/utils' +import { capitalize } from '~/utils/utils' import type { SelectOption } from '~/components/Select' import type { ConfigSchema, MenuItem } from '~/utils/config' import { create } from 'zustand' import { searchBoxParams, searchButtonParams } from '~/components/Orama' import { Framework, getFrameworkOptions } from '~/libraries' -import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG' -import { DocsCalloutBytes } from '~/components/DocsCalloutBytes' import { ClientOnlySearchButton } from './ClientOnlySearchButton' import { twMerge } from 'tailwind-merge' -import { partners } from '~/utils/partners' // Let's use zustand to wrap the local storage logic. This way // we'll get subscriptions for free and we can use it in other @@ -167,7 +151,7 @@ function useCurrentVersion(versions: string[]) { } } -const useMenuConfig = ({ +export const useMenuConfig = ({ config, repo, frameworks, @@ -294,7 +278,6 @@ type DocsLayoutProps = { version: string colorFrom: string colorTo: string - textColor: string config: ConfigSchema frameworks: Framework[] versions: string[] @@ -307,7 +290,6 @@ export function DocsLayout({ version, colorFrom, colorTo, - textColor, config, frameworks, versions, @@ -321,31 +303,8 @@ export function DocsLayout({ const versionConfig = useVersionConfig({ versions }) const menuConfig = useMenuConfig({ config, frameworks, repo }) - const matches = useMatches() - const lastMatch = last(matches) - - const isExample = matches.some((d) => d.pathname.includes('/examples/')) - const detailsRef = React.useRef(null!) - const flatMenu = React.useMemo( - () => menuConfig.flatMap((d) => d?.children), - [menuConfig] - ) - - const docsMatch = matches.find((d) => d.pathname.includes('/docs')) - - const relativePathname = lastMatch.pathname.replace( - docsMatch!.pathname + '/', - '' - ) - - const index = flatMenu.findIndex((d) => d?.to === relativePathname) - const prevItem = flatMenu[index - 1] - const nextItem = flatMenu[index + 1] - - const [showBytes, setShowBytes] = useLocalStorage('showBytes', true) - const [mounted, setMounted] = React.useState(false) React.useEffect(() => { @@ -559,181 +518,7 @@ export function DocsLayout({
{smallMenu} {largeMenu} -
- {children} -
-
- {prevItem ? ( - -
- - {prevItem.label} -
- - ) : null} -
-
- {nextItem ? ( - -
- - {nextItem.label} - {' '} - -
- - ) : null} -
-
-
-
-
-
-
- Our Partners -
- {!partners.some((d) => d.libraries?.includes(libraryId as any)) ? ( - - ) : ( - partners - .filter((d) => d.sidebarImgLight) - .filter((d) => d.libraries?.includes(libraryId as any)) - .map((partner) => { - return ( - - ) - }) - )} -
-
- {libraryId === 'query' ? ( - - ) : ( - - )} -
- -
- -
- This ad helps to keep us from burning out and rage-quitting OSS - just *that* much more, so chill. 😉 -
-
-
-
- {showBytes ? ( -
-
- {libraryId === 'query' ? ( - - ) : ( - - )} - -
-
- ) : ( - - )} + {children}
) } diff --git a/app/components/Markdown.tsx b/app/components/Markdown.tsx index d46905cda..0bd83ff96 100644 --- a/app/components/Markdown.tsx +++ b/app/components/Markdown.tsx @@ -206,33 +206,41 @@ const getHighlighter = cache(async (language: string, themes: string[]) => { return highlighter }) -export function Markdown({ code }: { code: string }) { - const jsx = React.useMemo(() => { - const markup = marked.use( - { gfm: true }, - gfmHeadingId(), - markedAlert() - )(code) as string - - const options: HTMLReactParserOptions = { - replace: (domNode) => { - if (domNode instanceof Element && domNode.attribs) { - const replacer = markdownComponents[domNode.name] - if (replacer) { - return React.createElement( - replacer, - attributesToProps(domNode.attribs), - domToReact(domNode.children, options) - ) - } - } +const options: HTMLReactParserOptions = { + replace: (domNode) => { + if (domNode instanceof Element && domNode.attribs) { + const replacer = markdownComponents[domNode.name] + if (replacer) { + return React.createElement( + replacer, + attributesToProps(domNode.attribs), + domToReact(domNode.children, options) + ) + } + } + + return + }, +} + +type MarkdownProps = { code?: string; markup?: string } - return - }, +export function Markdown({ code, markup }: MarkdownProps) { + return React.useMemo(() => { + if (code) { + const markup = marked.use( + { gfm: true }, + gfmHeadingId(), + markedAlert() + )(code) as string + + return parse(markup, options) } - return parse(markup, options) - }, [code]) + if (markup) { + return parse(markup, options) + } - return jsx + return null + }, [code, markup]) } diff --git a/app/components/Partners.tsx b/app/components/Partners.tsx new file mode 100644 index 000000000..cf4c49308 --- /dev/null +++ b/app/components/Partners.tsx @@ -0,0 +1,74 @@ +import { twMerge } from 'tailwind-merge' +import { partners } from '~/utils/partners' + +type PartnersProps = { + libraryId: string + repo: string +} + +export function Partners({ libraryId, repo }: PartnersProps) { + return ( +
+
+ Our Partners +
+ + {!partners.some((d) => d.libraries?.includes(libraryId as any)) ? ( + + ) : ( + partners + .filter((d) => d.sidebarImgLight) + .filter((d) => d.libraries?.includes(libraryId as any)) + .map((partner) => { + return ( + + ) + }) + )} +
+ ) +} diff --git a/app/components/Toc.tsx b/app/components/Toc.tsx new file mode 100644 index 000000000..275a3475f --- /dev/null +++ b/app/components/Toc.tsx @@ -0,0 +1,71 @@ +import * as React from 'react' +import { twMerge } from 'tailwind-merge' +import { HeadingData } from 'marked-gfm-heading-id' +import { useLocation } from '@tanstack/react-router' + +const headingLevels: Record = { + 1: 'pl-1', + 2: 'pl-2', + 3: 'pl-6', + 4: 'pl-10', + 5: 'pl-14', + 6: 'pl-16', +} + +export function Toc({ + colorFrom, + colorTo, + headings, +}: { + headings: HeadingData[] + colorFrom: string + colorTo: string +}) { + const location = useLocation() + + const [hash, setHash] = React.useState('') + const [isCollapsed, setIsCollapsed] = React.useState(true) + + React.useEffect(() => { + setHash(location.hash) + }, [location]) + + function toggleCollapse() { + setIsCollapsed((prev) => !prev) + } + + return ( + + ) +} diff --git a/app/routes/$libraryId/$version.docs.$.tsx b/app/routes/$libraryId/$version.docs.$.tsx index de31e616b..cf6649518 100644 --- a/app/routes/$libraryId/$version.docs.$.tsx +++ b/app/routes/$libraryId/$version.docs.$.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from '@tanstack/react-router' import { seo } from '~/utils/seo' -import { Doc } from '~/components/Doc' +import { DocContent } from '~/components/DocContent' import { loadDocs } from '~/utils/docs' import { getBranch, getLibrary } from '~/libraries' @@ -38,10 +38,10 @@ function Docs() { const branch = getBranch(library, version) return ( - diff --git a/app/routes/$libraryId/$version.docs.framework.$framework.$.tsx b/app/routes/$libraryId/$version.docs.framework.$framework.$.tsx index dd68d322b..3ee018a59 100644 --- a/app/routes/$libraryId/$version.docs.framework.$framework.$.tsx +++ b/app/routes/$libraryId/$version.docs.framework.$framework.$.tsx @@ -1,6 +1,6 @@ import { seo } from '~/utils/seo' import { createFileRoute } from '@tanstack/react-router' -import { Doc } from '~/components/Doc' +import { DocContent } from '~/components/DocContent' import { loadDocs } from '~/utils/docs' import { getBranch, getLibrary } from '~/libraries' import { capitalize } from '~/utils/utils' @@ -44,10 +44,10 @@ function Docs() { const branch = getBranch(library, version) return ( - diff --git a/app/routes/$libraryId/$version.docs.tsx b/app/routes/$libraryId/$version.docs.tsx index 9740cdaf6..8c2592b8f 100644 --- a/app/routes/$libraryId/$version.docs.tsx +++ b/app/routes/$libraryId/$version.docs.tsx @@ -44,7 +44,6 @@ function DocsRoute() { version={version === 'latest' ? library.latestVersion : version!} colorFrom={library.colorFrom} colorTo={library.colorTo} - textColor={library.textColor} config={config} frameworks={library.frameworks} versions={library.availableVersions} diff --git a/app/routes/_libraries/blog.$.tsx b/app/routes/_libraries/blog.$.tsx index bb0658b9b..56e75b46a 100644 --- a/app/routes/_libraries/blog.$.tsx +++ b/app/routes/_libraries/blog.$.tsx @@ -73,7 +73,7 @@ ${content}` return ( Date: Tue, 26 Nov 2024 13:13:49 +0500 Subject: [PATCH 2/6] fix: Fix merge artifacts --- app/components/DocContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/DocContent.tsx b/app/components/DocContent.tsx index 964655602..f61c8831d 100644 --- a/app/components/DocContent.tsx +++ b/app/components/DocContent.tsx @@ -57,7 +57,7 @@ export function DocContent({
Date: Wed, 27 Nov 2024 14:45:29 +0500 Subject: [PATCH 3/6] feat: update Toc hover colors for light theme --- app/components/Toc.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Toc.tsx b/app/components/Toc.tsx index 275a3475f..025afaefb 100644 --- a/app/components/Toc.tsx +++ b/app/components/Toc.tsx @@ -37,7 +37,7 @@ export function Toc({ return (
-
- ) : ( - - )} - - ) -} diff --git a/app/components/DocPagination.tsx b/app/components/DocPagination.tsx deleted file mode 100644 index 02c10cf08..000000000 --- a/app/components/DocPagination.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from 'react' -import { Link, useMatches } from '@tanstack/react-router' -import { FaArrowLeft, FaArrowRight } from 'react-icons/fa' -import { last } from '~/utils/utils' -import { MenuItem } from '~/utils/config' - -type DocPaginationProps = { - config: MenuItem[] - colorFrom: string - colorTo: string - textColor: string -} - -export function DocPagination({ - config, - colorFrom, - colorTo, - textColor, -}: DocPaginationProps) { - const matches = useMatches() - - const flatMenu = React.useMemo( - () => config.flatMap((d) => d?.children), - [config] - ) - - const lastMatch = last(matches) - const docsMatch = matches.find((d) => d.pathname.includes('/docs')) - - const relativePathname = lastMatch.pathname.replace( - docsMatch!.pathname + '/', - '' - ) - - const index = flatMenu.findIndex((d) => d?.to === relativePathname) - const prevItem = flatMenu[index - 1] - const nextItem = flatMenu[index + 1] - - return ( -
-
- {prevItem ? ( - -
- - {prevItem.label} -
- - ) : null} -
-
- {nextItem ? ( - -
- - {nextItem.label} - {' '} - -
- - ) : null} -
-
- ) -} diff --git a/app/components/DocsLayout.tsx b/app/components/DocsLayout.tsx index 3b28159ae..004cfec2f 100644 --- a/app/components/DocsLayout.tsx +++ b/app/components/DocsLayout.tsx @@ -1,18 +1,34 @@ import * as React from 'react' import { CgClose, CgMenuLeft } from 'react-icons/cg' -import { FaDiscord, FaGithub } from 'react-icons/fa' -import { Link, useNavigate, useParams } from '@tanstack/react-router' +import { + FaArrowLeft, + FaArrowRight, + FaDiscord, + FaGithub, + FaTimes, +} from 'react-icons/fa' +import { + Link, + useMatches, + useNavigate, + useParams, +} from '@tanstack/react-router' import { OramaSearchBox } from '@orama/react-components' +import { Carbon } from '~/components/Carbon' import { Select } from '~/components/Select' +import { useLocalStorage } from '~/utils/useLocalStorage' import { DocsLogo } from '~/components/DocsLogo' -import { capitalize } from '~/utils/utils' +import { last, capitalize } from '~/utils/utils' import type { SelectOption } from '~/components/Select' import type { ConfigSchema, MenuItem } from '~/utils/config' import { create } from 'zustand' import { searchBoxParams, searchButtonParams } from '~/components/Orama' import { Framework, getFrameworkOptions } from '~/libraries' +import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG' +import { DocsCalloutBytes } from '~/components/DocsCalloutBytes' import { ClientOnlySearchButton } from './ClientOnlySearchButton' import { twMerge } from 'tailwind-merge' +import { partners } from '~/utils/partners' // Let's use zustand to wrap the local storage logic. This way // we'll get subscriptions for free and we can use it in other @@ -151,7 +167,7 @@ function useCurrentVersion(versions: string[]) { } } -export const useMenuConfig = ({ +const useMenuConfig = ({ config, repo, frameworks, @@ -278,6 +294,7 @@ type DocsLayoutProps = { version: string colorFrom: string colorTo: string + textColor: string config: ConfigSchema frameworks: Framework[] versions: string[] @@ -290,6 +307,7 @@ export function DocsLayout({ version, colorFrom, colorTo, + textColor, config, frameworks, versions, @@ -303,8 +321,31 @@ export function DocsLayout({ const versionConfig = useVersionConfig({ versions }) const menuConfig = useMenuConfig({ config, frameworks, repo }) + const matches = useMatches() + const lastMatch = last(matches) + + const isExample = matches.some((d) => d.pathname.includes('/examples/')) + const detailsRef = React.useRef(null!) + const flatMenu = React.useMemo( + () => menuConfig.flatMap((d) => d?.children), + [menuConfig] + ) + + const docsMatch = matches.find((d) => d.pathname.includes('/docs')) + + const relativePathname = lastMatch.pathname.replace( + docsMatch!.pathname + '/', + '' + ) + + const index = flatMenu.findIndex((d) => d?.to === relativePathname) + const prevItem = flatMenu[index - 1] + const nextItem = flatMenu[index + 1] + + const [showBytes, setShowBytes] = useLocalStorage('showBytes', true) + const [mounted, setMounted] = React.useState(false) React.useEffect(() => { @@ -518,7 +559,181 @@ export function DocsLayout({
{smallMenu} {largeMenu} - {children} +
+ {children} +
+
+ {prevItem ? ( + +
+ + {prevItem.label} +
+ + ) : null} +
+
+ {nextItem ? ( + +
+ + {nextItem.label} + {' '} + +
+ + ) : null} +
+
+
+
+
+
+
+ Our Partners +
+ {!partners.some((d) => d.libraries?.includes(libraryId as any)) ? ( + + ) : ( + partners + .filter((d) => d.sidebarImgLight) + .filter((d) => d.libraries?.includes(libraryId as any)) + .map((partner) => { + return ( + + ) + }) + )} +
+
+ {libraryId === 'query' ? ( + + ) : ( + + )} +
+ +
+ +
+ This ad helps to keep us from burning out and rage-quitting OSS + just *that* much more, so chill. 😉 +
+
+
+
+ {showBytes ? ( +
+
+ {libraryId === 'query' ? ( + + ) : ( + + )} + +
+
+ ) : ( + + )}
) } diff --git a/app/components/Markdown.tsx b/app/components/Markdown.tsx index 0bd83ff96..ef76af3b9 100644 --- a/app/components/Markdown.tsx +++ b/app/components/Markdown.tsx @@ -223,24 +223,24 @@ const options: HTMLReactParserOptions = { }, } -type MarkdownProps = { code?: string; markup?: string } +type MarkdownProps = { rawContent?: string; htmlMarkup?: string } -export function Markdown({ code, markup }: MarkdownProps) { +export function Markdown({ rawContent, htmlMarkup }: MarkdownProps) { return React.useMemo(() => { - if (code) { + if (rawContent) { const markup = marked.use( { gfm: true }, gfmHeadingId(), markedAlert() - )(code) as string + )(rawContent) as string return parse(markup, options) } - if (markup) { - return parse(markup, options) + if (htmlMarkup) { + return parse(htmlMarkup, options) } return null - }, [code, markup]) + }, [rawContent, htmlMarkup]) } diff --git a/app/components/Partners.tsx b/app/components/Partners.tsx deleted file mode 100644 index cf4c49308..000000000 --- a/app/components/Partners.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { twMerge } from 'tailwind-merge' -import { partners } from '~/utils/partners' - -type PartnersProps = { - libraryId: string - repo: string -} - -export function Partners({ libraryId, repo }: PartnersProps) { - return ( -
-
- Our Partners -
- - {!partners.some((d) => d.libraries?.includes(libraryId as any)) ? ( -
- - - Wow, it looks like you could be our first partner for this - library! - {' '} - Chat with us! - -
- ) : ( - partners - .filter((d) => d.sidebarImgLight) - .filter((d) => d.libraries?.includes(libraryId as any)) - .map((partner) => { - return ( -
- -
- {partner.name} - {partner.name} -
-
-
- ) - }) - )} -
- ) -} diff --git a/app/components/Toc.tsx b/app/components/Toc.tsx index 025afaefb..bf5296590 100644 --- a/app/components/Toc.tsx +++ b/app/components/Toc.tsx @@ -4,7 +4,7 @@ import { HeadingData } from 'marked-gfm-heading-id' import { useLocation } from '@tanstack/react-router' const headingLevels: Record = { - 1: 'pl-1', + 1: 'pl-2', 2: 'pl-2', 3: 'pl-6', 4: 'pl-10', @@ -12,57 +12,45 @@ const headingLevels: Record = { 6: 'pl-16', } -export function Toc({ - colorFrom, - colorTo, - headings, -}: { +type TocProps = { headings: HeadingData[] - colorFrom: string - colorTo: string -}) { + colorFrom?: string + colorTo?: string +} + +export function Toc({ headings, colorFrom, colorTo }: TocProps) { const location = useLocation() const [hash, setHash] = React.useState('') - const [isCollapsed, setIsCollapsed] = React.useState(true) React.useEffect(() => { setHash(location.hash) }, [location]) - function toggleCollapse() { - setIsCollapsed((prev) => !prev) - } - return ( -