fix(theme): allow tabs children to be falsy (#8801)

This commit is contained in:
Joshua Chen 2023-03-22 19:15:43 +01:00 committed by GitHub
parent c04fab3bfb
commit 3a73fdb53f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 16 deletions

View file

@ -132,12 +132,9 @@ describe('Tabs', () => {
renderer.create(
<TestProviders>
<Tabs
// @ts-expect-error: for an edge-case that we didn't write types for
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}>
{tabs.map((t, idx) => (
// @ts-expect-error: for an edge-case that we didn't write types for
<TabItem key={idx} value={idx}>
{t}
</TabItem>
@ -199,4 +196,19 @@ describe('Tabs', () => {
);
}).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();
});
});

View file

@ -5,11 +5,12 @@
* 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 {
useScrollPositionBlocker,
useTabs,
type TabItemProps,
} from '@docusaurus/theme-common/internal';
import useIsBrowser from '@docusaurus/useIsBrowser';
import type {Props} from '@theme/Tabs';
@ -109,10 +110,11 @@ function TabContent({
children,
selectedValue,
}: Props & ReturnType<typeof useTabs>) {
// eslint-disable-next-line no-param-reassign
children = Array.isArray(children) ? children : [children];
const childTabs = (Array.isArray(children) ? children : [children]).filter(
Boolean,
) as ReactElement<TabItemProps>[];
if (lazy) {
const selectedTabItem = children.find(
const selectedTabItem = childTabs.find(
(tabItem) => tabItem.props.value === selectedValue,
);
if (!selectedTabItem) {
@ -123,7 +125,7 @@ function TabContent({
}
return (
<div className="margin-top--md">
{children.map((tabItem, i) =>
{childTabs.map((tabItem, i) =>
cloneElement(tabItem, {
key: i,
hidden: tabItem.props.value !== selectedValue,

View file

@ -29,12 +29,12 @@ export interface TabValue {
readonly default?: boolean;
}
type TabItem = ReactElement<TabItemProps> | null | false | undefined;
export interface TabsProps {
readonly lazy?: boolean;
readonly block?: boolean;
readonly children:
| readonly ReactElement<TabItemProps>[]
| ReactElement<TabItemProps>;
readonly children: TabItem[] | TabItem;
readonly defaultValue?: string | null;
readonly values?: readonly TabValue[];
readonly groupId?: string;
@ -55,14 +55,16 @@ export interface TabItemProps {
// A very rough duck type, but good enough to guard against mistakes while
// allowing customization
function isTabItem(
comp: ReactElement<object>,
comp: ReactElement<unknown>,
): 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']) {
return React.Children.map(children, (child) => {
if (isValidElement(child) && isTabItem(child)) {
return (React.Children.map(children, (child) => {
// Pass falsy values through: allow conditionally not rendering a tab
if (!child || (isValidElement(child) && isTabItem(child))) {
return child;
}
// 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
}>: 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[] {