@docusaurus/theme-classic 0.0.0-5407 → 0.0.0-5408
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/theme/Tabs/index.js +91 -22
- package/package.json +14 -14
- package/src/theme/Tabs/index.tsx +102 -25
- package/src/theme-classic.d.ts +1 -0
package/lib/theme/Tabs/index.js
CHANGED
|
@@ -4,20 +4,66 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import React, {
|
|
7
|
+
import React, {
|
|
8
|
+
cloneElement,
|
|
9
|
+
isValidElement,
|
|
10
|
+
useCallback,
|
|
11
|
+
useEffect,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
8
14
|
import clsx from 'clsx';
|
|
9
|
-
import
|
|
10
|
-
import {duplicates} from '@docusaurus/theme-common';
|
|
15
|
+
import {useHistory, useLocation} from '@docusaurus/router';
|
|
16
|
+
import {duplicates, useEvent} from '@docusaurus/theme-common';
|
|
11
17
|
import {
|
|
12
18
|
useScrollPositionBlocker,
|
|
13
19
|
useTabGroupChoice,
|
|
14
20
|
} from '@docusaurus/theme-common/internal';
|
|
21
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
15
22
|
import styles from './styles.module.css';
|
|
16
23
|
// A very rough duck type, but good enough to guard against mistakes while
|
|
17
24
|
// allowing customization
|
|
18
25
|
function isTabItem(comp) {
|
|
19
26
|
return 'value' in comp.props;
|
|
20
27
|
}
|
|
28
|
+
function getSearchKey({queryString = false, groupId}) {
|
|
29
|
+
if (typeof queryString === 'string') {
|
|
30
|
+
return queryString;
|
|
31
|
+
}
|
|
32
|
+
if (queryString === false) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (queryString === true && !groupId) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return groupId;
|
|
41
|
+
}
|
|
42
|
+
function useTabQueryString({queryString = false, groupId}) {
|
|
43
|
+
// TODO not re-render optimized
|
|
44
|
+
// See https://thisweekinreact.com/articles/useSyncExternalStore-the-underrated-react-api
|
|
45
|
+
const location = useLocation();
|
|
46
|
+
const history = useHistory();
|
|
47
|
+
const searchKey = getSearchKey({queryString, groupId});
|
|
48
|
+
const get = useCallback(() => {
|
|
49
|
+
if (!searchKey) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return new URLSearchParams(location.search).get(searchKey);
|
|
53
|
+
}, [searchKey, location.search]);
|
|
54
|
+
const set = useCallback(
|
|
55
|
+
(newTabValue) => {
|
|
56
|
+
if (!searchKey) {
|
|
57
|
+
return; // no-op
|
|
58
|
+
}
|
|
59
|
+
const searchParams = new URLSearchParams(location.search);
|
|
60
|
+
searchParams.set(searchKey, newTabValue);
|
|
61
|
+
history.replace({...location, search: searchParams.toString()});
|
|
62
|
+
},
|
|
63
|
+
[searchKey, history, location],
|
|
64
|
+
);
|
|
65
|
+
return {get, set};
|
|
66
|
+
}
|
|
21
67
|
function TabsComponent(props) {
|
|
22
68
|
const {
|
|
23
69
|
lazy,
|
|
@@ -26,6 +72,7 @@ function TabsComponent(props) {
|
|
|
26
72
|
values: valuesProp,
|
|
27
73
|
groupId,
|
|
28
74
|
className,
|
|
75
|
+
queryString = false,
|
|
29
76
|
} = props;
|
|
30
77
|
const children = React.Children.map(props.children, (child) => {
|
|
31
78
|
if (isValidElement(child) && isTabItem(child)) {
|
|
@@ -40,6 +87,7 @@ function TabsComponent(props) {
|
|
|
40
87
|
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
|
41
88
|
);
|
|
42
89
|
});
|
|
90
|
+
const tabQueryString = useTabQueryString({queryString, groupId});
|
|
43
91
|
const values =
|
|
44
92
|
valuesProp ??
|
|
45
93
|
// Only pick keys that we recognize. MDX would inject some keys by default
|
|
@@ -56,37 +104,57 @@ function TabsComponent(props) {
|
|
|
56
104
|
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
|
|
57
105
|
);
|
|
58
106
|
}
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
defaultValueProp
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
children[0].props.value;
|
|
66
|
-
if (defaultValue !== null && !values.some((a) => a.value === defaultValue)) {
|
|
107
|
+
// Warn user about passing incorrect defaultValue as prop.
|
|
108
|
+
if (
|
|
109
|
+
defaultValueProp !== null &&
|
|
110
|
+
defaultValueProp !== undefined &&
|
|
111
|
+
!values.some((a) => a.value === defaultValueProp)
|
|
112
|
+
) {
|
|
67
113
|
throw new Error(
|
|
68
|
-
`Docusaurus error: The <Tabs> has a defaultValue "${
|
|
114
|
+
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValueProp}" but none of its children has the corresponding value. Available values are: ${values
|
|
69
115
|
.map((a) => a.value)
|
|
70
116
|
.join(
|
|
71
117
|
', ',
|
|
72
118
|
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
|
|
73
119
|
);
|
|
74
120
|
}
|
|
75
|
-
const {
|
|
121
|
+
const {
|
|
122
|
+
ready: tabGroupChoicesReady,
|
|
123
|
+
tabGroupChoices,
|
|
124
|
+
setTabGroupChoices,
|
|
125
|
+
} = useTabGroupChoice();
|
|
126
|
+
const defaultValue =
|
|
127
|
+
defaultValueProp !== undefined
|
|
128
|
+
? defaultValueProp
|
|
129
|
+
: children.find((child) => child.props.default)?.props.value ??
|
|
130
|
+
children[0].props.value;
|
|
76
131
|
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
77
132
|
const tabRefs = [];
|
|
78
133
|
const {blockElementScrollPositionUntilNextRender} =
|
|
79
134
|
useScrollPositionBlocker();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
// Lazily restore the appropriate tab selected value
|
|
136
|
+
// We can't read queryString/localStorage on first render
|
|
137
|
+
// It would trigger a React SSR/client hydration mismatch
|
|
138
|
+
const restoreTabSelectedValue = useEvent(() => {
|
|
139
|
+
// wait for localStorage values to be set (initially empty object :s)
|
|
140
|
+
if (tabGroupChoicesReady) {
|
|
141
|
+
// querystring value > localStorage value
|
|
142
|
+
const valueToRestore =
|
|
143
|
+
tabQueryString.get() ?? (groupId && tabGroupChoices[groupId]);
|
|
144
|
+
const isValid =
|
|
145
|
+
valueToRestore &&
|
|
146
|
+
values.some((value) => value.value === valueToRestore);
|
|
147
|
+
if (isValid) {
|
|
148
|
+
setSelectedValue(valueToRestore);
|
|
149
|
+
}
|
|
88
150
|
}
|
|
89
|
-
}
|
|
151
|
+
});
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
// wait for localStorage values to be set (initially empty object :s)
|
|
154
|
+
if (tabGroupChoicesReady) {
|
|
155
|
+
restoreTabSelectedValue();
|
|
156
|
+
}
|
|
157
|
+
}, [tabGroupChoicesReady, restoreTabSelectedValue]);
|
|
90
158
|
const handleTabChange = (event) => {
|
|
91
159
|
const newTab = event.currentTarget;
|
|
92
160
|
const newTabIndex = tabRefs.indexOf(newTab);
|
|
@@ -94,6 +162,7 @@ function TabsComponent(props) {
|
|
|
94
162
|
if (newTabValue !== selectedValue) {
|
|
95
163
|
blockElementScrollPositionUntilNextRender(newTab);
|
|
96
164
|
setSelectedValue(newTabValue);
|
|
165
|
+
tabQueryString.set(newTabValue);
|
|
97
166
|
if (groupId != null) {
|
|
98
167
|
setTabGroupChoices(groupId, String(newTabValue));
|
|
99
168
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/theme-classic",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-5408",
|
|
4
4
|
"description": "Classic theme for Docusaurus",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/theme-classic.d.ts",
|
|
@@ -20,18 +20,18 @@
|
|
|
20
20
|
"copy:watch": "node ../../admin/scripts/copyUntypedFiles.js --watch"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@docusaurus/core": "0.0.0-
|
|
24
|
-
"@docusaurus/mdx-loader": "0.0.0-
|
|
25
|
-
"@docusaurus/module-type-aliases": "0.0.0-
|
|
26
|
-
"@docusaurus/plugin-content-blog": "0.0.0-
|
|
27
|
-
"@docusaurus/plugin-content-docs": "0.0.0-
|
|
28
|
-
"@docusaurus/plugin-content-pages": "0.0.0-
|
|
29
|
-
"@docusaurus/theme-common": "0.0.0-
|
|
30
|
-
"@docusaurus/theme-translations": "0.0.0-
|
|
31
|
-
"@docusaurus/types": "0.0.0-
|
|
32
|
-
"@docusaurus/utils": "0.0.0-
|
|
33
|
-
"@docusaurus/utils-common": "0.0.0-
|
|
34
|
-
"@docusaurus/utils-validation": "0.0.0-
|
|
23
|
+
"@docusaurus/core": "0.0.0-5408",
|
|
24
|
+
"@docusaurus/mdx-loader": "0.0.0-5408",
|
|
25
|
+
"@docusaurus/module-type-aliases": "0.0.0-5408",
|
|
26
|
+
"@docusaurus/plugin-content-blog": "0.0.0-5408",
|
|
27
|
+
"@docusaurus/plugin-content-docs": "0.0.0-5408",
|
|
28
|
+
"@docusaurus/plugin-content-pages": "0.0.0-5408",
|
|
29
|
+
"@docusaurus/theme-common": "0.0.0-5408",
|
|
30
|
+
"@docusaurus/theme-translations": "0.0.0-5408",
|
|
31
|
+
"@docusaurus/types": "0.0.0-5408",
|
|
32
|
+
"@docusaurus/utils": "0.0.0-5408",
|
|
33
|
+
"@docusaurus/utils-common": "0.0.0-5408",
|
|
34
|
+
"@docusaurus/utils-validation": "0.0.0-5408",
|
|
35
35
|
"@mdx-js/react": "^1.6.22",
|
|
36
36
|
"clsx": "^1.2.1",
|
|
37
37
|
"copy-text-to-clipboard": "^3.0.1",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">=16.14"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "6d1f31ee146538494968633a12e92c47cd3de973"
|
|
66
66
|
}
|
package/src/theme/Tabs/index.tsx
CHANGED
|
@@ -6,21 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, {
|
|
9
|
-
useState,
|
|
10
9
|
cloneElement,
|
|
11
10
|
isValidElement,
|
|
11
|
+
useCallback,
|
|
12
|
+
useEffect,
|
|
13
|
+
useState,
|
|
12
14
|
type ReactElement,
|
|
13
15
|
} from 'react';
|
|
14
16
|
import clsx from 'clsx';
|
|
15
|
-
import
|
|
16
|
-
import {duplicates} from '@docusaurus/theme-common';
|
|
17
|
+
import {useHistory, useLocation} from '@docusaurus/router';
|
|
18
|
+
import {duplicates, useEvent} from '@docusaurus/theme-common';
|
|
17
19
|
import {
|
|
18
20
|
useScrollPositionBlocker,
|
|
19
21
|
useTabGroupChoice,
|
|
20
22
|
} from '@docusaurus/theme-common/internal';
|
|
21
|
-
import
|
|
23
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
22
24
|
import type {Props as TabItemProps} from '@theme/TabItem';
|
|
23
|
-
|
|
25
|
+
import type {Props} from '@theme/Tabs';
|
|
24
26
|
import styles from './styles.module.css';
|
|
25
27
|
|
|
26
28
|
// A very rough duck type, but good enough to guard against mistakes while
|
|
@@ -31,6 +33,57 @@ function isTabItem(
|
|
|
31
33
|
return 'value' in comp.props;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
function getSearchKey({
|
|
37
|
+
queryString = false,
|
|
38
|
+
groupId,
|
|
39
|
+
}: Pick<Props, 'queryString' | 'groupId'>) {
|
|
40
|
+
if (typeof queryString === 'string') {
|
|
41
|
+
return queryString;
|
|
42
|
+
}
|
|
43
|
+
if (queryString === false) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (queryString === true && !groupId) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return groupId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function useTabQueryString({
|
|
55
|
+
queryString = false,
|
|
56
|
+
groupId,
|
|
57
|
+
}: Pick<Props, 'queryString' | 'groupId'>) {
|
|
58
|
+
// TODO not re-render optimized
|
|
59
|
+
// See https://thisweekinreact.com/articles/useSyncExternalStore-the-underrated-react-api
|
|
60
|
+
const location = useLocation();
|
|
61
|
+
const history = useHistory();
|
|
62
|
+
|
|
63
|
+
const searchKey = getSearchKey({queryString, groupId});
|
|
64
|
+
|
|
65
|
+
const get = useCallback(() => {
|
|
66
|
+
if (!searchKey) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return new URLSearchParams(location.search).get(searchKey);
|
|
70
|
+
}, [searchKey, location.search]);
|
|
71
|
+
|
|
72
|
+
const set = useCallback(
|
|
73
|
+
(newTabValue: string) => {
|
|
74
|
+
if (!searchKey) {
|
|
75
|
+
return; // no-op
|
|
76
|
+
}
|
|
77
|
+
const searchParams = new URLSearchParams(location.search);
|
|
78
|
+
searchParams.set(searchKey, newTabValue);
|
|
79
|
+
history.replace({...location, search: searchParams.toString()});
|
|
80
|
+
},
|
|
81
|
+
[searchKey, history, location],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {get, set};
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
function TabsComponent(props: Props): JSX.Element {
|
|
35
88
|
const {
|
|
36
89
|
lazy,
|
|
@@ -39,6 +92,7 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
39
92
|
values: valuesProp,
|
|
40
93
|
groupId,
|
|
41
94
|
className,
|
|
95
|
+
queryString = false,
|
|
42
96
|
} = props;
|
|
43
97
|
const children = React.Children.map(props.children, (child) => {
|
|
44
98
|
if (isValidElement(child) && isTabItem(child)) {
|
|
@@ -53,6 +107,7 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
53
107
|
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
|
54
108
|
);
|
|
55
109
|
});
|
|
110
|
+
const tabQueryString = useTabQueryString({queryString, groupId});
|
|
56
111
|
const values =
|
|
57
112
|
valuesProp ??
|
|
58
113
|
// Only pick keys that we recognize. MDX would inject some keys by default
|
|
@@ -69,16 +124,15 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
69
124
|
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
|
|
70
125
|
);
|
|
71
126
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (defaultValue !== null && !values.some((a) => a.value === defaultValue)) {
|
|
127
|
+
|
|
128
|
+
// Warn user about passing incorrect defaultValue as prop.
|
|
129
|
+
if (
|
|
130
|
+
defaultValueProp !== null &&
|
|
131
|
+
defaultValueProp !== undefined &&
|
|
132
|
+
!values.some((a) => a.value === defaultValueProp)
|
|
133
|
+
) {
|
|
80
134
|
throw new Error(
|
|
81
|
-
`Docusaurus error: The <Tabs> has a defaultValue "${
|
|
135
|
+
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValueProp}" but none of its children has the corresponding value. Available values are: ${values
|
|
82
136
|
.map((a) => a.value)
|
|
83
137
|
.join(
|
|
84
138
|
', ',
|
|
@@ -86,22 +140,45 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
86
140
|
);
|
|
87
141
|
}
|
|
88
142
|
|
|
89
|
-
const {
|
|
143
|
+
const {
|
|
144
|
+
ready: tabGroupChoicesReady,
|
|
145
|
+
tabGroupChoices,
|
|
146
|
+
setTabGroupChoices,
|
|
147
|
+
} = useTabGroupChoice();
|
|
148
|
+
const defaultValue =
|
|
149
|
+
defaultValueProp !== undefined
|
|
150
|
+
? defaultValueProp
|
|
151
|
+
: children.find((child) => child.props.default)?.props.value ??
|
|
152
|
+
children[0]!.props.value;
|
|
153
|
+
|
|
90
154
|
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
91
155
|
const tabRefs: (HTMLLIElement | null)[] = [];
|
|
92
156
|
const {blockElementScrollPositionUntilNextRender} =
|
|
93
157
|
useScrollPositionBlocker();
|
|
94
158
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
// Lazily restore the appropriate tab selected value
|
|
160
|
+
// We can't read queryString/localStorage on first render
|
|
161
|
+
// It would trigger a React SSR/client hydration mismatch
|
|
162
|
+
const restoreTabSelectedValue = useEvent(() => {
|
|
163
|
+
// wait for localStorage values to be set (initially empty object :s)
|
|
164
|
+
if (tabGroupChoicesReady) {
|
|
165
|
+
// querystring value > localStorage value
|
|
166
|
+
const valueToRestore =
|
|
167
|
+
tabQueryString.get() ?? (groupId && tabGroupChoices[groupId]);
|
|
168
|
+
const isValid =
|
|
169
|
+
valueToRestore &&
|
|
170
|
+
values.some((value) => value.value === valueToRestore);
|
|
171
|
+
if (isValid) {
|
|
172
|
+
setSelectedValue(valueToRestore);
|
|
173
|
+
}
|
|
103
174
|
}
|
|
104
|
-
}
|
|
175
|
+
});
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
// wait for localStorage values to be set (initially empty object :s)
|
|
178
|
+
if (tabGroupChoicesReady) {
|
|
179
|
+
restoreTabSelectedValue();
|
|
180
|
+
}
|
|
181
|
+
}, [tabGroupChoicesReady, restoreTabSelectedValue]);
|
|
105
182
|
|
|
106
183
|
const handleTabChange = (
|
|
107
184
|
event:
|
|
@@ -116,7 +193,7 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
116
193
|
if (newTabValue !== selectedValue) {
|
|
117
194
|
blockElementScrollPositionUntilNextRender(newTab);
|
|
118
195
|
setSelectedValue(newTabValue);
|
|
119
|
-
|
|
196
|
+
tabQueryString.set(newTabValue);
|
|
120
197
|
if (groupId != null) {
|
|
121
198
|
setTabGroupChoices(groupId, String(newTabValue));
|
|
122
199
|
}
|
package/src/theme-classic.d.ts
CHANGED