mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-01 16:00:29 +02:00
fix(theme): allow tabs children to be falsy (#8801)
This commit is contained in:
parent
c04fab3bfb
commit
3a73fdb53f
3 changed files with 32 additions and 16 deletions
|
@ -132,12 +132,9 @@ describe('Tabs', () => {
|
||||||
renderer.create(
|
renderer.create(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<Tabs
|
<Tabs
|
||||||
// @ts-expect-error: for an edge-case that we didn't write types for
|
|
||||||
values={tabs.map((t, idx) => ({label: t, value: idx}))}
|
values={tabs.map((t, idx) => ({label: t, value: idx}))}
|
||||||
// @ts-expect-error: for an edge-case that we didn't write types for
|
|
||||||
defaultValue={0}>
|
defaultValue={0}>
|
||||||
{tabs.map((t, idx) => (
|
{tabs.map((t, idx) => (
|
||||||
// @ts-expect-error: for an edge-case that we didn't write types for
|
|
||||||
<TabItem key={idx} value={idx}>
|
<TabItem key={idx} value={idx}>
|
||||||
{t}
|
{t}
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
@ -199,4 +196,19 @@ describe('Tabs', () => {
|
||||||
);
|
);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows a tab to be falsy', () => {
|
||||||
|
expect(() => {
|
||||||
|
renderer.create(
|
||||||
|
<TestProviders>
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="val1">Val1</TabItem>
|
||||||
|
{null}
|
||||||
|
{false}
|
||||||
|
{undefined}
|
||||||
|
</Tabs>
|
||||||
|
</TestProviders>,
|
||||||
|
);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {cloneElement} from 'react';
|
import React, {cloneElement, type ReactElement} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {
|
||||||
useScrollPositionBlocker,
|
useScrollPositionBlocker,
|
||||||
useTabs,
|
useTabs,
|
||||||
|
type TabItemProps,
|
||||||
} from '@docusaurus/theme-common/internal';
|
} from '@docusaurus/theme-common/internal';
|
||||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||||
import type {Props} from '@theme/Tabs';
|
import type {Props} from '@theme/Tabs';
|
||||||
|
@ -109,10 +110,11 @@ function TabContent({
|
||||||
children,
|
children,
|
||||||
selectedValue,
|
selectedValue,
|
||||||
}: Props & ReturnType<typeof useTabs>) {
|
}: Props & ReturnType<typeof useTabs>) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
const childTabs = (Array.isArray(children) ? children : [children]).filter(
|
||||||
children = Array.isArray(children) ? children : [children];
|
Boolean,
|
||||||
|
) as ReactElement<TabItemProps>[];
|
||||||
if (lazy) {
|
if (lazy) {
|
||||||
const selectedTabItem = children.find(
|
const selectedTabItem = childTabs.find(
|
||||||
(tabItem) => tabItem.props.value === selectedValue,
|
(tabItem) => tabItem.props.value === selectedValue,
|
||||||
);
|
);
|
||||||
if (!selectedTabItem) {
|
if (!selectedTabItem) {
|
||||||
|
@ -123,7 +125,7 @@ function TabContent({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="margin-top--md">
|
<div className="margin-top--md">
|
||||||
{children.map((tabItem, i) =>
|
{childTabs.map((tabItem, i) =>
|
||||||
cloneElement(tabItem, {
|
cloneElement(tabItem, {
|
||||||
key: i,
|
key: i,
|
||||||
hidden: tabItem.props.value !== selectedValue,
|
hidden: tabItem.props.value !== selectedValue,
|
||||||
|
|
|
@ -29,12 +29,12 @@ export interface TabValue {
|
||||||
readonly default?: boolean;
|
readonly default?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TabItem = ReactElement<TabItemProps> | null | false | undefined;
|
||||||
|
|
||||||
export interface TabsProps {
|
export interface TabsProps {
|
||||||
readonly lazy?: boolean;
|
readonly lazy?: boolean;
|
||||||
readonly block?: boolean;
|
readonly block?: boolean;
|
||||||
readonly children:
|
readonly children: TabItem[] | TabItem;
|
||||||
| readonly ReactElement<TabItemProps>[]
|
|
||||||
| ReactElement<TabItemProps>;
|
|
||||||
readonly defaultValue?: string | null;
|
readonly defaultValue?: string | null;
|
||||||
readonly values?: readonly TabValue[];
|
readonly values?: readonly TabValue[];
|
||||||
readonly groupId?: string;
|
readonly groupId?: string;
|
||||||
|
@ -55,14 +55,16 @@ export interface TabItemProps {
|
||||||
// A very rough duck type, but good enough to guard against mistakes while
|
// A very rough duck type, but good enough to guard against mistakes while
|
||||||
// allowing customization
|
// allowing customization
|
||||||
function isTabItem(
|
function isTabItem(
|
||||||
comp: ReactElement<object>,
|
comp: ReactElement<unknown>,
|
||||||
): comp is ReactElement<TabItemProps> {
|
): comp is ReactElement<TabItemProps> {
|
||||||
return 'value' in comp.props;
|
const {props} = comp;
|
||||||
|
return !!props && typeof props === 'object' && 'value' in props;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureValidChildren(children: TabsProps['children']) {
|
function ensureValidChildren(children: TabsProps['children']) {
|
||||||
return React.Children.map(children, (child) => {
|
return (React.Children.map(children, (child) => {
|
||||||
if (isValidElement(child) && isTabItem(child)) {
|
// Pass falsy values through: allow conditionally not rendering a tab
|
||||||
|
if (!child || (isValidElement(child) && isTabItem(child))) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
// child.type.name will give non-sensical values in prod because of
|
// child.type.name will give non-sensical values in prod because of
|
||||||
|
@ -73,7 +75,7 @@ function ensureValidChildren(children: TabsProps['children']) {
|
||||||
typeof child.type === 'string' ? child.type : child.type.name
|
typeof child.type === 'string' ? child.type : child.type.name
|
||||||
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`,
|
||||||
);
|
);
|
||||||
});
|
})?.filter(Boolean) ?? []) as ReactElement<TabItemProps>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractChildrenTabValues(children: TabsProps['children']): TabValue[] {
|
function extractChildrenTabValues(children: TabsProps['children']): TabValue[] {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue