diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx index 90f150b6ef1d..58a1a636fdbd 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx @@ -132,12 +132,9 @@ describe('Tabs', () => { renderer.create( ({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 {t} @@ -199,4 +196,19 @@ describe('Tabs', () => { ); }).not.toThrow(); }); + + it('allows a tab to be falsy', () => { + expect(() => { + renderer.create( + + + Val1 + {null} + {false} + {undefined} + + , + ); + }).not.toThrow(); + }); }); diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx index e989d082a53e..7ccb52c9dc08 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx @@ -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) { - // eslint-disable-next-line no-param-reassign - children = Array.isArray(children) ? children : [children]; + const childTabs = (Array.isArray(children) ? children : [children]).filter( + Boolean, + ) as ReactElement[]; if (lazy) { - const selectedTabItem = children.find( + const selectedTabItem = childTabs.find( (tabItem) => tabItem.props.value === selectedValue, ); if (!selectedTabItem) { @@ -123,7 +125,7 @@ function TabContent({ } return (
- {children.map((tabItem, i) => + {childTabs.map((tabItem, i) => cloneElement(tabItem, { key: i, hidden: tabItem.props.value !== selectedValue, diff --git a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx index 64b3d6446ed2..5b974b8af58d 100644 --- a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx @@ -29,12 +29,12 @@ export interface TabValue { readonly default?: boolean; } +type TabItem = ReactElement | null | false | undefined; + export interface TabsProps { readonly lazy?: boolean; readonly block?: boolean; - readonly children: - | readonly ReactElement[] - | ReactElement; + 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, + comp: ReactElement, ): comp is ReactElement { - 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 component should be , and every should have a unique "value" prop.`, ); - }); + })?.filter(Boolean) ?? []) as ReactElement[]; } function extractChildrenTabValues(children: TabsProps['children']): TabValue[] {