Skip to content

Commit c09fe03

Browse files
fix docs navigation stability and search filter interactions (#772)
1 parent fb4dc25 commit c09fe03

File tree

11 files changed

+147
-42
lines changed

11 files changed

+147
-42
lines changed

src/components/CopyPageDropdown.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import {
1010
DropdownContent,
1111
DropdownItem,
1212
} from './Dropdown'
13-
import {
14-
getPackageManager,
15-
PACKAGE_MANAGERS,
16-
} from '~/utils/markdown/installCommand'
13+
import { getPackageManager } from '~/utils/markdown/installCommand'
1714

1815
// Markdown icon component matching the screenshot
1916
function MarkdownIcon({ className }: { className?: string }) {

src/components/DocsLayout.tsx

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -534,28 +534,59 @@ export function DocsLayout({
534534
d.status === 'active' && d.name !== 'Nozzle.io' && d.id !== 'fireship',
535535
)
536536

537-
const menuItems = menuConfig.map((group, i) => {
538-
const WrapperComp = group.collapsible ? 'details' : 'div'
539-
const LabelComp = group.collapsible ? 'summary' : 'div'
537+
const groupInitialOpenState = React.useMemo(() => {
538+
return menuConfig.reduce<Record<string, boolean>>((acc, group, index) => {
539+
const isChildActive = group.children.some((child) => child.to === _splat)
540+
const key = `${index}:${String(group.label)}`
540541

541-
const isChildActive = group.children.some((d) => d.to === _splat)
542-
const configGroupOpenState =
543-
typeof group.defaultCollapsed !== 'undefined'
544-
? !group.defaultCollapsed // defaultCollapsed is true means the group is closed
545-
: undefined
546-
const isOpen = isChildActive ? true : (configGroupOpenState ?? false)
542+
acc[key] = isChildActive
543+
? true
544+
: typeof group.defaultCollapsed !== 'undefined'
545+
? !group.defaultCollapsed
546+
: false
547547

548-
const detailsProps = group.collapsible ? { open: isOpen } : {}
548+
return acc
549+
}, {})
550+
}, [menuConfig, _splat])
549551

550-
return (
551-
<WrapperComp
552-
key={`group-${i}`}
553-
className="[&>summary]:before:mr-1 [&>summary]:marker:text-[0.8em] [&>summary]:marker:leading-4 relative select-none"
554-
{...detailsProps}
555-
>
556-
<LabelComp className="text-[.8em] font-bold leading-4 px-2 ts-sidebar-label">
557-
{group?.label}
558-
</LabelComp>
552+
const [openGroups, setOpenGroups] = React.useState(groupInitialOpenState)
553+
554+
React.useEffect(() => {
555+
setOpenGroups((prev) => {
556+
let hasChanged = false
557+
const next = { ...prev }
558+
559+
Object.entries(groupInitialOpenState).forEach(([key, isOpen]) => {
560+
if (!(key in next)) {
561+
next[key] = isOpen
562+
hasChanged = true
563+
return
564+
}
565+
566+
if (isOpen && !next[key]) {
567+
next[key] = true
568+
hasChanged = true
569+
}
570+
})
571+
572+
return hasChanged ? next : prev
573+
})
574+
}, [groupInitialOpenState])
575+
576+
const menuItems = menuConfig.map((group, i) => {
577+
const groupKey = `${i}:${String(group.label)}`
578+
579+
const groupContent = (
580+
<>
581+
{group.collapsible ? (
582+
<summary className="text-[.8em] font-bold leading-4 px-2 ts-sidebar-label">
583+
{group.label}
584+
</summary>
585+
) : (
586+
<div className="text-[.8em] font-bold leading-4 px-2 ts-sidebar-label">
587+
{group.label}
588+
</div>
589+
)}
559590
<div className="h-2" />
560591
<ul className="text-[.85em] leading-snug list-none">
561592
{group?.children?.map((child, i) => {
@@ -580,6 +611,7 @@ export function DocsLayout({
580611
onClick={() => {
581612
detailsRef.current.removeAttribute('open')
582613
}}
614+
preload={false}
583615
activeOptions={{
584616
exact: true,
585617
includeHash: false,
@@ -614,7 +646,32 @@ export function DocsLayout({
614646
)
615647
})}
616648
</ul>
617-
</WrapperComp>
649+
</>
650+
)
651+
652+
return group.collapsible ? (
653+
<details
654+
key={`group-${i}`}
655+
className="[&>summary]:before:mr-1 [&>summary]:marker:text-[0.8em] [&>summary]:marker:leading-4 relative select-none"
656+
open={openGroups[groupKey] ?? false}
657+
onToggle={(event) => {
658+
const nextOpen = event.currentTarget.open
659+
setOpenGroups((prev) =>
660+
prev[groupKey] === nextOpen
661+
? prev
662+
: { ...prev, [groupKey]: nextOpen },
663+
)
664+
}}
665+
>
666+
{groupContent}
667+
</details>
668+
) : (
669+
<div
670+
key={`group-${i}`}
671+
className="[&>summary]:before:mr-1 [&>summary]:marker:text-[0.8em] [&>summary]:marker:leading-4 relative select-none"
672+
>
673+
{groupContent}
674+
</div>
618675
)
619676
})
620677

src/components/SearchModal.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -565,9 +565,12 @@ function LibraryRefinement() {
565565
const currentLibrary = libraries.find((l) => l.id === selectedLibrary)
566566

567567
return (
568-
<Dropdown>
569-
<DropdownTrigger asChild={false}>
570-
<button className="flex items-center gap-1 text-sm focus:outline-none cursor-pointer font-bold">
568+
<Dropdown modal={false}>
569+
<DropdownTrigger>
570+
<button
571+
type="button"
572+
className="flex items-center gap-1 text-sm focus:outline-none cursor-pointer font-bold"
573+
>
571574
{currentLibrary ? (
572575
<span className="uppercase font-black [letter-spacing:-.05em]">
573576
<span className="opacity-50">TanStack</span>{' '}
@@ -637,9 +640,12 @@ function FrameworkRefinement() {
637640
)
638641

639642
return (
640-
<Dropdown>
641-
<DropdownTrigger asChild={false}>
642-
<button className="flex items-center gap-1 text-sm font-bold focus:outline-none cursor-pointer">
643+
<Dropdown modal={false}>
644+
<DropdownTrigger>
645+
<button
646+
type="button"
647+
className="flex items-center gap-1 text-sm font-bold focus:outline-none cursor-pointer"
648+
>
643649
{currentFramework && (
644650
<img
645651
src={currentFramework.logo}

src/routes/$libraryId/$version.docs.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import { Outlet, useMatch, createFileRoute } from '@tanstack/react-router'
1+
import {
2+
Outlet,
3+
useMatch,
4+
notFound,
5+
createFileRoute,
6+
} from '@tanstack/react-router'
27
import { DocsLayout } from '~/components/DocsLayout'
3-
import { getLibrary } from '~/libraries'
8+
import { findLibrary } from '~/libraries'
49
import { seo } from '~/utils/seo'
510
import type { ConfigSchema } from '~/utils/config'
611

712
export const Route = createFileRoute('/$libraryId/$version/docs')({
813
head: (ctx) => {
914
const { libraryId } = ctx.params
10-
const library = getLibrary(libraryId)
15+
const library = findLibrary(libraryId)
16+
17+
if (!library) {
18+
throw notFound()
19+
}
1120

1221
return {
1322
meta: seo({
@@ -27,7 +36,11 @@ export const Route = createFileRoute('/$libraryId/$version/docs')({
2736

2837
function DocsRoute() {
2938
const { libraryId, version } = Route.useParams()
30-
const library = getLibrary(libraryId)
39+
const library = findLibrary(libraryId)
40+
41+
if (!library) {
42+
throw notFound()
43+
}
3144
const versionMatch = useMatch({ from: '/$libraryId/$version' })
3245
const { config } = versionMatch.loaderData as { config: ConfigSchema }
3346

src/routes/$libraryId/$version.index.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import {
22
useMatch,
33
redirect,
44
Link,
5+
notFound,
56
createFileRoute,
67
} from '@tanstack/react-router'
78
import { DocsLayout } from '~/components/DocsLayout'
8-
import { getLibrary } from '~/libraries'
9+
import { findLibrary } from '~/libraries'
910
import type { LibraryId } from '~/libraries'
1011
import { seo } from '~/utils/seo'
1112

@@ -52,7 +53,11 @@ const landingComponents: Partial<Record<LibraryId, ComponentType>> = {
5253
export const Route = createFileRoute('/$libraryId/$version/')({
5354
head: (ctx) => {
5455
const { libraryId } = ctx.params
55-
const library = getLibrary(libraryId)
56+
const library = findLibrary(libraryId)
57+
58+
if (!library) {
59+
throw notFound()
60+
}
5661

5762
return {
5863
meta: seo({
@@ -79,7 +84,11 @@ export const Route = createFileRoute('/$libraryId/$version/')({
7984

8085
function LibraryVersionIndex() {
8186
const { libraryId, version } = Route.useParams()
82-
const library = getLibrary(libraryId)
87+
const library = findLibrary(libraryId)
88+
89+
if (!library) {
90+
throw notFound()
91+
}
8392
const versionMatch = useMatch({ from: '/$libraryId/$version' })
8493
const { config } = versionMatch.loaderData as { config: ConfigSchema }
8594

src/routes/$libraryId/$version.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import { Outlet, redirect, createFileRoute } from '@tanstack/react-router'
1+
import {
2+
Outlet,
3+
redirect,
4+
notFound,
5+
createFileRoute,
6+
} from '@tanstack/react-router'
27
import { RedirectVersionBanner } from '~/components/RedirectVersionBanner'
3-
import { getBranch, getLibrary } from '~/libraries'
8+
import { findLibrary, getBranch } from '~/libraries'
49
import { getTanstackDocsConfig } from '~/utils/config'
510

611
export const Route = createFileRoute('/$libraryId/$version')({
712
staleTime: 1000 * 60 * 5,
813
beforeLoad: (ctx) => {
914
const { libraryId, version } = ctx.params
10-
const library = getLibrary(libraryId)
15+
const library = findLibrary(libraryId)
16+
17+
if (!library) {
18+
throw notFound()
19+
}
1120

1221
library.handleRedirects?.(ctx.location.href)
1322

@@ -19,7 +28,12 @@ export const Route = createFileRoute('/$libraryId/$version')({
1928
},
2029
loader: async (ctx) => {
2130
const { libraryId, version } = ctx.params
22-
const library = getLibrary(libraryId)
31+
const library = findLibrary(libraryId)
32+
33+
if (!library) {
34+
throw notFound()
35+
}
36+
2337
const branch = getBranch(library, version)
2438
const config = await getTanstackDocsConfig({
2539
data: {
@@ -36,7 +50,11 @@ export const Route = createFileRoute('/$libraryId/$version')({
3650

3751
function RouteForm() {
3852
const { libraryId, version } = Route.useParams()
39-
const library = getLibrary(libraryId)
53+
const library = findLibrary(libraryId)
54+
55+
if (!library) {
56+
throw notFound()
57+
}
4058

4159
return (
4260
<>

src/routes/admin/audit.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ function AuditPage() {
354354
[],
355355
)
356356

357+
// eslint-disable-next-line react-hooks/incompatible-library
357358
const table = useReactTable({
358359
data: (auditQuery?.data?.page || []) as AuditLogEntry[],
359360
columns,

src/routes/admin/github-stats.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ function GitHubStatsAdmin() {
302302
[refreshingKey, refreshAllMutation, refreshMutation],
303303
)
304304

305+
// eslint-disable-next-line react-hooks/incompatible-library
305306
const table = useReactTable({
306307
data: cacheEntries ?? [],
307308
columns,

src/routes/admin/logins.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ function LoginsPage() {
250250
[],
251251
)
252252

253+
// eslint-disable-next-line react-hooks/incompatible-library
253254
const table = useReactTable({
254255
data: (loginsQuery?.data?.page || []) as LoginHistoryEntry[],
255256
columns,

src/routes/admin/npm-stats.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ function NpmStatsAdmin() {
268268
[refreshPackageMutation],
269269
)
270270

271+
// eslint-disable-next-line react-hooks/incompatible-library
271272
const libraryTable = useReactTable({
272273
data: libraryStats ?? [],
273274
columns: libraryColumns,

0 commit comments

Comments
 (0)