Skip to content

Commit 2978523

Browse files
benjaminclaussclaudedevin-ai-integration[bot]coltondotio
authored
Add Page Versions (#4833)
Co-authored-by: Claude <[email protected]> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Colton Berry <[email protected]>
1 parent c752194 commit 2978523

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

packages/fern-docs/bundle/src/mdx/components/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { Step, StepGroup } from "./steps";
5050
import { Tab, TabGroup } from "./tabs";
5151
import { Tooltip } from "./tooltip";
5252
import { TwoSlash } from "./twoslash/TwoSlash";
53+
import { Version, Versions } from "./versions";
5354

5455
const ElevenLabsWaveform = dynamic(
5556
() => import("./waveform/WaveformComplex").then((mod) => mod.default),
@@ -95,6 +96,8 @@ const FERN_COMPONENTS = {
9596
Template,
9697
Tooltip,
9798
TwoSlash,
99+
Version,
100+
Versions,
98101
// callout aliases
99102
Info: InfoCallout,
100103
Warning: WarningCallout,
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { cn } from "@fern-docs/components/cn";
2+
import { FernDropdown } from "@fern-docs/components/FernDropdown";
3+
import { ChevronDown } from "lucide-react";
4+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
5+
import React, { type ReactNode, useEffect, useState } from "react";
6+
7+
import { unwrapChildren } from "../../common/unwrap-children";
8+
9+
export interface VersionsProps {
10+
children?: ReactNode;
11+
className?: string;
12+
/**
13+
* the query parameter name to use for version selection
14+
* @default "v"
15+
*/
16+
paramName?: string;
17+
}
18+
19+
export function Versions({ children, className, paramName = "v" }: VersionsProps) {
20+
const items = unwrapChildren(children, Version);
21+
const router = useRouter();
22+
const pathname = usePathname();
23+
const searchParams = useSearchParams();
24+
const versionParam = searchParams.get(paramName);
25+
26+
const defaultVersion = items.find((item) => item.props.default) || items[0];
27+
const [activeVersion, setActiveVersion] = useState(() => {
28+
if (versionParam != null) {
29+
const matchingVersion = items.find((item) => item.props.version === versionParam);
30+
if (matchingVersion) {
31+
return versionParam;
32+
}
33+
}
34+
return defaultVersion?.props.version;
35+
});
36+
37+
useEffect(() => {
38+
if (versionParam != null) {
39+
const matchingVersion = items.find((item) => item.props.version === versionParam);
40+
if (matchingVersion) {
41+
setActiveVersion(versionParam);
42+
}
43+
} else {
44+
setActiveVersion(defaultVersion?.props.version);
45+
}
46+
}, [versionParam, items, defaultVersion]);
47+
48+
const handleVersionChange = (version: string) => {
49+
setActiveVersion(version);
50+
51+
// Update URL with query parameter when a version is selected.
52+
const params = new URLSearchParams(searchParams.toString());
53+
params.set(paramName, version);
54+
const newURL = `${pathname}?${params.toString()}`;
55+
router.replace(newURL, { scroll: false });
56+
};
57+
58+
const currentVersion = items.find((item) => item.props.version === activeVersion);
59+
60+
return (
61+
<div className={cn("mb-6 mt-4 first:-mt-3", className)}>
62+
<div className="mb-6">
63+
<FernDropdown
64+
value={activeVersion}
65+
onValueChange={handleVersionChange}
66+
options={items.map(({ props: { version, title } }) => ({
67+
type: "value" as const,
68+
label: title || version,
69+
value: version
70+
}))}
71+
side="bottom"
72+
align="start"
73+
triggerAsChild={false}
74+
lang="en"
75+
>
76+
<div className="bg-tag-default hover:bg-tag-default/80 inline-flex h-9 items-center gap-2 rounded px-3 text-sm">
77+
{currentVersion?.props.title || currentVersion?.props.version}
78+
<ChevronDown className="size-icon transition-transform data-[state=open]:rotate-180" />
79+
</div>
80+
</FernDropdown>
81+
</div>
82+
{items.map((item) => (
83+
<div
84+
key={item.props.version}
85+
style={{ display: item.props.version === activeVersion ? "block" : "none" }}
86+
>
87+
{item}
88+
</div>
89+
))}
90+
</div>
91+
);
92+
}
93+
94+
export interface VersionProps {
95+
/**
96+
* the version identifier (e.g., "v1.0.0", "v2.0.0") - must be unique
97+
*/
98+
version: string;
99+
/**
100+
* the display title for this version
101+
* @default version
102+
*/
103+
title?: string;
104+
/**
105+
* whether this is the default version to display
106+
* @default false
107+
*/
108+
default?: boolean;
109+
/**
110+
* the children of the version
111+
*/
112+
children?: ReactNode;
113+
className?: string;
114+
}
115+
116+
export function Version({ children, className }: VersionProps) {
117+
return <div className={className}>{children}</div>;
118+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Version, Versions } from "./Versions";

0 commit comments

Comments
 (0)