Skip to content

Commit 4947182

Browse files
author
Sarah Bawabe
committed
get navigation working on product click, lint fixes
1 parent 522e55f commit 4947182

File tree

11 files changed

+93
-225
lines changed

11 files changed

+93
-225
lines changed

packages/fdr-sdk/src/navigation/NodeCollector.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { once } from "es-toolkit/function";
33
import { EMPTY_ARRAY } from "@fern-api/ui-core-utils";
44

55
import { FernNavigation } from "./..";
6+
import { pruneProductNode } from "./utils/pruneProductNode";
67
import { pruneVersionNode } from "./utils/pruneVersionNode";
78
import { NavigationNodeWithMetadata } from "./versions";
8-
import { pruneProductNode } from "./utils/pruneProductNode";
99

1010
interface NavigationNodeWithMetadataAndParents {
1111
node: FernNavigation.NavigationNodeWithMetadata;
@@ -75,10 +75,12 @@ export class NodeCollector {
7575
}
7676
}
7777

78-
private defaultVersion: FernNavigation.VersionNode | undefined;
7978
private versionNodes: FernNavigation.VersionNode[] = [];
79+
private defaultVersion: FernNavigation.VersionNode | undefined;
80+
8081
private productNodes: FernNavigation.ProductNode[] = [];
8182
private defaultProduct: FernNavigation.ProductNode | undefined;
83+
8284
constructor(rootNode: FernNavigation.NavigationNode | undefined) {
8385
if (rootNode == null) {
8486
return;
@@ -87,14 +89,21 @@ export class NodeCollector {
8789
if (node.type === "product") {
8890
this.productNodes.push(node);
8991
// Handle the default product
90-
if (node.default && rootNode.type === "root") {
92+
if (node.default && rootNode.type === "root") {
9193
const copy = JSON.parse(
9294
JSON.stringify(node)
9395
) as FernNavigation.ProductNode;
94-
this.defaultProduct = pruneProductNode(copy, rootNode.slug, node.slug);
95-
FernNavigation.traverseDF(this.defaultProduct, (node, innerParents) => {
96-
this.visitNode(node, [...parents, ...innerParents], true);
97-
});
96+
this.defaultProduct = pruneProductNode(
97+
copy,
98+
rootNode.slug,
99+
node.slug
100+
);
101+
FernNavigation.traverseDF(
102+
this.defaultProduct,
103+
(node, innerParents) => {
104+
this.visitNode(node, [...parents, ...innerParents], true);
105+
}
106+
);
98107
}
99108
}
100109

packages/fdr-sdk/src/navigation/utils/findNode.ts

+36-18
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export declare namespace Node {
2222
root: FernNavigation.RootNode;
2323
products: readonly FernNavigation.ProductNode[];
2424
currentProduct: FernNavigation.ProductNode | undefined;
25+
/**
26+
* This is true if the current product is the default product node (without the product slug prefix)
27+
*/
28+
isCurrentProductDefault: boolean;
2529
versions: readonly FernNavigation.VersionNode[];
2630
currentVersion: FernNavigation.VersionNode | undefined;
2731
/**
@@ -69,39 +73,49 @@ export function findNode(
6973

7074
// if the slug points to a node that doesn't exist, we should redirect to the first likely node
7175
if (found == null) {
72-
let maybeVersionNode: FernNavigation.RootNode | FernNavigation.VersionNode =
73-
root;
74-
75-
// the 404 behavior should be version-aware
76-
for (const versionNode of collector.getVersionNodes()) {
77-
if (slug.startsWith(versionNode.slug)) {
78-
maybeVersionNode = versionNode;
76+
let maybeProductOrVersionNode:
77+
| FernNavigation.RootNode
78+
| FernNavigation.ProductNode
79+
| FernNavigation.VersionNode = root;
80+
let foundProductNode = false;
81+
82+
// the 404 behavior should be product-aware
83+
for (const productNode of collector.getProductNodes()) {
84+
if (slug.startsWith(productNode.slug)) {
85+
maybeProductOrVersionNode = productNode;
86+
foundProductNode = true;
7987
break;
8088
}
8189
}
8290

91+
// if we didn't find a product node, try to find a version node
92+
if (!foundProductNode) {
93+
// the 404 behavior should be version-aware
94+
for (const versionNode of collector.getVersionNodes()) {
95+
if (slug.startsWith(versionNode.slug)) {
96+
maybeProductOrVersionNode = versionNode;
97+
break;
98+
}
99+
}
100+
}
101+
83102
return {
84103
type: "notFound",
85-
redirect: maybeVersionNode.pointsTo,
86-
authed: maybeVersionNode.authed,
104+
redirect: maybeProductOrVersionNode.pointsTo,
105+
authed: maybeProductOrVersionNode.authed,
87106
};
88107
}
89108

90109
const sidebar = found.parents.find(isSidebarRootNode);
91110
const currentProductGroup = found.parents.find(isProductGroupNode);
92-
// TODO: remove console.log
93-
console.log("currentProduct", currentProductGroup);
94-
const productChildren = currentProductGroup?.children;
111+
const currentProduct = found.parents.find(isProductNode);
112+
95113
const currentVersion = found.parents.find(isVersionNode);
96114
const unversionedNode = found.parents.find(isUnversionedNode);
97115
const versionChild = (currentVersion ?? unversionedNode)?.child;
98116
const landingPage = (currentProductGroup ?? currentVersion ?? unversionedNode)
99117
?.landingPage;
100118

101-
const currentProduct = productChildren?.find(isProductNode);
102-
// TODO: remove console.log
103-
console.log("currentProduct", currentProduct);
104-
105119
const tabbedNode =
106120
found.parents.find(isTabbedNode) ??
107121
// fallback to the version child because the current node may be a landing page
@@ -121,13 +135,14 @@ export function findNode(
121135
);
122136
const currentTabNode =
123137
tabbedNodeIndex !== -1 ? parentsAndNode[tabbedNodeIndex + 1] : undefined;
138+
124139
const products = collector.getProductNodes().map((node) => {
125140
if (node.default) {
126-
// if we're currently viewing the default version, we may be viewing the non-pruned version
141+
// if we're currently viewing the default product, we may be viewing the non-pruned product node
127142
if (node.id === currentProduct?.id) {
128143
return currentProduct;
129144
}
130-
// otherwise, we should always use the pruned version node
145+
// otherwise, we should always use the pruned product node
131146
return collector.defaultProductNode ?? node;
132147
}
133148
return node;
@@ -164,6 +179,9 @@ export function findNode(
164179
currentProduct,
165180
versions, // this is used to render the version switcher
166181
currentVersion,
182+
isCurrentProductDefault: currentProduct?.default
183+
? currentProduct === collector.defaultProductNode
184+
: false,
167185
isCurrentVersionDefault: currentVersion?.default
168186
? currentVersion === collector.defaultVersionNode
169187
: false,
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { NavigationNode, ProductGroupNode } from ".";
22

3-
export function isProductGroupNode(node: NavigationNode): node is ProductGroupNode {
3+
export function isProductGroupNode(
4+
node: NavigationNode
5+
): node is ProductGroupNode {
46
return node.type === "productgroup";
57
}

packages/fern-docs/bundle/src/components/FernProductDropdown.tsx

-16
This file was deleted.

packages/fern-docs/bundle/src/components/header/ProductDropdown.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ export async function ProductDropdown({ loader }: { loader: DocsLoader }) {
2929
? FernNavigation.toDefaultSlug(slug, root.slug, product.slug)
3030
: undefined,
3131
icon: product.icon ? <FaIconServer icon={product.icon} /> : undefined,
32-
authed: product.authed,
33-
default: product.default,
34-
hidden: product.hidden,
3532
subtitle: product.subtitle,
33+
default: product.default,
3634
};
3735
});
3836
return <ProductDropdownClient products={productOptions} />;

packages/fern-docs/bundle/src/components/header/ProductDropdownClient.tsx

+6-37
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
"use client";
22

3-
import { ChevronDown, Lock } from "lucide-react";
3+
import { ChevronDown } from "lucide-react";
44

5-
import {
6-
Availability,
7-
AvailabilityBadge,
8-
FernProductDropdown,
9-
} from "@fern-docs/components";
5+
import { FernDropdown } from "@fern-docs/components";
106
import { slugToHref } from "@fern-docs/utils";
117

128
import { useCurrentProductId, useCurrentProductSlug } from "@/state/navigation";
@@ -18,10 +14,7 @@ export interface ProductDropdownItem {
1814
slug: string;
1915
defaultSlug?: string;
2016
icon?: React.ReactNode;
21-
authed?: boolean;
2217
default: boolean;
23-
availability?: Availability;
24-
hidden?: boolean;
2518
}
2619

2720
export function ProductDropdownClient({
@@ -36,47 +29,23 @@ export function ProductDropdownClient({
3629
products.find((product) => product.default);
3730

3831
return (
39-
<FernProductDropdown
32+
<FernDropdown
4033
value={currentProductId}
4134
options={products.map(
42-
({
43-
icon,
44-
productId,
45-
title,
46-
availability,
47-
slug,
48-
defaultSlug,
49-
authed,
50-
hidden,
51-
subtitle,
52-
}) => ({
35+
({ icon, productId, title, slug, subtitle, defaultSlug }) => ({
5336
type: "product",
5437
id: productId,
5538
title,
5639
subtitle,
57-
label: (
58-
<div className="flex items-center gap-2">
59-
{title}
60-
{availability != null ? (
61-
<AvailabilityBadge availability={availability} size="sm" />
62-
) : null}
63-
</div>
64-
),
6540
value: productId,
66-
disabled: availability == null,
6741
href: slugToHref(
6842
pickProductSlug({
6943
currentProductSlug,
7044
defaultSlug,
7145
slug,
7246
})
7347
),
74-
icon: authed ? (
75-
<Lock className="text-(color:--grayscale-a9) size-4 self-center" />
76-
) : (
77-
icon
78-
),
79-
className: hidden ? "opacity-50" : undefined,
48+
icon,
8049
})
8150
)}
8251
contentProps={{
@@ -89,7 +58,7 @@ export function ProductDropdownClient({
8958
<p className="product-item-title">{currentProduct?.title}</p>
9059
<ChevronDown className="size-icon transition-transform data-[state=open]:rotate-180" />
9160
</div>
92-
</FernProductDropdown>
61+
</FernDropdown>
9362
);
9463

9564
function pickProductSlug({

packages/fern-docs/bundle/src/components/shared-page.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ export default async function SharedPage({
228228
versionId={found.currentVersion?.versionId}
229229
versionSlug={found.currentVersion?.slug}
230230
versionIsDefault={found.isCurrentVersionDefault}
231+
productIsDefault={found.isCurrentProductDefault}
231232
/>
232233
<SetIsLandingPage value={found.node.type === "landingPage"} />
233234
<DocsMainContent

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

+8
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ const currentVersionSlugAtom = atom<FernNavigation.Slug | undefined>(undefined);
9090
// if true, the current version is the default version
9191
const currentVersionIsDefaultAtom = atom<boolean>(false);
9292

93+
// if true, the current product is the default product
94+
const currentProductIsDefaultAtom = atom<boolean>(false);
95+
9396
export function useCurrentSidebarRootNodeId() {
9497
return useAtomValue(currentSidebarRootNodeIdAtom);
9598
}
@@ -131,6 +134,7 @@ export function SetCurrentNavigationNode({
131134
versionId,
132135
versionSlug,
133136
versionIsDefault,
137+
productIsDefault,
134138
}: {
135139
sidebarRootNodeId?: FernNavigation.NodeId;
136140
nodeId?: FernNavigation.NodeId;
@@ -140,6 +144,7 @@ export function SetCurrentNavigationNode({
140144
versionId?: FernNavigation.VersionId;
141145
versionSlug?: FernNavigation.Slug;
142146
versionIsDefault?: boolean;
147+
productIsDefault?: boolean;
143148
}) {
144149
const useStore = React.useContext(RootNodeStoreContext);
145150
const dispatch = useStore((s) => s.dispatch);
@@ -151,6 +156,7 @@ export function SetCurrentNavigationNode({
151156
const setCurrentVersionId = useSetAtom(currentVersionIdAtom);
152157
const setCurrentVersionSlug = useSetAtom(currentVersionSlugAtom);
153158
const setCurrentVersionIsDefault = useSetAtom(currentVersionIsDefaultAtom);
159+
const setCurrentProductIsDefault = useSetAtom(currentProductIsDefaultAtom);
154160
useIsomorphicLayoutEffect(() => {
155161
setCurrentSidebarRootNodeId(sidebarRootNodeId);
156162
setCurrentNodeId(nodeId);
@@ -160,6 +166,7 @@ export function SetCurrentNavigationNode({
160166
setCurrentVersionId(versionId);
161167
setCurrentVersionSlug(versionSlug);
162168
setCurrentVersionIsDefault(versionIsDefault ?? false);
169+
setCurrentProductIsDefault(productIsDefault ?? false);
163170
}, [
164171
nodeId,
165172
tabId,
@@ -169,6 +176,7 @@ export function SetCurrentNavigationNode({
169176
versionId,
170177
versionSlug,
171178
versionIsDefault,
179+
productIsDefault,
172180
]);
173181

174182
useIsomorphicLayoutEffect(() => {

packages/fern-docs/components/src/FernDropdown.scss

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
@apply py-2 lg:py-0;
2626
@apply rounded-2 bg-(color:--grayscale-a2) lg:bg-transparent;
2727
}
28+
2829
.product-item-title {
2930
@apply hidden lg:block;
3031
}
@@ -39,24 +40,28 @@
3940
.fern-product-item-icon {
4041
@apply rounded-3/2 border-(color:--grayscale-a9) border;
4142
@apply m-1 flex h-[56px] w-[56px] shrink-0 items-center justify-center p-1;
43+
4244
svg {
4345
@apply block h-[60%] w-[60%];
4446
}
4547
}
4648

4749
/* Defines the styles for the product dropdown item when it is highlighted. */
4850
&[data-highlighted="true"] {
51+
@apply text-(color:--accent) bg-(color:--grayscale-a2);
52+
4953
.fern-product-item-icon {
5054
@apply border-(color:--accent) text-(color:--accent);
5155
}
52-
@apply text-(color:--accent) bg-(color:--grayscale-a2);
5356
}
5457
}
58+
5559
/* Defines the hover styles for the product dropdown item. */
5660
.fern-product-item:hover {
61+
@apply text-(color:--accent) bg-(color:--accent-2);
62+
5763
.fern-product-item-icon {
5864
@apply border-(color:--accent) text-(color:--accent);
5965
}
60-
@apply text-(color:--accent) bg-(color:--accent-2);
6166
}
6267
}

0 commit comments

Comments
 (0)