@docusaurus/theme-classic 0.0.0-5428 → 0.0.0-5431
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/Layout/Provider/index.js +0 -2
- package/lib/theme/Tabs/index.js +62 -198
- package/package.json +14 -14
- package/src/theme/Layout/Provider/index.tsx +0 -2
- package/src/theme/Tabs/index.tsx +78 -218
- package/src/theme-classic.d.ts +4 -26
|
@@ -8,7 +8,6 @@ import React from 'react';
|
|
|
8
8
|
import {composeProviders} from '@docusaurus/theme-common';
|
|
9
9
|
import {
|
|
10
10
|
ColorModeProvider,
|
|
11
|
-
TabGroupChoiceProvider,
|
|
12
11
|
AnnouncementBarProvider,
|
|
13
12
|
DocsPreferredVersionContextProvider,
|
|
14
13
|
ScrollControllerProvider,
|
|
@@ -18,7 +17,6 @@ import {
|
|
|
18
17
|
const Provider = composeProviders([
|
|
19
18
|
ColorModeProvider,
|
|
20
19
|
AnnouncementBarProvider,
|
|
21
|
-
TabGroupChoiceProvider,
|
|
22
20
|
ScrollControllerProvider,
|
|
23
21
|
DocsPreferredVersionContextProvider,
|
|
24
22
|
PluginHtmlClassNameProvider,
|
package/lib/theme/Tabs/index.js
CHANGED
|
@@ -4,168 +4,25 @@
|
|
|
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, {
|
|
8
|
-
cloneElement,
|
|
9
|
-
isValidElement,
|
|
10
|
-
useCallback,
|
|
11
|
-
useEffect,
|
|
12
|
-
useState,
|
|
13
|
-
} from 'react';
|
|
7
|
+
import React, {cloneElement} from 'react';
|
|
14
8
|
import clsx from 'clsx';
|
|
15
|
-
import {useHistory, useLocation} from '@docusaurus/router';
|
|
16
|
-
import {duplicates, useEvent} from '@docusaurus/theme-common';
|
|
17
9
|
import {
|
|
18
10
|
useScrollPositionBlocker,
|
|
19
|
-
|
|
11
|
+
useTabs,
|
|
20
12
|
} from '@docusaurus/theme-common/internal';
|
|
21
13
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
22
14
|
import styles from './styles.module.css';
|
|
23
|
-
|
|
24
|
-
// allowing customization
|
|
25
|
-
function isTabItem(comp) {
|
|
26
|
-
return 'value' in comp.props;
|
|
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
|
-
}
|
|
67
|
-
function TabsComponent(props) {
|
|
68
|
-
const {
|
|
69
|
-
lazy,
|
|
70
|
-
block,
|
|
71
|
-
defaultValue: defaultValueProp,
|
|
72
|
-
values: valuesProp,
|
|
73
|
-
groupId,
|
|
74
|
-
className,
|
|
75
|
-
queryString = false,
|
|
76
|
-
} = props;
|
|
77
|
-
const children = React.Children.map(props.children, (child) => {
|
|
78
|
-
if (isValidElement(child) && isTabItem(child)) {
|
|
79
|
-
return child;
|
|
80
|
-
}
|
|
81
|
-
// child.type.name will give non-sensical values in prod because of
|
|
82
|
-
// minification, but we assume it won't throw in prod.
|
|
83
|
-
throw new Error(
|
|
84
|
-
`Docusaurus error: Bad <Tabs> child <${
|
|
85
|
-
// @ts-expect-error: guarding against unexpected cases
|
|
86
|
-
typeof child.type === 'string' ? child.type : child.type.name
|
|
87
|
-
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
const tabQueryString = useTabQueryString({queryString, groupId});
|
|
91
|
-
const values =
|
|
92
|
-
valuesProp ??
|
|
93
|
-
// Only pick keys that we recognize. MDX would inject some keys by default
|
|
94
|
-
children.map(({props: {value, label, attributes}}) => ({
|
|
95
|
-
value,
|
|
96
|
-
label,
|
|
97
|
-
attributes,
|
|
98
|
-
}));
|
|
99
|
-
const dup = duplicates(values, (a, b) => a.value === b.value);
|
|
100
|
-
if (dup.length > 0) {
|
|
101
|
-
throw new Error(
|
|
102
|
-
`Docusaurus error: Duplicate values "${dup
|
|
103
|
-
.map((a) => a.value)
|
|
104
|
-
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
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
|
-
) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValueProp}" but none of its children has the corresponding value. Available values are: ${values
|
|
115
|
-
.map((a) => a.value)
|
|
116
|
-
.join(
|
|
117
|
-
', ',
|
|
118
|
-
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
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;
|
|
131
|
-
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
15
|
+
function TabList({className, block, selectedValue, selectValue, tabValues}) {
|
|
132
16
|
const tabRefs = [];
|
|
133
17
|
const {blockElementScrollPositionUntilNextRender} =
|
|
134
18
|
useScrollPositionBlocker();
|
|
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
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
// wait for localStorage values to be set (initially empty object :s)
|
|
154
|
-
if (tabGroupChoicesReady) {
|
|
155
|
-
restoreTabSelectedValue();
|
|
156
|
-
}
|
|
157
|
-
}, [tabGroupChoicesReady, restoreTabSelectedValue]);
|
|
158
19
|
const handleTabChange = (event) => {
|
|
159
20
|
const newTab = event.currentTarget;
|
|
160
21
|
const newTabIndex = tabRefs.indexOf(newTab);
|
|
161
|
-
const newTabValue =
|
|
22
|
+
const newTabValue = tabValues[newTabIndex].value;
|
|
162
23
|
if (newTabValue !== selectedValue) {
|
|
163
24
|
blockElementScrollPositionUntilNextRender(newTab);
|
|
164
|
-
|
|
165
|
-
tabQueryString.set(newTabValue);
|
|
166
|
-
if (groupId != null) {
|
|
167
|
-
setTabGroupChoices(groupId, String(newTabValue));
|
|
168
|
-
}
|
|
25
|
+
selectValue(newTabValue);
|
|
169
26
|
}
|
|
170
27
|
};
|
|
171
28
|
const handleKeydown = (event) => {
|
|
@@ -191,60 +48,67 @@ function TabsComponent(props) {
|
|
|
191
48
|
focusElement?.focus();
|
|
192
49
|
};
|
|
193
50
|
return (
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
)}
|
|
243
|
-
</div>
|
|
51
|
+
<ul
|
|
52
|
+
role="tablist"
|
|
53
|
+
aria-orientation="horizontal"
|
|
54
|
+
className={clsx(
|
|
55
|
+
'tabs',
|
|
56
|
+
{
|
|
57
|
+
'tabs--block': block,
|
|
58
|
+
},
|
|
59
|
+
className,
|
|
60
|
+
)}>
|
|
61
|
+
{tabValues.map(({value, label, attributes}) => (
|
|
62
|
+
<li
|
|
63
|
+
// TODO extract TabListItem
|
|
64
|
+
role="tab"
|
|
65
|
+
tabIndex={selectedValue === value ? 0 : -1}
|
|
66
|
+
aria-selected={selectedValue === value}
|
|
67
|
+
key={value}
|
|
68
|
+
ref={(tabControl) => tabRefs.push(tabControl)}
|
|
69
|
+
onKeyDown={handleKeydown}
|
|
70
|
+
onClick={handleTabChange}
|
|
71
|
+
{...attributes}
|
|
72
|
+
className={clsx('tabs__item', styles.tabItem, attributes?.className, {
|
|
73
|
+
'tabs__item--active': selectedValue === value,
|
|
74
|
+
})}>
|
|
75
|
+
{label ?? value}
|
|
76
|
+
</li>
|
|
77
|
+
))}
|
|
78
|
+
</ul>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function TabContent({lazy, children, selectedValue}) {
|
|
82
|
+
if (lazy) {
|
|
83
|
+
const selectedTabItem = children.find(
|
|
84
|
+
(tabItem) => tabItem.props.value === selectedValue,
|
|
85
|
+
);
|
|
86
|
+
if (!selectedTabItem) {
|
|
87
|
+
// fail-safe or fail-fast? not sure what's best here
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return cloneElement(selectedTabItem, {className: 'margin-top--md'});
|
|
91
|
+
}
|
|
92
|
+
return (
|
|
93
|
+
<div className="margin-top--md">
|
|
94
|
+
{children.map((tabItem, i) =>
|
|
95
|
+
cloneElement(tabItem, {
|
|
96
|
+
key: i,
|
|
97
|
+
hidden: tabItem.props.value !== selectedValue,
|
|
98
|
+
}),
|
|
244
99
|
)}
|
|
245
100
|
</div>
|
|
246
101
|
);
|
|
247
102
|
}
|
|
103
|
+
function TabsComponent(props) {
|
|
104
|
+
const tabs = useTabs(props);
|
|
105
|
+
return (
|
|
106
|
+
<div className={clsx('tabs-container', styles.tabList)}>
|
|
107
|
+
<TabList {...props} {...tabs} />
|
|
108
|
+
<TabContent {...props} {...tabs} />
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
248
112
|
export default function Tabs(props) {
|
|
249
113
|
const isBrowser = useIsBrowser();
|
|
250
114
|
return (
|
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-5431",
|
|
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-5431",
|
|
24
|
+
"@docusaurus/mdx-loader": "0.0.0-5431",
|
|
25
|
+
"@docusaurus/module-type-aliases": "0.0.0-5431",
|
|
26
|
+
"@docusaurus/plugin-content-blog": "0.0.0-5431",
|
|
27
|
+
"@docusaurus/plugin-content-docs": "0.0.0-5431",
|
|
28
|
+
"@docusaurus/plugin-content-pages": "0.0.0-5431",
|
|
29
|
+
"@docusaurus/theme-common": "0.0.0-5431",
|
|
30
|
+
"@docusaurus/theme-translations": "0.0.0-5431",
|
|
31
|
+
"@docusaurus/types": "0.0.0-5431",
|
|
32
|
+
"@docusaurus/utils": "0.0.0-5431",
|
|
33
|
+
"@docusaurus/utils-common": "0.0.0-5431",
|
|
34
|
+
"@docusaurus/utils-validation": "0.0.0-5431",
|
|
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": "ddec3dc0d57757f793a7218dacc6c4e95f00d737"
|
|
66
66
|
}
|
|
@@ -9,7 +9,6 @@ import React from 'react';
|
|
|
9
9
|
import {composeProviders} from '@docusaurus/theme-common';
|
|
10
10
|
import {
|
|
11
11
|
ColorModeProvider,
|
|
12
|
-
TabGroupChoiceProvider,
|
|
13
12
|
AnnouncementBarProvider,
|
|
14
13
|
DocsPreferredVersionContextProvider,
|
|
15
14
|
ScrollControllerProvider,
|
|
@@ -21,7 +20,6 @@ import type {Props} from '@theme/Layout/Provider';
|
|
|
21
20
|
const Provider = composeProviders([
|
|
22
21
|
ColorModeProvider,
|
|
23
22
|
AnnouncementBarProvider,
|
|
24
|
-
TabGroupChoiceProvider,
|
|
25
23
|
ScrollControllerProvider,
|
|
26
24
|
DocsPreferredVersionContextProvider,
|
|
27
25
|
PluginHtmlClassNameProvider,
|
package/src/theme/Tabs/index.tsx
CHANGED
|
@@ -5,181 +5,27 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, {
|
|
9
|
-
cloneElement,
|
|
10
|
-
isValidElement,
|
|
11
|
-
useCallback,
|
|
12
|
-
useEffect,
|
|
13
|
-
useState,
|
|
14
|
-
type ReactElement,
|
|
15
|
-
} from 'react';
|
|
8
|
+
import React, {cloneElement} from 'react';
|
|
16
9
|
import clsx from 'clsx';
|
|
17
|
-
import {useHistory, useLocation} from '@docusaurus/router';
|
|
18
|
-
import {duplicates, useEvent} from '@docusaurus/theme-common';
|
|
19
10
|
import {
|
|
20
11
|
useScrollPositionBlocker,
|
|
21
|
-
|
|
12
|
+
useTabs,
|
|
22
13
|
} from '@docusaurus/theme-common/internal';
|
|
23
14
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
24
|
-
import type {Props as TabItemProps} from '@theme/TabItem';
|
|
25
15
|
import type {Props} from '@theme/Tabs';
|
|
26
16
|
import styles from './styles.module.css';
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
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
|
-
|
|
87
|
-
function TabsComponent(props: Props): JSX.Element {
|
|
88
|
-
const {
|
|
89
|
-
lazy,
|
|
90
|
-
block,
|
|
91
|
-
defaultValue: defaultValueProp,
|
|
92
|
-
values: valuesProp,
|
|
93
|
-
groupId,
|
|
94
|
-
className,
|
|
95
|
-
queryString = false,
|
|
96
|
-
} = props;
|
|
97
|
-
const children = React.Children.map(props.children, (child) => {
|
|
98
|
-
if (isValidElement(child) && isTabItem(child)) {
|
|
99
|
-
return child;
|
|
100
|
-
}
|
|
101
|
-
// child.type.name will give non-sensical values in prod because of
|
|
102
|
-
// minification, but we assume it won't throw in prod.
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Docusaurus error: Bad <Tabs> child <${
|
|
105
|
-
// @ts-expect-error: guarding against unexpected cases
|
|
106
|
-
typeof child.type === 'string' ? child.type : child.type.name
|
|
107
|
-
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
const tabQueryString = useTabQueryString({queryString, groupId});
|
|
111
|
-
const values =
|
|
112
|
-
valuesProp ??
|
|
113
|
-
// Only pick keys that we recognize. MDX would inject some keys by default
|
|
114
|
-
children.map(({props: {value, label, attributes}}) => ({
|
|
115
|
-
value,
|
|
116
|
-
label,
|
|
117
|
-
attributes,
|
|
118
|
-
}));
|
|
119
|
-
const dup = duplicates(values, (a, b) => a.value === b.value);
|
|
120
|
-
if (dup.length > 0) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`Docusaurus error: Duplicate values "${dup
|
|
123
|
-
.map((a) => a.value)
|
|
124
|
-
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
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
|
-
) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValueProp}" but none of its children has the corresponding value. Available values are: ${values
|
|
136
|
-
.map((a) => a.value)
|
|
137
|
-
.join(
|
|
138
|
-
', ',
|
|
139
|
-
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
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
|
-
|
|
154
|
-
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
18
|
+
function TabList({
|
|
19
|
+
className,
|
|
20
|
+
block,
|
|
21
|
+
selectedValue,
|
|
22
|
+
selectValue,
|
|
23
|
+
tabValues,
|
|
24
|
+
}: Props & ReturnType<typeof useTabs>) {
|
|
155
25
|
const tabRefs: (HTMLLIElement | null)[] = [];
|
|
156
26
|
const {blockElementScrollPositionUntilNextRender} =
|
|
157
27
|
useScrollPositionBlocker();
|
|
158
28
|
|
|
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
|
-
}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
// wait for localStorage values to be set (initially empty object :s)
|
|
178
|
-
if (tabGroupChoicesReady) {
|
|
179
|
-
restoreTabSelectedValue();
|
|
180
|
-
}
|
|
181
|
-
}, [tabGroupChoicesReady, restoreTabSelectedValue]);
|
|
182
|
-
|
|
183
29
|
const handleTabChange = (
|
|
184
30
|
event:
|
|
185
31
|
| React.FocusEvent<HTMLLIElement>
|
|
@@ -188,15 +34,11 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
188
34
|
) => {
|
|
189
35
|
const newTab = event.currentTarget;
|
|
190
36
|
const newTabIndex = tabRefs.indexOf(newTab);
|
|
191
|
-
const newTabValue =
|
|
37
|
+
const newTabValue = tabValues[newTabIndex]!.value;
|
|
192
38
|
|
|
193
39
|
if (newTabValue !== selectedValue) {
|
|
194
40
|
blockElementScrollPositionUntilNextRender(newTab);
|
|
195
|
-
|
|
196
|
-
tabQueryString.set(newTabValue);
|
|
197
|
-
if (groupId != null) {
|
|
198
|
-
setTabGroupChoices(groupId, String(newTabValue));
|
|
199
|
-
}
|
|
41
|
+
selectValue(newTabValue);
|
|
200
42
|
}
|
|
201
43
|
};
|
|
202
44
|
|
|
@@ -226,61 +68,79 @@ function TabsComponent(props: Props): JSX.Element {
|
|
|
226
68
|
};
|
|
227
69
|
|
|
228
70
|
return (
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
71
|
+
<ul
|
|
72
|
+
role="tablist"
|
|
73
|
+
aria-orientation="horizontal"
|
|
74
|
+
className={clsx(
|
|
75
|
+
'tabs',
|
|
76
|
+
{
|
|
77
|
+
'tabs--block': block,
|
|
78
|
+
},
|
|
79
|
+
className,
|
|
80
|
+
)}>
|
|
81
|
+
{tabValues.map(({value, label, attributes}) => (
|
|
82
|
+
<li
|
|
83
|
+
// TODO extract TabListItem
|
|
84
|
+
role="tab"
|
|
85
|
+
tabIndex={selectedValue === value ? 0 : -1}
|
|
86
|
+
aria-selected={selectedValue === value}
|
|
87
|
+
key={value}
|
|
88
|
+
ref={(tabControl) => tabRefs.push(tabControl)}
|
|
89
|
+
onKeyDown={handleKeydown}
|
|
90
|
+
onClick={handleTabChange}
|
|
91
|
+
{...attributes}
|
|
92
|
+
className={clsx(
|
|
93
|
+
'tabs__item',
|
|
94
|
+
styles.tabItem,
|
|
95
|
+
attributes?.className as string,
|
|
96
|
+
{
|
|
97
|
+
'tabs__item--active': selectedValue === value,
|
|
98
|
+
},
|
|
99
|
+
)}>
|
|
100
|
+
{label ?? value}
|
|
101
|
+
</li>
|
|
102
|
+
))}
|
|
103
|
+
</ul>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
262
106
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
107
|
+
function TabContent({
|
|
108
|
+
lazy,
|
|
109
|
+
children,
|
|
110
|
+
selectedValue,
|
|
111
|
+
}: Props & ReturnType<typeof useTabs>) {
|
|
112
|
+
if (lazy) {
|
|
113
|
+
const selectedTabItem = children.find(
|
|
114
|
+
(tabItem) => tabItem.props.value === selectedValue,
|
|
115
|
+
);
|
|
116
|
+
if (!selectedTabItem) {
|
|
117
|
+
// fail-safe or fail-fast? not sure what's best here
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return cloneElement(selectedTabItem, {className: 'margin-top--md'});
|
|
121
|
+
}
|
|
122
|
+
return (
|
|
123
|
+
<div className="margin-top--md">
|
|
124
|
+
{children.map((tabItem, i) =>
|
|
125
|
+
cloneElement(tabItem, {
|
|
126
|
+
key: i,
|
|
127
|
+
hidden: tabItem.props.value !== selectedValue,
|
|
128
|
+
}),
|
|
279
129
|
)}
|
|
280
130
|
</div>
|
|
281
131
|
);
|
|
282
132
|
}
|
|
283
133
|
|
|
134
|
+
function TabsComponent(props: Props): JSX.Element {
|
|
135
|
+
const tabs = useTabs(props);
|
|
136
|
+
return (
|
|
137
|
+
<div className={clsx('tabs-container', styles.tabList)}>
|
|
138
|
+
<TabList {...props} {...tabs} />
|
|
139
|
+
<TabContent {...props} {...tabs} />
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
284
144
|
export default function Tabs(props: Props): JSX.Element {
|
|
285
145
|
const isBrowser = useIsBrowser();
|
|
286
146
|
return (
|
package/src/theme-classic.d.ts
CHANGED
|
@@ -1232,39 +1232,17 @@ declare module '@theme/Mermaid' {
|
|
|
1232
1232
|
}
|
|
1233
1233
|
|
|
1234
1234
|
declare module '@theme/TabItem' {
|
|
1235
|
-
import type {
|
|
1235
|
+
import type {TabItemProps} from '@docusaurus/theme-common/internal';
|
|
1236
1236
|
|
|
1237
|
-
export interface Props {
|
|
1238
|
-
readonly children: ReactNode;
|
|
1239
|
-
readonly value: string;
|
|
1240
|
-
readonly default?: boolean;
|
|
1241
|
-
readonly label?: string;
|
|
1242
|
-
readonly hidden?: boolean;
|
|
1243
|
-
readonly className?: string;
|
|
1244
|
-
readonly attributes?: {[key: string]: unknown};
|
|
1245
|
-
}
|
|
1237
|
+
export interface Props extends TabItemProps {}
|
|
1246
1238
|
|
|
1247
1239
|
export default function TabItem(props: Props): JSX.Element;
|
|
1248
1240
|
}
|
|
1249
1241
|
|
|
1250
1242
|
declare module '@theme/Tabs' {
|
|
1251
|
-
import type {
|
|
1252
|
-
import type {Props as TabItemProps} from '@theme/TabItem';
|
|
1243
|
+
import type {TabsProps} from '@docusaurus/theme-common/internal';
|
|
1253
1244
|
|
|
1254
|
-
export interface Props {
|
|
1255
|
-
readonly lazy?: boolean;
|
|
1256
|
-
readonly block?: boolean;
|
|
1257
|
-
readonly children: readonly ReactElement<TabItemProps>[];
|
|
1258
|
-
readonly defaultValue?: string | null;
|
|
1259
|
-
readonly values?: readonly {
|
|
1260
|
-
value: string;
|
|
1261
|
-
label?: string;
|
|
1262
|
-
attributes?: {[key: string]: unknown};
|
|
1263
|
-
}[];
|
|
1264
|
-
readonly groupId?: string;
|
|
1265
|
-
readonly className?: string;
|
|
1266
|
-
readonly queryString?: string | boolean;
|
|
1267
|
-
}
|
|
1245
|
+
export interface Props extends TabsProps {}
|
|
1268
1246
|
|
|
1269
1247
|
export default function Tabs(props: Props): JSX.Element;
|
|
1270
1248
|
}
|