Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/fern-docs/bundle/src/app/[host]/[domain]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { LaunchDarklyInfo } from "@fern-docs/components/state/feature-flags
import { RootNodeProvider, SetBasePath } from "@fern-docs/components/state/navigation";
import {
getAllSidebarRootNodes,
getInitiallyCollapsedNodes,
getSidebarRootNodeIdToChildToParentsMap
} from "@fern-docs/components/state/navigation-server";
import { FernThemeProvider } from "@fern-docs/components/theme";
Expand Down Expand Up @@ -89,14 +90,25 @@ export default async function Layout({
const sidebarRootNodes = getAllSidebarRootNodes(unsafe_fullRoot);
const sidebarRootNodesToChildToParentsMap = getSidebarRootNodeIdToChildToParentsMap(sidebarRootNodes);

// Get initially collapsed nodes for each sidebar root
const sidebarRootNodesToInitiallyCollapsedNodes = new Map(
Array.from(sidebarRootNodes.entries()).map(([nodeId, sidebarRootNode]) => [
nodeId,
getInitiallyCollapsedNodes(sidebarRootNode)
])
);

return (
<FernThemeProvider
hasLight={Boolean(colors.light)}
hasDark={Boolean(colors.dark)}
lightThemeColor={colors.light?.themeColor}
darkThemeColor={colors.dark?.themeColor}
>
<RootNodeProvider sidebarRootNodesToChildToParentsMap={sidebarRootNodesToChildToParentsMap}>
<RootNodeProvider
sidebarRootNodesToChildToParentsMap={sidebarRootNodesToChildToParentsMap}
sidebarRootNodesToInitiallyCollapsedNodes={sidebarRootNodesToInitiallyCollapsedNodes}
>
<Domain value={domain} />
<SetBasePath value={basePath || "/"} />
{!isSelfHosted() && !settings.disableAnalytics && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { useCurrentVariantId } from "../../state/navigation";
import type { SidebarRenderOptions } from "../SidebarRenderOptions";
import { SidebarNavigationChild } from "./SidebarNavigationChild";
import { SidebarRootChild } from "./SidebarRootChild";
import { SidebarRootSectionNode } from "./SidebarRootSectionNode";

interface SidebarVariantedNodeProps {
node: FernNavigation.VariantedNode;
Expand Down Expand Up @@ -148,42 +147,30 @@ export function SidebarVariantedNode({ node, depth, renderOptions, lang }: Sideb
</DropdownMenu.Root>

{/* Render the selected variant's children */}
<div className={cn("mt-2")}>
<ul className="fern-sidebar-group mt-2">
{currentVariant.children.map((child) => {
// VariantChild can be sidebar-level or section-level items
// We need to determine if this should be rendered as a root child or navigation child
// Based on the discriminated union, VariantChild includes SidebarGroup which is a root-level item
if (child.type === "sidebarGroup") {
return (
<SidebarRootChild key={child.id} node={child} renderOptions={renderOptions} lang={lang} />
<li key={child.id}>
<SidebarRootChild node={child} renderOptions={renderOptions} lang={lang} />
</li>
);
}

if (depth === 0 && child.type === "section") {
return (
<SidebarRootSectionNode
key={child.id}
// All other types (sections, pages, links, etc.) go through SidebarNavigationChild
// This ensures sections get collapse functionality via SidebarSectionNode
return (
<li key={child.id}>
<SidebarNavigationChild
node={child}
icon={processIcon({ node: child, forceClientRender, files: renderOptions.files })}
depth={depth + 1}
renderOptions={renderOptions}
files={renderOptions.files}
lang={lang}
/>
);
}

// All other types are NavigationChild types (or nested sections)
return (
<SidebarNavigationChild
key={child.id}
node={child}
depth={depth + 1}
renderOptions={renderOptions}
lang={lang}
/>
</li>
);
})}
</div>
</ul>
</div>
);
}
30 changes: 28 additions & 2 deletions packages/fern-docs/components/src/state/navigation-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,24 @@ export interface ExpandedNodesState {
* Create the initial expanded nodes for a sidebar root node
* @param currentNodeId - The current node id
* @param childToParentsMap - The child to parents map
* @param initiallyCollapsedNodes - Nodes that should start collapsed (from collapsed: true in config)
* @returns The initial expanded nodes state
*/
export function createInitialExpandedNodes(
currentNodeId: FernNavigation.NodeId | undefined,
childToParentsMap: ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
childToParentsMap: ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>,
initiallyCollapsedNodes?: ReadonlySet<FernNavigation.NodeId>
): ExpandedNodesState {
const expandedNodes = new Set<FernNavigation.NodeId>();
const implicitExpandedNodes = new Set<FernNavigation.NodeId>();
const collapsedNodes = new Set<FernNavigation.NodeId>();
const collapsedNodes = new Set<FernNavigation.NodeId>(initiallyCollapsedNodes);

if (currentNodeId != null) {
expandedNodes.add(currentNodeId);
childToParentsMap.get(currentNodeId)?.forEach((parent) => {
implicitExpandedNodes.add(parent);
// If a parent is in the path to the current node, remove it from collapsed
collapsedNodes.delete(parent);
});
}

Expand Down Expand Up @@ -113,6 +117,28 @@ export function invertParentChildMap(
return invertedParentChildMap;
}

/**
* Get all nodes that should be initially collapsed based on their `collapsed: true` property
* @param sidebar - The sidebar root node
* @returns Set of node IDs that should start collapsed
*/
export function getInitiallyCollapsedNodes(
sidebar: FernNavigation.SidebarRootNode | undefined
): ReadonlySet<FernNavigation.NodeId> {
const collapsedNodes = new Set<FernNavigation.NodeId>();
if (sidebar == null) {
return collapsedNodes;
}

FernNavigation.traverseDF(sidebar, (node) => {
if (node.type === "section" && node.collapsed === true) {
collapsedNodes.add(node.id);
}
});

return collapsedNodes;
}

// /**
// * Create the initial expanded nodes for a sidebar root node
// * @param currentNodeId - The current node id
Expand Down
23 changes: 17 additions & 6 deletions packages/fern-docs/components/src/state/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export function createRootNodeStore(
sidebarRootNodeToChildToParentsMap: ReadonlyMap<
FernNavigation.NodeId,
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
>
>,
sidebarRootNodeToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>
) {
return create<RootNodeState>((set) => ({
state: new Map(),
Expand All @@ -38,7 +39,8 @@ export function createRootNodeStore(
state,
action,
sidebarRootNodeToChildToParentsMap,
currentSidebarRootNodeId
currentSidebarRootNodeId,
sidebarRootNodeToInitiallyCollapsedNodes
)
}))
}));
Expand Down Expand Up @@ -242,15 +244,19 @@ export function useToggleSidebarNode(nodeId: FernNavigation.NodeId) {

export function RootNodeProvider({
children,
sidebarRootNodesToChildToParentsMap
sidebarRootNodesToChildToParentsMap,
sidebarRootNodesToInitiallyCollapsedNodes
}: {
children: React.ReactNode;
sidebarRootNodesToChildToParentsMap: ReadonlyMap<
FernNavigation.NodeId,
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
>;
sidebarRootNodesToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>;
}) {
const store = useLazyRef(() => createRootNodeStore(sidebarRootNodesToChildToParentsMap));
const store = useLazyRef(() =>
createRootNodeStore(sidebarRootNodesToChildToParentsMap, sidebarRootNodesToInitiallyCollapsedNodes)
);
return <RootNodeStoreContext.Provider value={store.current}>{children}</RootNodeStoreContext.Provider>;
}

Expand Down Expand Up @@ -369,6 +375,7 @@ function reduceExpandedNodes(prev: ExpandedNodesState, action: SidebarAction): E
* @param action - The action to perform
* @param sidebarRootNodeIdToChildToParentsMap - The sidebar root node id to child to parents map
* @param currentSidebarRootNodeId - The current sidebar root node id
* @param sidebarRootNodeIdToInitiallyCollapsedNodes - Map of sidebar root node id to initially collapsed nodes
* @returns The new expanded nodes state
*/
function reduceExpandedNodesBySidebarRootId(
Expand All @@ -378,19 +385,23 @@ function reduceExpandedNodesBySidebarRootId(
FernNavigation.NodeId,
ReadonlyMap<FernNavigation.NodeId, FernNavigation.NodeId[]>
>,
currentSidebarRootNodeId: FernNavigation.NodeId
currentSidebarRootNodeId: FernNavigation.NodeId,
sidebarRootNodeIdToInitiallyCollapsedNodes?: ReadonlyMap<FernNavigation.NodeId, ReadonlySet<FernNavigation.NodeId>>
): ReadonlyMap<FernNavigation.NodeId, ExpandedNodesState> {
const childToParentsMap = sidebarRootNodeIdToChildToParentsMap.get(currentSidebarRootNodeId);

if (childToParentsMap == null) {
return prev;
}

const initiallyCollapsedNodes = sidebarRootNodeIdToInitiallyCollapsedNodes?.get(currentSidebarRootNodeId);

const next = new Map(prev);
next.set(
currentSidebarRootNodeId,
reduceExpandedNodes(
prev.get(currentSidebarRootNodeId) ?? createInitialExpandedNodes(undefined, childToParentsMap),
prev.get(currentSidebarRootNodeId) ??
createInitialExpandedNodes(undefined, childToParentsMap, initiallyCollapsedNodes),
action
)
);
Expand Down
Loading