diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.js b/packages/docusaurus-theme-classic/src/theme/Layout/index.js
index c001ed1368..571a10db98 100644
--- a/packages/docusaurus-theme-classic/src/theme/Layout/index.js
+++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.js
@@ -11,6 +11,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl';
import ThemeProvider from '@theme/ThemeProvider';
+import TabGroupChoiceProvider from '@theme/TabGroupChoiceProvider';
import Navbar from '@theme/Navbar';
import Footer from '@theme/Footer';
@@ -41,33 +42,39 @@ function Layout(props) {
return (
-
- {/* TODO: Do not assume that it is in english language */}
-
+
+
+ {/* TODO: Do not assume that it is in english language */}
+
-
- {metaTitle &&
+ {!noFooter && }
+
);
}
diff --git a/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceContext.js b/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceContext.js
new file mode 100644
index 0000000000..4b8db8725e
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceContext.js
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {createContext} from 'react';
+
+const TabGroupChoiceContext = createContext({
+ tabGroupChoices: {},
+ setTabGroupChoices: () => {},
+});
+
+export default TabGroupChoiceContext;
diff --git a/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceProvider/index.js b/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceProvider/index.js
new file mode 100644
index 0000000000..195a02d2ad
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/TabGroupChoiceProvider/index.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+
+import useTabGroupChoice from '@theme/hooks/useTabGroupChoice';
+import TabGroupChoiceContext from '@theme/TabGroupChoiceContext';
+
+function TabGroupChoiceProvider(props) {
+ const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice();
+
+ return (
+
+ {props.children}
+
+ );
+}
+
+export default TabGroupChoiceProvider;
diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/index.js b/packages/docusaurus-theme-classic/src/theme/Tabs/index.js
index 22e1557eae..9cd3a76038 100644
--- a/packages/docusaurus-theme-classic/src/theme/Tabs/index.js
+++ b/packages/docusaurus-theme-classic/src/theme/Tabs/index.js
@@ -6,6 +6,7 @@
*/
import React, {useState, Children} from 'react';
+import useTabGroupChoiceContext from '@theme/hooks/useTabGroupChoiceContext';
import classnames from 'classnames';
@@ -17,8 +18,27 @@ const keys = {
};
function Tabs(props) {
- const {block, children, defaultValue, values} = props;
+ const {block, children, defaultValue, values, groupId} = props;
+ const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoiceContext();
const [selectedValue, setSelectedValue] = useState(defaultValue);
+
+ if (groupId != null) {
+ const relevantTabGroupChoice = tabGroupChoices[groupId];
+ if (
+ relevantTabGroupChoice != null &&
+ relevantTabGroupChoice !== selectedValue
+ ) {
+ setSelectedValue(relevantTabGroupChoice);
+ }
+ }
+
+ const changeSelectedValue = newValue => {
+ setSelectedValue(newValue);
+ if (groupId != null) {
+ setTabGroupChoices(groupId, newValue);
+ }
+ };
+
const tabRefs = [];
const focusNextTab = (tabs, target) => {
@@ -73,8 +93,8 @@ function Tabs(props) {
key={value}
ref={tabControl => tabRefs.push(tabControl)}
onKeyDown={event => handleKeydown(tabRefs, event.target, event)}
- onFocus={() => setSelectedValue(value)}
- onClick={() => setSelectedValue(value)}>
+ onFocus={() => changeSelectedValue(value)}
+ onClick={() => changeSelectedValue(value)}>
{label}
))}
diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.js b/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.js
new file mode 100644
index 0000000000..74b8141e8b
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {useState, useCallback, useEffect} from 'react';
+
+const TAB_CHOICE_PREFIX = 'docusaurus.tab.';
+
+const useTabGroupChoice = () => {
+ const [tabGroupChoices, setChoices] = useState({});
+ const setChoiceSyncWithLocalStorage = useCallback((groupId, newChoice) => {
+ try {
+ localStorage.setItem(`${TAB_CHOICE_PREFIX}${groupId}`, newChoice);
+ } catch (err) {
+ console.error(err);
+ }
+ }, []);
+
+ useEffect(() => {
+ try {
+ const localStorageChoices = {};
+ for (let i = 0; i < localStorage.length; i += 1) {
+ const storageKey = localStorage.key(i);
+ if (storageKey.startsWith(TAB_CHOICE_PREFIX)) {
+ const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length);
+ localStorageChoices[groupId] = localStorage.getItem(storageKey);
+ }
+ }
+ setChoices(localStorageChoices);
+ } catch (err) {
+ console.error(err);
+ }
+ }, []);
+
+ return {
+ tabGroupChoices,
+ setTabGroupChoices: (groupId, newChoice) => {
+ setChoices(oldChoices => ({...oldChoices, [groupId]: newChoice}));
+ setChoiceSyncWithLocalStorage(groupId, newChoice);
+ },
+ };
+};
+
+export default useTabGroupChoice;
diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoiceContext.js b/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoiceContext.js
new file mode 100644
index 0000000000..22e20cc1b6
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoiceContext.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {useContext} from 'react';
+
+import TabGroupChoiceContext from '@theme/TabGroupChoiceContext';
+
+function useTabGroupChoiceContext() {
+ return useContext(TabGroupChoiceContext);
+}
+
+export default useTabGroupChoiceContext;
diff --git a/website/docs/markdown-features.mdx b/website/docs/markdown-features.mdx
index f770a965e7..7526970920 100644
--- a/website/docs/markdown-features.mdx
+++ b/website/docs/markdown-features.mdx
@@ -477,6 +477,115 @@ class HelloWorld {
You may want to implement your own `` abstraction if you find the above approach too verbose. We might just implement one in future for convenience.
+If you have multiple of these multi-language code tabs, and you want to sync the selection across the tab instances, read on for the [Syncing tab choices section](#syncing-tab-choices).
+
+### Syncing tab choices
+
+You may want choices of the same kind of tabs to sync with each other. For example, you might want to provide different instructions for users on Windows vs users on macOS, and you want to changing all OS-specific instructions tabs in one click. To achieve that, you can give all related tabs the same `groupId`. Note that doing this will persist the choice in `localStorage` and all `` instances with the same `groupId` will update automatically when the value of one of them is changed.
+
+ import Tabs from '@theme/Tabs';
+ import TabItem from '@theme/TabItem';
+
+ Use Ctrl + C to copy.
+ Use Command + C to copy.
+
+
+
+ Use Ctrl + V to paste.
+ Use Command + V to paste.
+
+
+
+
+Use Ctrl + C to copy.
+Use Command + C to copy.
+
+
+
+Use Ctrl + V to paste.
+Use Command + V to paste.
+
+
+Tab choices with different `groupId`s will not interfere with each other:
+
+ import Tabs from '@theme/Tabs';
+ import TabItem from '@theme/TabItem';
+
+ Windows is windows.
+ Unix is unix.
+
+
+
+ Windows is not unix.
+ Unix is not windows.
+
+
+
+Windows is windows.
+Unix is unix.
+
+
+
+Windows is not unix.
+Unix is not windows.
+
+
### Callouts/admonitions
In addition to the basic Markdown syntax, we use [remark-admonitions](https://github.com/elviswolcott/remark-admonitions) alongside MDX to add support for admonitions.
diff --git a/website/versioned_docs/version-2.0.0-alpha.43/markdown-features.mdx b/website/versioned_docs/version-2.0.0-alpha.43/markdown-features.mdx
index 4ac6ada323..1bedeacf81 100644
--- a/website/versioned_docs/version-2.0.0-alpha.43/markdown-features.mdx
+++ b/website/versioned_docs/version-2.0.0-alpha.43/markdown-features.mdx
@@ -16,7 +16,7 @@ Docusaurus 2 uses modern tooling to help you compose your interactive documentat
In this section, we'd like to introduce you to the tools we've picked that we believe will help you build powerful documentation. Let us walk you through with an example.
-:::important
+:::important
All the following content assumes you are using `@docusaurus/preset-classic`.
:::