Skip to content

Commit e14f12e

Browse files
authored
feat: optionally allow the primary sections of the project sidebar be collapsible (#258)
* chore: cleanup imports * feat: docs config schema to accept a `collapsible` boolean * chore: tweak the zod schema and MenuItem type * feat: add the collapsible menu * refactor: correct semantic tags for the list of links * feat: make the details collapsible by default
1 parent 19b8ce3 commit e14f12e

File tree

3 files changed

+42
-15
lines changed

3 files changed

+42
-15
lines changed

app/components/DocsLayout.tsx

+28-15
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import {
1212
useMatches,
1313
useNavigate,
1414
useParams,
15-
useRouterState,
1615
} from '@tanstack/react-router'
1716
import type { AnyOrama, SearchParamsFullText, AnyDocument } from '@orama/orama'
18-
import { SearchBox, SearchButton } from '@orama/searchbox'
17+
import { SearchBox } from '@orama/searchbox'
1918
import { Carbon } from '~/components/Carbon'
2019
import { Select } from '~/components/Select'
2120
import { useLocalStorage } from '~/utils/useLocalStorage'
@@ -25,7 +24,7 @@ import type { SelectOption } from '~/components/Select'
2524
import type { ConfigSchema, MenuItem } from '~/utils/config'
2625
import { create } from 'zustand'
2726
import { searchBoxParams, searchButtonParams } from '~/components/Orama'
28-
import { Framework, getFrameworkOptions, getLibrary } from '~/libraries'
27+
import { Framework, getFrameworkOptions } from '~/libraries'
2928
import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG'
3029
import { DocsCalloutBytes } from '~/components/DocsCalloutBytes'
3130
import { ClientOnlySearchButton } from './ClientOnlySearchButton'
@@ -179,7 +178,7 @@ const useMenuConfig = ({
179178
config: ConfigSchema
180179
repo: string
181180
frameworks: Framework[]
182-
}) => {
181+
}): MenuItem[] => {
183182
const currentFramework = useCurrentFramework(frameworks)
184183

185184
const localMenu: MenuItem = {
@@ -211,7 +210,7 @@ const useMenuConfig = ({
211210
return [
212211
localMenu,
213212
// Merge the two menus together based on their group labels
214-
...config.sections.map((section) => {
213+
...config.sections.map((section): MenuItem | undefined => {
215214
const frameworkDocs = section.frameworks?.find(
216215
(f) => f.label === currentFramework.framework
217216
)
@@ -232,9 +231,11 @@ const useMenuConfig = ({
232231
return {
233232
label: section.label,
234233
children,
234+
collapsible: section.collapsible ?? false,
235+
defaultCollapsed: section.defaultCollapsed ?? false,
235236
}
236237
}),
237-
].filter(Boolean)
238+
].filter((item) => item !== undefined)
238239
}
239240

240241
const useFrameworkConfig = ({ frameworks }: { frameworks: Framework[] }) => {
@@ -317,8 +318,7 @@ export function DocsLayout({
317318
children,
318319
}: DocsLayoutProps) {
319320
const { libraryId } = useParams({
320-
strict: false,
321-
experimental_returnIntersection: true,
321+
from: '/$libraryId/$version/docs',
322322
})
323323
const frameworkConfig = useFrameworkConfig({ frameworks })
324324
const versionConfig = useVersionConfig({ versions })
@@ -350,16 +350,29 @@ export function DocsLayout({
350350
const [showBytes, setShowBytes] = useLocalStorage('showBytes', true)
351351

352352
const menuItems = menuConfig.map((group, i) => {
353+
const WrapperComp = group.collapsible ? 'details' : 'div'
354+
const LabelComp = group.collapsible ? 'summary' : 'div'
355+
356+
const isCollapsed = group.defaultCollapsed ?? false
357+
358+
const detailsProps = group.collapsible ? { open: !isCollapsed } : {}
359+
353360
return (
354-
<div key={i}>
355-
<div className="text-[.8em] uppercase font-black">{group?.label}</div>
361+
<WrapperComp
362+
key={`group-${i}`}
363+
className="[&>summary]:before:mr-[0.4rem] [&>summary]:marker:text-[0.8em] [&>summary]:marker:-ml-[0.3rem] [&>summary]:marker:leading-4 [&>div.ts-sidebar-label]:ml-[1rem] relative select-none"
364+
{...detailsProps}
365+
>
366+
<LabelComp className="text-[.8em] uppercase font-black leading-4 ts-sidebar-label">
367+
{group?.label}
368+
</LabelComp>
356369
<div className="h-2" />
357-
<div className="ml-2 text-[.85em]">
370+
<ul className="ml-2 text-[.85em] list-none">
358371
{group?.children?.map((child, i) => {
359372
const linkClasses = `cursor-pointer flex gap-2 items-center justify-between group px-2 py-[.1rem] rounded-lg hover:bg-gray-500 hover:bg-opacity-10`
360373

361374
return (
362-
<React.Fragment key={i}>
375+
<li key={i}>
363376
{child.to.startsWith('http') ? (
364377
<a href={child.to} className={linkClasses}>
365378
{child.label}
@@ -423,11 +436,11 @@ export function DocsLayout({
423436
}}
424437
</Link>
425438
)}
426-
</React.Fragment>
439+
</li>
427440
)
428441
})}
429-
</div>
430-
</div>
442+
</ul>
443+
</WrapperComp>
431444
)
432445
})
433446

app/utils/config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export type MenuItem = {
99
to: string
1010
badge?: string
1111
}[]
12+
collapsible?: boolean
13+
defaultCollapsed?: boolean
1214
}
1315

1416
const configSchema = z.object({
@@ -36,6 +38,8 @@ const configSchema = z.object({
3638
})
3739
)
3840
.optional(),
41+
collapsible: z.boolean().optional(),
42+
defaultCollapsed: z.boolean().optional(),
3943
})
4044
),
4145
users: z.array(z.string()).optional(),

tanstack-docs-config.schema.json

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@
8787
"required": ["label", "children"],
8888
"additionalProperties": false
8989
}
90+
},
91+
"collapsible": {
92+
"type": "boolean",
93+
"default": false,
94+
"description": "Whether the section should be collapsible."
95+
},
96+
"defaultCollapsed": {
97+
"type": "boolean",
98+
"default": false,
99+
"description": "Whether the section should be collapsed by default."
90100
}
91101
}
92102
}

0 commit comments

Comments
 (0)