Skip to content

Commit 840afe9

Browse files
committed
RFC for a docs version selector
An idea how we could do versioning of docs that I think might be sustainable. - In the reference we have a version selector (design TBD) - `sidebarReference.json` can contain a version now to conditionally include entries - a `<VersionCondidion />` component allows to conditionally include content in a doc when APIs change over time Code quality is very rough. Would probably put the version into the path somewhere, but that needs some more changes.
1 parent 07cbd00 commit 840afe9

File tree

8 files changed

+71
-11
lines changed

8 files changed

+71
-11
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"react-collapsed": "4.0.4",
3939
"react-dom": "^0.0.0-experimental-16d053d59-20230506",
4040
"remark-frontmatter": "^4.0.1",
41-
"remark-gfm": "^3.0.1"
41+
"remark-gfm": "^3.0.1",
42+
"semver": "^7.6.0"
4243
},
4344
"devDependencies": {
4445
"@babel/core": "^7.12.9",
@@ -54,6 +55,7 @@
5455
"@types/parse-numeric-range": "^0.0.1",
5556
"@types/react": "^18.0.9",
5657
"@types/react-dom": "^18.0.5",
58+
"@types/semver": "^7.5.8",
5759
"@typescript-eslint/eslint-plugin": "^5.36.2",
5860
"@typescript-eslint/parser": "^5.36.2",
5961
"asyncro": "^3.0.0",

src/components/Layout/Sidebar/SidebarRouteTree.tsx

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {useRef, useLayoutEffect, Fragment} from 'react';
66

77
import cn from 'classnames';
8+
import * as Semver from 'semver';
89
import {useRouter} from 'next/router';
910
import {SidebarLink} from './SidebarLink';
1011
import {useCollapse} from 'react-collapsed';
@@ -77,6 +78,8 @@ export function SidebarRouteTree({
7778
}: SidebarRouteTreeProps) {
7879
const slug = useRouter().asPath.split(/[\?\#]/)[0];
7980
const pendingRoute = usePendingRoute();
81+
const selectedVersion: string =
82+
(useRouter().query.version as string) ?? '19.0.0';
8083
const currentRoutes = routeTree.routes as RouteItem[];
8184
return (
8285
<ul>
@@ -86,13 +89,22 @@ export function SidebarRouteTree({
8689
path,
8790
title,
8891
routes,
89-
canary,
9092
heading,
9193
hasSectionHeader,
9294
sectionHeader,
95+
version,
9396
},
9497
index
9598
) => {
99+
if (
100+
version &&
101+
!Semver.satisfies(
102+
Semver.coerce(selectedVersion) as Semver.SemVer,
103+
version
104+
)
105+
) {
106+
return null;
107+
}
96108
const selected = slug === path;
97109
let listItem = null;
98110
if (!path || heading) {
@@ -120,7 +132,6 @@ export function SidebarRouteTree({
120132
selected={selected}
121133
level={level}
122134
title={title}
123-
canary={canary}
124135
isExpanded={isExpanded}
125136
hideArrow={isForceExpanded}
126137
/>
@@ -144,7 +155,6 @@ export function SidebarRouteTree({
144155
selected={selected}
145156
level={level}
146157
title={title}
147-
canary={canary}
148158
/>
149159
</li>
150160
);
@@ -163,7 +173,7 @@ export function SidebarRouteTree({
163173
'mb-1 text-sm font-bold ms-5 text-tertiary dark:text-tertiary-dark',
164174
index !== 0 && 'mt-2'
165175
)}>
166-
{sectionHeader}
176+
{sectionHeader?.replace('%VERSION%', selectedVersion)}
167177
</h3>
168178
</Fragment>
169179
);

src/components/Layout/SidebarNav/SidebarNav.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import cn from 'classnames';
88
import {Feedback} from '../Feedback';
99
import {SidebarRouteTree} from '../Sidebar/SidebarRouteTree';
1010
import type {RouteItem} from '../getRouteMeta';
11+
import {useRouter} from 'next/router';
1112

1213
declare global {
1314
interface Window {
@@ -23,6 +24,14 @@ export default function SidebarNav({
2324
routeTree: RouteItem;
2425
breadcrumbs: RouteItem[];
2526
}) {
27+
const {push, query} = useRouter();
28+
const onChangeVersion = React.useCallback(
29+
(e: any) => {
30+
push({query: {...query, version: e.target.value}});
31+
},
32+
[push, query]
33+
);
34+
2635
// HACK. Fix up the data structures instead.
2736
if ((routeTree as any).routes.length === 1) {
2837
routeTree = (routeTree as any).routes[0];
@@ -48,6 +57,12 @@ export default function SidebarNav({
4857
className="w-full pt-6 scrolling-touch lg:h-auto grow pe-0 lg:pe-5 lg:pb-16 md:pt-4 lg:pt-4 scrolling-gpu">
4958
{/* No fallback UI so need to be careful not to suspend directly inside. */}
5059
<Suspense fallback={null}>
60+
<select
61+
value={query.version ?? '19.0.0'}
62+
onChange={onChangeVersion}>
63+
<option value="18.0.0">Version 18.0.0</option>
64+
<option value="19.0.0">Version 19.0.0</option>
65+
</select>
5166
<SidebarRouteTree
5267
routeTree={routeTree}
5368
breadcrumbs={breadcrumbs}

src/components/Layout/getRouteMeta.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export type RouteTag =
1919
export interface RouteItem {
2020
/** Page title (for the sidebar) */
2121
title: string;
22-
/** Optional canary flag for heading */
23-
canary?: boolean;
22+
/** Optional version that supports this item */
23+
version?: string;
2424
/** Optional page description for heading */
2525
description?: string;
2626
/* Additional meta info for page tagging */

src/components/MDX/MDXComponents.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,21 @@ import ButtonLink from 'components/ButtonLink';
3131
import {TocContext} from './TocContext';
3232
import type {Toc, TocItem} from './TocContext';
3333
import {TeamMember} from './TeamMember';
34+
import * as Semver from 'semver';
3435

3536
import ErrorDecoder from './ErrorDecoder';
3637
import {IconCanary} from '../Icon/IconCanary';
38+
import {useRouter} from 'next/router';
39+
40+
function VersionCondition({children, range}: {children: any; range: string}) {
41+
const router = useRouter();
42+
return Semver.satisfies(
43+
Semver.coerce((router.query.version as any) ?? '19.0.0') as any,
44+
range
45+
)
46+
? children
47+
: null;
48+
}
3749

3850
function CodeStep({children, step}: {children: any; step: number}) {
3951
return (
@@ -462,6 +474,7 @@ export const MDXComponents = {
462474
CodeStep,
463475
YouTubeIframe,
464476
ErrorDecoder,
477+
VersionCondition,
465478
};
466479

467480
for (let key in MDXComponents) {

src/content/reference/react/useCallback.md

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ const cachedFn = useCallback(fn, dependencies)
2020

2121
### `useCallback(fn, dependencies)` {/*usecallback*/}
2222

23+
<VersionCondition range=">= 19.0.0">
24+
# 19+ content example
25+
</VersionCondition>
26+
27+
<VersionCondition range="< 19.0.0">
28+
# pre 19 content example
29+
</VersionCondition>
30+
2331
Call `useCallback` at the top level of your component to cache a function definition between re-renders:
2432

2533
```js {4,9}

src/sidebarReference.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"routes": [
55
{
66
"hasSectionHeader": true,
7-
"sectionHeader": "react@18.2.0"
7+
"sectionHeader": "react@%VERSION%"
88
},
99
{
1010
"title": "Overview",
@@ -17,7 +17,7 @@
1717
{
1818
"title": "use",
1919
"path": "/reference/react/use",
20-
"canary": true
20+
"version": ">= 19"
2121
},
2222
{
2323
"title": "useCallback",
@@ -62,7 +62,7 @@
6262
{
6363
"title": "useOptimistic",
6464
"path": "/reference/react/useOptimistic",
65-
"canary": true
65+
"version": ">= 19"
6666
},
6767
{
6868
"title": "useReducer",
@@ -168,7 +168,7 @@
168168
},
169169
{
170170
"hasSectionHeader": true,
171-
"sectionHeader": "react-dom@18.2.0"
171+
"sectionHeader": "react-dom@%VERSION%"
172172
},
173173
{
174174
"title": "Hooks",

yarn.lock

+12
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,11 @@
10731073
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
10741074
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
10751075

1076+
"@types/semver@^7.5.8":
1077+
version "7.5.8"
1078+
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
1079+
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
1080+
10761081
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
10771082
version "2.0.6"
10781083
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
@@ -5704,6 +5709,13 @@ semver@^7.3.7:
57045709
dependencies:
57055710
lru-cache "^6.0.0"
57065711

5712+
semver@^7.6.0:
5713+
version "7.6.0"
5714+
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
5715+
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
5716+
dependencies:
5717+
lru-cache "^6.0.0"
5718+
57075719
57085720
version "0.18.0"
57095721
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"

0 commit comments

Comments
 (0)