Skip to content

Commit d6975cc

Browse files
committed
feat: add version selector
1 parent 57068b8 commit d6975cc

File tree

7 files changed

+227
-3
lines changed

7 files changed

+227
-3
lines changed

app/[[...path]]/page.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function MDXLayoutRenderer({mdxSource, ...rest}) {
4747
export default async function Page({params}: {params: {path?: string[]}}) {
4848
// get frontmatter of all docs in tree
4949
const rootNode = await getDocsRootNode();
50+
5051
setServerContext({
5152
rootNode,
5253
path: params.path ?? [],
@@ -108,8 +109,19 @@ export default async function Page({params}: {params: {path?: string[]}}) {
108109
}
109110
const {mdxSource, frontMatter} = doc;
110111

112+
const versions = (await getDocsFrontMatter())
113+
.filter(docc => {
114+
return docc.slug.includes('__v') && docc.slug.includes(pageNode.path);
115+
})
116+
.map(({slug}) => {
117+
const segments = slug.split('__v');
118+
return segments[segments.length - 1];
119+
});
120+
111121
// pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc.
112-
return <MDXLayoutRenderer mdxSource={mdxSource} frontMatter={frontMatter} />;
122+
return (
123+
<MDXLayoutRenderer mdxSource={mdxSource} frontMatter={{...frontMatter, versions}} />
124+
);
113125
}
114126

115127
type MetadataProps = {

src/components/docPage/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function DocPage({
5050
<Header pathname={pathname} searchPlatforms={searchPlatforms} />
5151

5252
<section className="px-0 flex relative">
53-
{sidebar ?? <Sidebar path={path} />}
53+
{sidebar ?? <Sidebar path={path} versions={frontMatter.versions} />}
5454
<main className="main-content flex w-full mt-[var(--header-height)] flex-1 mx-auto">
5555
<div
5656
className={[

src/components/sidebar/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import styles from './style.module.scss';
1212

1313
import {ScrollActiveLink} from '../focus-active-link';
1414
import {PlatformSelector} from '../platformSelector';
15+
import {VersionSelector} from '../versionSelector';
1516

1617
import {DevelopDocsSidebar} from './developDocsSidebar';
1718
import {SidebarLinks} from './sidebarLinks';
@@ -22,7 +23,7 @@ const headerClassName = `${styles['sidebar-title']} flex items-center`;
2223

2324
export const sidebarToggleId = styles['navbar-menu-toggle'];
2425

25-
export async function Sidebar({path}: SidebarProps) {
26+
export async function Sidebar({path, versions}: SidebarProps) {
2627
const rootNode = await getDocsRootNode();
2728

2829
if (isDeveloperDocs) {
@@ -92,6 +93,11 @@ export async function Sidebar({path}: SidebarProps) {
9293
currentPlatform={currentGuide || currentPlatform}
9394
/>
9495
</div>
96+
{versions && versions.length >= 1 && (
97+
<div className="mb-3">
98+
<VersionSelector versions={versions} />
99+
</div>
100+
)}
95101
</div>
96102
<div className={styles.toc}>
97103
<ScrollActiveLink activeLinkSelector={activeLinkSelector} />

src/components/sidebar/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type SidebarNode = {
99

1010
export type SidebarProps = {
1111
path: string[];
12+
versions?: string[];
1213
};
1314

1415
export type DefaultSidebarProps = SidebarProps & {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use client';
2+
import {useState} from 'react';
3+
import {ChevronDownIcon} from '@radix-ui/react-icons';
4+
import * as RadixSelect from '@radix-ui/react-select';
5+
import {usePathname, useRouter} from 'next/navigation';
6+
7+
import styles from './style.module.scss';
8+
9+
const stripTrailingSlash = (url: string) => {
10+
return url.replace(/\/$/, '');
11+
};
12+
13+
const VERSION_INDICATOR = '__v';
14+
15+
export function VersionSelector({versions}: {versions: string[]}) {
16+
const availableVersions = ['latest', ...versions];
17+
const router = useRouter();
18+
const pathname = usePathname();
19+
20+
const getCurrentVersion = () => {
21+
if (pathname?.includes(VERSION_INDICATOR)) {
22+
const segments = pathname.split(VERSION_INDICATOR);
23+
return segments[segments.length - 1];
24+
}
25+
26+
return 'latest';
27+
};
28+
29+
const [selectedVersion, setSelectedVersion] = useState(getCurrentVersion());
30+
31+
const getVersionedPathname = (version: string) => {
32+
if (pathname) {
33+
if (version === 'latest') {
34+
return pathname?.split(VERSION_INDICATOR)[0];
35+
}
36+
37+
return `${stripTrailingSlash(pathname)}${VERSION_INDICATOR}${version}`;
38+
}
39+
40+
return '';
41+
};
42+
43+
const handleVersionChange = (newVersion: string) => {
44+
setSelectedVersion(newVersion);
45+
router.push(getVersionedPathname(newVersion));
46+
};
47+
48+
return (
49+
<div>
50+
<RadixSelect.Root value={selectedVersion} onValueChange={handleVersionChange}>
51+
<RadixSelect.Trigger aria-label="Version" className={styles.select}>
52+
<RadixSelect.Value placeholder="Version">
53+
Version: {selectedVersion}
54+
</RadixSelect.Value>
55+
<RadixSelect.Icon>
56+
<ChevronDownIcon />
57+
</RadixSelect.Icon>
58+
</RadixSelect.Trigger>
59+
<RadixSelect.Content
60+
role="dialog"
61+
aria-label="Versions"
62+
position="popper"
63+
className={styles.popover}
64+
>
65+
{availableVersions.map(version => (
66+
<RadixSelect.Item key={version} value={version} className={styles.item}>
67+
{version}
68+
</RadixSelect.Item>
69+
))}
70+
</RadixSelect.Content>
71+
</RadixSelect.Root>
72+
</div>
73+
);
74+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
.select {
2+
display: flex;
3+
padding: 0.5rem;
4+
overflow-wrap: break-word;
5+
align-items: center;
6+
justify-content: center;
7+
gap: 0.25rem;
8+
min-width: 15rem;
9+
width: 100%;
10+
min-height: 42px;
11+
border-radius: 0.25rem;
12+
background-color: var(--gray-a2);
13+
padding-left: 1rem;
14+
padding-right: 1rem;
15+
justify-content: space-between;
16+
border: 1.5px solid var(--gray-4);
17+
}
18+
19+
.select:focus {
20+
outline: 0;
21+
border-color: var(--accent-purple);
22+
box-shadow: 0 0 0 0.2rem var(--accent-purple-light);
23+
}
24+
25+
.select:hover {
26+
background-color: var(--gray-a4);
27+
}
28+
29+
@media (min-width: 640px) {
30+
.select {
31+
font-size: 15px;
32+
}
33+
}
34+
35+
.popover {
36+
z-index: 50;
37+
border-radius: 0.5rem;
38+
background-color: var(--gray-2);
39+
width: var(--radix-select-trigger-width);
40+
box-shadow: var(--shadow-6);
41+
display: flex;
42+
flex-direction: column;
43+
}
44+
45+
.item {
46+
display: flex;
47+
flex: 1;
48+
padding: 0.5rem;
49+
cursor: default;
50+
align-items: center;
51+
border-radius: 0.25rem;
52+
outline: 1.5px solid transparent;
53+
outline-offset: 2px;
54+
}
55+
56+
.item:hover {
57+
background-color: var(--accent-purple-light);
58+
cursor: pointer;
59+
}
60+
61+
.item[data-active-item] {
62+
background-color: var(--accent-purple-light);
63+
}
64+
65+
:global(.dark) {
66+
.item[data-active-item] {
67+
background-color: var(--gray-a4);
68+
}
69+
.item[data-active-item] + .expand-button {
70+
background-color: var(--gray-a4);
71+
}
72+
.item[data-state='checked']:hover + .expand-button {
73+
background-color: var(--gray-a4);
74+
}
75+
.select.select:focus {
76+
box-shadow: 0 0 0 0.2rem var(--gray-a4);
77+
}
78+
}
79+
80+
.item[data-state='checked'] {
81+
background-color: var(--accent-purple);
82+
color: white;
83+
}
84+
85+
.item[data-guide='true'] {
86+
margin-left: 1.5rem;
87+
}
88+
89+
.item[data-platform-with-guides] {
90+
border-radius: 0.25rem 0 0 0.25rem;
91+
}
92+
93+
.item-text {
94+
display: flex;
95+
align-items: start;
96+
gap: 0.25rem;
97+
text-align: left;
98+
99+
.platform-icon {
100+
translate: 0 4px;
101+
}
102+
}
103+
104+
@media (min-width: 1024px) {
105+
.item {
106+
padding-top: 0.315rem;
107+
padding-bottom: 0.315rem;
108+
font-size: 15px;
109+
}
110+
}
111+
112+
.item[data-guide='true']::before {
113+
content: '';
114+
display: inline-block;
115+
width: 1px;
116+
height: 100%;
117+
// background-color: hsl(204 4% 0% / 0.1);
118+
position: absolute;
119+
left: -0.5rem;
120+
top: 0;
121+
}
122+
123+
.item[data-last-guide='true']:before {
124+
height: 80%;
125+
}

src/types/frontmatter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@ export interface FrontMatter {
6060
* Specific guides that this page is relevant to.
6161
*/
6262
supported?: string[];
63+
64+
/**
65+
* Available versions for this page
66+
* @example ['v7.119.0', 'next']
67+
*/
68+
versions?: string[];
6369
}

0 commit comments

Comments
 (0)