Skip to content

Commit e707a17

Browse files
committed
fix(docs): sidebar sections have same behavior between varianted and nonvarianted
1 parent 3ce48a3 commit e707a17

File tree

4 files changed

+76
-33
lines changed

4 files changed

+76
-33
lines changed

packages/fern-docs/bundle/src/app/[host]/[domain]/layout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { LaunchDarklyInfo } from "@fern-docs/components/state/feature-flags
1212
import { RootNodeProvider, SetBasePath } from "@fern-docs/components/state/navigation";
1313
import {
1414
getAllSidebarRootNodes,
15+
getInitiallyCollapsedNodes,
1516
getSidebarRootNodeIdToChildToParentsMap
1617
} from "@fern-docs/components/state/navigation-server";
1718
import { FernThemeProvider } from "@fern-docs/components/theme";
@@ -89,14 +90,25 @@ export default async function Layout({
8990
const sidebarRootNodes = getAllSidebarRootNodes(unsafe_fullRoot);
9091
const sidebarRootNodesToChildToParentsMap = getSidebarRootNodeIdToChildToParentsMap(sidebarRootNodes);
9192

93+
// Get initially collapsed nodes for each sidebar root
94+
const sidebarRootNodesToInitiallyCollapsedNodes = new Map(
95+
Array.from(sidebarRootNodes.entries()).map(([nodeId, sidebarRootNode]) => [
96+
nodeId,
97+
getInitiallyCollapsedNodes(sidebarRootNode)
98+
])
99+
);
100+
92101
return (
93102
<FernThemeProvider
94103
hasLight={Boolean(colors.light)}
95104
hasDark={Boolean(colors.dark)}
96105
lightThemeColor={colors.light?.themeColor}
97106
darkThemeColor={colors.dark?.themeColor}
98107
>
99-
<RootNodeProvider sidebarRootNodesToChildToParentsMap={sidebarRootNodesToChildToParentsMap}>
108+
<RootNodeProvider
109+
sidebarRootNodesToChildToParentsMap={sidebarRootNodesToChildToParentsMap}
110+
sidebarRootNodesToInitiallyCollapsedNodes={sidebarRootNodesToInitiallyCollapsedNodes}
111+
>
100112
<Domain value={domain} />
101113
<SetBasePath value={basePath || "/"} />
102114
{!isSelfHosted() && !settings.disableAnalytics && (

packages/fern-docs/components/src/sidebar/nodes/SidebarVariantedNode.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { useCurrentVariantId } from "../../state/navigation";
1616
import type { SidebarRenderOptions } from "../SidebarRenderOptions";
1717
import { SidebarNavigationChild } from "./SidebarNavigationChild";
1818
import { SidebarRootChild } from "./SidebarRootChild";
19-
import { SidebarRootSectionNode } from "./SidebarRootSectionNode";
2019

2120
interface SidebarVariantedNodeProps {
2221
node: FernNavigation.VariantedNode;
@@ -148,42 +147,37 @@ export function SidebarVariantedNode({ node, depth, renderOptions, lang }: Sideb
148147
</DropdownMenu.Root>
149148

150149
{/* Render the selected variant's children */}
151-
<div className={cn("mt-2")}>
152-
{currentVariant.children.map((child) => {
150+
<ul className="fern-sidebar-group mt-2">
151+
{currentVariant.children.map((child, index) => {
153152
// VariantChild can be sidebar-level or section-level items
154153
// We need to determine if this should be rendered as a root child or navigation child
155-
// Based on the discriminated union, VariantChild includes SidebarGroup which is a root-level item
154+
// Group-level items (sections, sidebarGroups, apiReferences) get spacing; individual items don't
155+
const isGroupLevel =
156+
child.type === "sidebarGroup" || child.type === "section" || child.type === "apiReference";
157+
const groupSpacing = isGroupLevel && index > 0 ? "mt-6" : "";
158+
156159
if (child.type === "sidebarGroup") {
157160
return (
158-
<SidebarRootChild key={child.id} node={child} renderOptions={renderOptions} lang={lang} />
161+
<li key={child.id} className={groupSpacing}>
162+
<SidebarRootChild node={child} renderOptions={renderOptions} lang={lang} />
163+
</li>
159164
);
160165
}
161166

162-
if (depth === 0 && child.type === "section") {
163-
return (
164-
<SidebarRootSectionNode
165-
key={child.id}
167+
// All other types (sections, pages, links, etc.) go through SidebarNavigationChild
168+
// This ensures sections get collapse functionality via SidebarSectionNode
169+
return (
170+
<li key={child.id} className={groupSpacing}>
171+
<SidebarNavigationChild
166172
node={child}
167-
icon={processIcon({ node: child, forceClientRender, files: renderOptions.files })}
173+
depth={depth + 1}
168174
renderOptions={renderOptions}
169-
files={renderOptions.files}
170175
lang={lang}
171176
/>
172-
);
173-
}
174-
175-
// All other types are NavigationChild types (or nested sections)
176-
return (
177-
<SidebarNavigationChild
178-
key={child.id}
179-
node={child}
180-
depth={depth + 1}
181-
renderOptions={renderOptions}
182-
lang={lang}
183-
/>
177+
</li>
184178
);
185179
})}
186-
</div>
180+
</ul>
187181
</div>
188182
);
189183
}

packages/fern-docs/components/src/state/navigation-server.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,24 @@ export interface ExpandedNodesState {
1515
* Create the initial expanded nodes for a sidebar root node
1616
* @param currentNodeId - The current node id
1717
* @param childToParentsMap - The child to parents map
18+
* @param initiallyCollapsedNodes - Nodes that should start collapsed (from collapsed: true in config)
1819
* @returns The initial expanded nodes state
1920
*/
2021
export function createInitialExpandedNodes(
2122
currentNodeId: FernNavigation.NodeId | undefined,
22-
childToParentsMap: ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
23+
childToParentsMap: ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>,
24+
initiallyCollapsedNodes?: ReadonlySet<FernNavigation.NodeId>
2325
): ExpandedNodesState {
2426
const expandedNodes = new Set<FernNavigation.NodeId>();
2527
const implicitExpandedNodes = new Set<FernNavigation.NodeId>();
26-
const collapsedNodes = new Set<FernNavigation.NodeId>();
28+
const collapsedNodes = new Set<FernNavigation.NodeId>(initiallyCollapsedNodes);
2729

2830
if (currentNodeId != null) {
2931
expandedNodes.add(currentNodeId);
3032
childToParentsMap.get(currentNodeId)?.forEach((parent) => {
3133
implicitExpandedNodes.add(parent);
34+
// If a parent is in the path to the current node, remove it from collapsed
35+
collapsedNodes.delete(parent);
3236
});
3337
}
3438

@@ -113,6 +117,28 @@ export function invertParentChildMap(
113117
return invertedParentChildMap;
114118
}
115119

120+
/**
121+
* Get all nodes that should be initially collapsed based on their `collapsed: true` property
122+
* @param sidebar - The sidebar root node
123+
* @returns Set of node IDs that should start collapsed
124+
*/
125+
export function getInitiallyCollapsedNodes(
126+
sidebar: FernNavigation.SidebarRootNode | undefined
127+
): ReadonlySet<FernNavigation.NodeId> {
128+
const collapsedNodes = new Set<FernNavigation.NodeId>();
129+
if (sidebar == null) {
130+
return collapsedNodes;
131+
}
132+
133+
FernNavigation.traverseDF(sidebar, (node) => {
134+
if (node.type === "section" && node.collapsed === true) {
135+
collapsedNodes.add(node.id);
136+
}
137+
});
138+
139+
return collapsedNodes;
140+
}
141+
116142
// /**
117143
// * Create the initial expanded nodes for a sidebar root node
118144
// * @param currentNodeId - The current node id

packages/fern-docs/components/src/state/navigation.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export function createRootNodeStore(
2828
sidebarRootNodeToChildToParentsMap: ReadonlyMap<
2929
FernNavigation.NodeId,
3030
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
31-
>
31+
>,
32+
sidebarRootNodeToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>
3233
) {
3334
return create<RootNodeState>((set) => ({
3435
state: new Map(),
@@ -38,7 +39,8 @@ export function createRootNodeStore(
3839
state,
3940
action,
4041
sidebarRootNodeToChildToParentsMap,
41-
currentSidebarRootNodeId
42+
currentSidebarRootNodeId,
43+
sidebarRootNodeToInitiallyCollapsedNodes
4244
)
4345
}))
4446
}));
@@ -242,15 +244,19 @@ export function useToggleSidebarNode(nodeId: FernNavigation.NodeId) {
242244

243245
export function RootNodeProvider({
244246
children,
245-
sidebarRootNodesToChildToParentsMap
247+
sidebarRootNodesToChildToParentsMap,
248+
sidebarRootNodesToInitiallyCollapsedNodes
246249
}: {
247250
children: React.ReactNode;
248251
sidebarRootNodesToChildToParentsMap: ReadonlyMap<
249252
FernNavigation.NodeId,
250253
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
251254
>;
255+
sidebarRootNodesToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>;
252256
}) {
253-
const store = useLazyRef(() => createRootNodeStore(sidebarRootNodesToChildToParentsMap));
257+
const store = useLazyRef(() =>
258+
createRootNodeStore(sidebarRootNodesToChildToParentsMap, sidebarRootNodesToInitiallyCollapsedNodes)
259+
);
254260
return <RootNodeStoreContext.Provider value={store.current}>{children}</RootNodeStoreContext.Provider>;
255261
}
256262

@@ -369,6 +375,7 @@ function reduceExpandedNodes(prev: ExpandedNodesState, action: SidebarAction): E
369375
* @param action - The action to perform
370376
* @param sidebarRootNodeIdToChildToParentsMap - The sidebar root node id to child to parents map
371377
* @param currentSidebarRootNodeId - The current sidebar root node id
378+
* @param sidebarRootNodeIdToInitiallyCollapsedNodes - Map of sidebar root node id to initially collapsed nodes
372379
* @returns The new expanded nodes state
373380
*/
374381
function reduceExpandedNodesBySidebarRootId(
@@ -378,19 +385,23 @@ function reduceExpandedNodesBySidebarRootId(
378385
FernNavigation.NodeId,
379386
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
380387
>,
381-
currentSidebarRootNodeId: FernNavigation.NodeId
388+
currentSidebarRootNodeId: FernNavigation.NodeId,
389+
sidebarRootNodeIdToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>
382390
): ReadonlyMap<FernNavigation.NodeId, ExpandedNodesState> {
383391
const childToParentsMap = sidebarRootNodeIdToChildToParentsMap.get(currentSidebarRootNodeId);
384392

385393
if (childToParentsMap == null) {
386394
return prev;
387395
}
388396

397+
const initiallyCollapsedNodes = sidebarRootNodeIdToInitiallyCollapsedNodes?.get(currentSidebarRootNodeId);
398+
389399
const next = new Map(prev);
390400
next.set(
391401
currentSidebarRootNodeId,
392402
reduceExpandedNodes(
393-
prev.get(currentSidebarRootNodeId) ?? createInitialExpandedNodes(undefined, childToParentsMap),
403+
prev.get(currentSidebarRootNodeId) ??
404+
createInitialExpandedNodes(undefined, childToParentsMap, initiallyCollapsedNodes),
394405
action
395406
)
396407
);

0 commit comments

Comments
 (0)