feat(theme-classic): allow passing tab label and default value through TabItem (#5442)

* Implement feature

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Dogfood edge case

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Correct keydown handling

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update index.tsx

* Update markdown-features-tabs.mdx

* Update markdown-features-tabs.mdx

* polish tabs doc

* Update markdown-features-tabs.mdx

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2021-09-01 18:44:37 +08:00 committed by GitHub
parent 553f914639
commit 5f003bcabd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 166 deletions

View file

@ -21,18 +21,32 @@ function isInViewport(element: HTMLElement): boolean {
return top >= 0 && right <= innerWidth && bottom <= innerHeight && left >= 0;
}
const keys = {
left: 37,
right: 39,
} as const;
function Tabs(props: Props): JSX.Element {
const {lazy, block, defaultValue, values, groupId, className} = props;
const {tabGroupChoices, setTabGroupChoices} = useUserPreferencesContext();
const [selectedValue, setSelectedValue] = useState(defaultValue);
const {
lazy,
block,
defaultValue: defaultValueProp,
values: valuesProp,
groupId,
className,
} = props;
const children = Children.toArray(
props.children,
) as ReactElement<TabItemProps>[];
const values =
valuesProp ??
children.map((child) => {
return {
value: child.props.value,
label: child.props.label,
};
});
const defaultValue =
defaultValueProp ??
children.find((child) => child.props.default)?.props.value;
const {tabGroupChoices, setTabGroupChoices} = useUserPreferencesContext();
const [selectedValue, setSelectedValue] = useState(defaultValue);
const tabRefs: (HTMLLIElement | null)[] = [];
if (groupId != null) {
@ -77,17 +91,17 @@ function Tabs(props: Props): JSX.Element {
}
};
const handleKeydown = (event) => {
let focusElement;
const handleKeydown = (event: React.KeyboardEvent<HTMLLIElement>) => {
let focusElement: HTMLLIElement | null = null;
switch (event.keyCode) {
case keys.right: {
const nextTab = tabRefs.indexOf(event.target) + 1;
switch (event.key) {
case 'ArrowRight': {
const nextTab = tabRefs.indexOf(event.target as HTMLLIElement) + 1;
focusElement = tabRefs[nextTab] || tabRefs[0];
break;
}
case keys.left: {
const prevTab = tabRefs.indexOf(event.target) - 1;
case 'ArrowLeft': {
const prevTab = tabRefs.indexOf(event.target as HTMLLIElement) - 1;
focusElement = tabRefs[prevTab] || tabRefs[tabRefs.length - 1];
break;
}
@ -123,7 +137,7 @@ function Tabs(props: Props): JSX.Element {
onKeyDown={handleKeydown}
onFocus={handleTabChange}
onClick={handleTabChange}>
{label}
{label ?? value}
</li>
))}
</ul>

View file

@ -522,6 +522,8 @@ declare module '@theme/TabItem' {
export type Props = {
readonly children: ReactNode;
readonly value: string;
readonly default?: boolean;
readonly label?: string;
readonly hidden?: boolean;
readonly className?: string;
};
@ -539,7 +541,7 @@ declare module '@theme/Tabs' {
readonly block?: boolean;
readonly children: readonly ReactElement<TabItemProps>[];
readonly defaultValue?: string;
readonly values: readonly {value: string; label: string}[];
readonly values?: readonly {value: string; label?: string}[];
readonly groupId?: string;
readonly className?: string;
};

View file

@ -5,32 +5,46 @@ description: Using tabs inside Docusaurus Markdown
slug: /markdown-features/tabs
---
```mdx-code-block
import BrowserWindow from '@site/src/components/BrowserWindow';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```
To show tabbed content within Markdown files, you can fall back on MDX. Docusaurus provides `<Tabs>` components out-of-the-box.
Docusaurus provides `<Tabs>` components that you can use thanks to [MDX](./markdown-features-react.mdx):
```jsx
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
defaultValue="apple"
values={[
{label: 'Apple', value: 'apple'},
{label: 'Orange', value: 'orange'},
{label: 'Banana', value: 'banana'},
]}>
<TabItem value="apple">This is an apple 🍎</TabItem>
<TabItem value="orange">This is an orange 🍊</TabItem>
<TabItem value="banana">This is a banana 🍌</TabItem>
<Tabs>
<TabItem value="apple" label="Apple" default>
This is an apple 🍎
</TabItem>
<TabItem value="orange" label="Orange">
This is an orange 🍊
</TabItem>
<TabItem value="banana" label="Banana">
This is a banana 🍌
</TabItem>
</Tabs>;
```
And you will get the following:
```mdx-code-block
<BrowserWindow>
<Tabs>
<TabItem value="apple" label="Apple" default>This is an apple 🍎</TabItem>
<TabItem value="orange" label="Orange">This is an orange 🍊</TabItem>
<TabItem value="banana" label="Banana">This is a banana 🍌</TabItem>
</Tabs>
</BrowserWindow>
```
---
It is also possible to provide `values` and `defaultValue` props to `Tabs`:
```jsx
<Tabs
defaultValue="apple"
values={[
@ -44,193 +58,196 @@ And you will get the following:
</Tabs>
```
:::info
```mdx-code-block
<BrowserWindow>
<Tabs
defaultValue="apple"
values={[
{label: 'Apple', value: 'apple'},
{label: 'Orange', value: 'orange'},
{label: 'Banana', value: 'banana'},
]}>
<TabItem value="apple">This is an apple 🍎</TabItem>
<TabItem value="orange">This is an orange 🍊</TabItem>
<TabItem value="banana">This is a banana 🍌</TabItem>
</Tabs>
</BrowserWindow>
<br/>
```
By default, tabs are rendered eagerly, but it is possible to load them lazily by passing the `lazy` prop to the `Tabs` component.
<details>
<summary><code>Tabs</code> props take precedence over the <code>TabItem</code> props:</summary>
```jsx
<Tabs
defaultValue="apple"
values={[
{label: 'Apple 1', value: 'apple'},
{label: 'Orange 1', value: 'orange'},
{label: 'Banana 1', value: 'banana'},
]}>
<TabItem value="apple" label="Apple 2">
This is an apple 🍎
</TabItem>
<TabItem value="orange" label="Orange 2">
This is an orange 🍊
</TabItem>
<TabItem value="banana" label="Banana 2" default>
This is a banana 🍌
</TabItem>
</Tabs>
```
```mdx-code-block
<BrowserWindow>
<Tabs
defaultValue="apple"
values={[
{label: 'Apple 1', value: 'apple'},
{label: 'Orange 1', value: 'orange'},
{label: 'Banana 1', value: 'banana'},
]}>
<TabItem value="apple" label="Apple 2">This is an apple 🍎</TabItem>
<TabItem value="orange" label="Orange 2">This is an orange 🍊</TabItem>
<TabItem value="banana" label="Banana 2">This is a banana 🍌</TabItem>
</Tabs>
</BrowserWindow>
<br/>
```
</details>
:::tip
By default, all tabs are rendered eagerly during the build process, and search engines can index hidden tabs.
It is possible to only render the default tab with `<Tabs lazy={true} />`.
:::
## Displaying a default tab
Set the `defaultValue` prop in the `Tabs` component to the label value of your choice to show the matching tab by default.
Add `default` to one of the tab items to make it displayed by default. You can also set the `defaultValue` prop in the `Tabs` component to the label value of your choice.
For example, in the example above, `defaultValue="apple"` forces the `Apple` tab to be open by default.
For example, in the example above, setting `default` for the `value="apple"` tab forces it to be open by default.
If the `defaultValue` prop is not provided or refers to an non-existing value, only the tab headings appear until the user clicks on a tab.
If none of the children contains the `default` prop, neither is the `defaultValue` provided for the `Tabs`, or it refers to an non-existing value, only the tab headings appear until the user clicks on a tab.
## 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` prop. Note that doing this will persist the choice in `localStorage` and all `<Tab>` instances with the same `groupId` will update automatically when the value of one of them is changed. Note that `groupID` are globally-namespaced.
```jsx {2,14}
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]
}>
<TabItem value="win">Use Ctrl + C to copy.</TabItem>
<TabItem value="mac">Use Command + C to copy.</TabItem>
```jsx
// highlight-next-line
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Use Ctrl + C to copy.</TabItem>
<TabItem value="mac" label="MacOS">Use Command + C to copy.</TabItem>
</Tabs>
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]
}>
<TabItem value="win">Use Ctrl + V to paste.</TabItem>
<TabItem value="mac">Use Command + V to paste.</TabItem>
// highlight-next-line
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Use Ctrl + V to paste.</TabItem>
<TabItem value="mac" label="MacOS">Use Command + V to paste.</TabItem>
</Tabs>
```
```mdx-code-block
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]}>
<TabItem value="win">Use Ctrl + C to copy.</TabItem>
<TabItem value="mac">Use Command + C to copy.</TabItem>
</Tabs>
<BrowserWindow>
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Use Ctrl + C to copy.</TabItem>
<TabItem value="mac" label="MacOS">Use Command + C to copy.</TabItem>
</Tabs>
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]}>
<TabItem value="win">Use Ctrl + V to paste.</TabItem>
<TabItem value="mac">Use Command + V to paste.</TabItem>
</Tabs>
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Use Ctrl + V to paste.</TabItem>
<TabItem value="mac" label="MacOS">Use Command + V to paste.</TabItem>
</Tabs>
</BrowserWindow>
<br/>
```
For all tab groups that have the same `groupId`, the possible values do not need to be the same. If one tab group with chooses an value that does not exist in another tab group with the same `groupId`, the tab group with the missing value won't change its tab. You can see that from the following example. Try to select Linux, and the above tab groups doesn't change.
```jsx
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
{label: 'Linux', value: 'linux'},
]}>
<TabItem value="win">I am Windows.</TabItem>
<TabItem value="mac">I am macOS.</TabItem>
<TabItem value="linux">I am Linux.</TabItem>
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>
I am Windows.
</TabItem>
<TabItem value="mac" label="MacOS">
I am macOS.
</TabItem>
<TabItem value="linux" label="Linux">
I am Linux.
</TabItem>
</Tabs>
```
```mdx-code-block
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
{label: 'Linux', value: 'linux'},
]}>
<TabItem value="win">I am Windows.</TabItem>
<TabItem value="mac">I am macOS.</TabItem>
<TabItem value="linux">I am Linux.</TabItem>
</Tabs>
<BrowserWindow>
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>I am Windows.</TabItem>
<TabItem value="mac" label="MacOS">I am macOS.</TabItem>
<TabItem value="linux" label="Linux">I am Linux.</TabItem>
</Tabs>
</BrowserWindow>
```
---
Tab choices with different `groupId`s will not interfere with each other:
```jsx {2,14}
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]
}>
<TabItem value="win">Windows in windows.</TabItem>
<TabItem value="mac">macOS is macOS.</TabItem>
```jsx
// highlight-next-line
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Windows in windows.</TabItem>
<TabItem value="mac" label="MacOS">macOS is macOS.</TabItem>
</Tabs>
<Tabs
groupId="non-mac-operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'Unix', value: 'unix'},
]
}>
<TabItem value="win">Windows is windows.</TabItem>
<TabItem value="unix">Unix is unix.</TabItem>
// highlight-next-line
<Tabs groupId="non-mac-operating-systems">
<TabItem value="win" label="Windows" default>Windows is windows.</TabItem>
<TabItem value="unix" label="Unix">Unix is unix.</TabItem>
</Tabs>
```
```mdx-code-block
<Tabs
groupId="operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'macOS', value: 'mac'},
]}>
<TabItem value="win">Windows in windows.</TabItem>
<TabItem value="mac">macOS is macOS.</TabItem>
</Tabs>
<BrowserWindow>
<Tabs groupId="operating-systems">
<TabItem value="win" label="Windows" default>Windows in windows.</TabItem>
<TabItem value="mac" label="MacOS">macOS is macOS.</TabItem>
</Tabs>
<Tabs
groupId="non-mac-operating-systems"
defaultValue="win"
values={[
{label: 'Windows', value: 'win'},
{label: 'Unix', value: 'unix'},
]}>
<TabItem value="win">Windows is windows.</TabItem>
<TabItem value="unix">Unix is unix.</TabItem>
</Tabs>
<Tabs groupId="non-mac-operating-systems">
<TabItem value="win" label="Windows" default>Windows is windows.</TabItem>
<TabItem value="unix" label="Unix">Unix is unix.</TabItem>
</Tabs>
</BrowserWindow>
```
## Customizing tabs {#customizing-tabs}
You might want to customize the appearance of certain set of tabs. To do that you can pass the string in `className` prop and the specified CSS class will be added to the `Tabs` component:
```jsx {5}
```jsx
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs
className="unique-tabs"
defaultValue="apple"
values={[
{label: 'Apple', value: 'apple'},
{label: 'Orange', value: 'orange'},
{label: 'Banana', value: 'banana'},
]}>
<TabItem value="apple">This is an apple 🍎</TabItem>
<TabItem value="orange">This is an orange 🍊</TabItem>
<TabItem value="banana">This is a banana 🍌</TabItem>
// highlight-next-line
<Tabs className="unique-tabs">
<TabItem value="Apple" default>
This is an apple 🍎
</TabItem>
<TabItem value="Orange">This is an orange 🍊</TabItem>
<TabItem value="Banana">This is a banana 🍌</TabItem>
</Tabs>;
```
```mdx-code-block
<Tabs
className="unique-tabs"
defaultValue="apple"
values={[
{label: 'Apple', value: 'apple'},
{label: 'Orange', value: 'orange'},
{label: 'Banana', value: 'banana'},
]}>
<TabItem value="apple">This is an apple 🍎</TabItem>
<TabItem value="orange">This is an orange 🍊</TabItem>
<TabItem value="banana">This is a banana 🍌</TabItem>
</Tabs>
<BrowserWindow>
<Tabs className="unique-tabs">
<TabItem value="Apple" default>This is an apple 🍎</TabItem>
<TabItem value="Orange">This is an orange 🍊</TabItem>
<TabItem value="Banana">This is a banana 🍌</TabItem>
</Tabs>
</BrowserWindow>
```

View file

@ -9,6 +9,7 @@
border: 3px solid var(--ifm-color-emphasis-200);
border-top-left-radius: var(--ifm-global-radius);
border-top-right-radius: var(--ifm-global-radius);
box-shadow: var(--ifm-global-shadow-lw);
}
.browserWindowHeader {

View file

@ -69,7 +69,6 @@ html[data-theme='dark'] .header-github-link:before {
}
.unique-tabs .tabs__item {
height: 18px;
line-height: 16px;
margin-right: 8px;
}