diff --git a/package.json b/package.json index 6b177ec3..0e5f35a3 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@rc-component/menu": "~1.6.0", "@rc-component/motion": "^1.1.3", "@rc-component/resize-observer": "^1.0.0", - "@rc-component/util": "^1.11.1", + "@rc-component/util": "^1.13.0", "clsx": "^2.1.1" }, "devDependencies": { diff --git a/src/TabNavList/AddButton.tsx b/src/TabNavList/AddButton.tsx index 6b6712f9..18ec0358 100644 --- a/src/TabNavList/AddButton.tsx +++ b/src/TabNavList/AddButton.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { isReactRenderable } from '@rc-component/util'; import type { EditableConfig, TabsLocale } from '../interface'; export interface AddButtonProps { @@ -25,7 +26,7 @@ const AddButton = React.forwardRef((props, re editable.onEdit('add', { event }); }} > - {editable.addIcon || '+'} + {isReactRenderable(editable.addIcon) ? editable.addIcon : '+'} ); }); diff --git a/src/TabNavList/ExtraContent.tsx b/src/TabNavList/ExtraContent.tsx index 0da8cadf..a28acd0d 100644 --- a/src/TabNavList/ExtraContent.tsx +++ b/src/TabNavList/ExtraContent.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { isReactRenderable } from '@rc-component/util'; import type { TabBarExtraContent, TabBarExtraMap, TabBarExtraPosition } from '../interface'; interface ExtraContentProps { @@ -9,7 +10,7 @@ interface ExtraContentProps { const ExtraContent = React.forwardRef((props, ref) => { const { position, prefixCls, extra } = props; - if (!extra) { + if (!isReactRenderable(extra)) { return null; } @@ -31,7 +32,7 @@ const ExtraContent = React.forwardRef((props, content = assertExtra.left; } - return content ? ( + return isReactRenderable(content) ? (
{content}
diff --git a/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index 59a33952..c8edb661 100644 --- a/src/TabNavList/OperationNode.tsx +++ b/src/TabNavList/OperationNode.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx'; import Dropdown from '@rc-component/dropdown'; import Menu, { MenuItem } from '@rc-component/menu'; -import { KeyCode } from '@rc-component/util'; +import { isReactRenderable, KeyCode } from '@rc-component/util'; import * as React from 'react'; import { useEffect, useState } from 'react'; import type { EditableConfig, Tab, TabsLocale, MoreProps } from '../interface'; @@ -110,7 +110,11 @@ const OperationNode = React.forwardRef((prop onRemoveTab(e, key); }} > - {closeIcon || editable.removeIcon || '×'} + {isReactRenderable(closeIcon) + ? closeIcon + : isReactRenderable(editable.removeIcon) + ? editable.removeIcon + : '×'} )} diff --git a/src/TabNavList/TabNode.tsx b/src/TabNavList/TabNode.tsx index 761d7aa6..d5a0dce6 100644 --- a/src/TabNavList/TabNode.tsx +++ b/src/TabNavList/TabNode.tsx @@ -1,4 +1,5 @@ import { clsx } from 'clsx'; +import { isReactRenderable } from '@rc-component/util'; import * as React from 'react'; import type { EditableConfig, Tab } from '../interface'; import { genDataNodeKey, getRemovable } from '../util'; @@ -68,7 +69,7 @@ const TabNode: React.FC = props => { } const labelNode = React.useMemo( - () => (icon && typeof label === 'string' ? {label} : label), + () => (isReactRenderable(icon) && typeof label === 'string' ? {label} : label), [label, icon], ); @@ -121,8 +122,8 @@ const TabNode: React.FC = props => { {`Tab ${currentPosition} of ${tabCount}`} )} - {icon && {icon}} - {label && labelNode} + {isReactRenderable(icon) && {icon}} + {isReactRenderable(label) && labelNode} {/* Remove Button */} @@ -138,7 +139,11 @@ const TabNode: React.FC = props => { onRemoveTab(e); }} > - {closeIcon || editable.removeIcon || '×'} + {isReactRenderable(closeIcon) + ? closeIcon + : isReactRenderable(editable.removeIcon) + ? editable.removeIcon + : '×'} )} diff --git a/tests/index.test.tsx b/tests/index.test.tsx index e4576ee1..2d5029e6 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -464,6 +464,56 @@ describe('Tabs.Basic', () => { container.querySelector('.rc-tabs-tab-remove').querySelector('.close-light'), ).toBeTruthy(); }); + + it('should render numeric close and remove icons', () => { + const onEdit = jest.fn(); + const { container } = render( + getTabs({ + editable: { onEdit, removeIcon: 0 }, + items: [ + { + key: 'close-icon', + label: 'Close icon', + closeIcon: 0, + children: 'Close icon', + }, + { + key: 'remove-icon', + label: 'Remove icon', + children: 'Remove icon', + }, + ], + }), + ); + + const removes = container.querySelectorAll('.rc-tabs-tab-remove'); + expect(removes).toHaveLength(2); + expect(removes[0]).toHaveTextContent('0'); + expect(removes[1]).toHaveTextContent('0'); + }); + + it.each([false, '', null, undefined])( + 'should fall back for a non-renderable remove icon: %p', + removeIcon => { + const onEdit = jest.fn(); + const { container } = render( + getTabs({ + editable: { onEdit, removeIcon }, + items: [ + { + key: 'fallback-icon', + label: 'Fallback icon', + closable: true, + children: 'Fallback icon', + }, + ], + }), + ); + + expect(container.querySelector('.rc-tabs-tab-remove')).toHaveTextContent('×'); + }, + ); + it('should hide closeIcon when closeIcon is set to null or false', () => { const onEdit = jest.fn(); const { container } = render( diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index 03a70ad6..0e8b57ab 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -398,6 +398,54 @@ describe('Tabs.Overflow', () => { }); }); + it('renders numeric remove icons in the dropdown menu', () => { + jest.useFakeTimers(); + try { + hackOffsetInfo.container = 20; + + const onEdit = jest.fn(); + const { container, unmount } = render( + getTabs({ + editable: { onEdit, removeIcon: 0 }, + items: [ + { + key: 'close-icon', + label: 'Close icon', + closeIcon: 0, + children: 'Close icon', + }, + { + key: 'remove-icon', + label: 'Remove icon', + closeIcon: '', + closable: true, + children: 'Remove icon', + }, + ], + }), + ); + + triggerResize(container); + act(() => { + jest.runAllTimers(); + }); + + fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); + act(() => { + jest.runAllTimers(); + }); + + const removes = document.querySelectorAll('.rc-tabs-dropdown-menu-item-remove'); + expect(removes).toHaveLength(2); + expect(removes[0]).toHaveTextContent('0'); + expect(removes[1]).toHaveTextContent('0'); + + unmount(); + } finally { + jest.useRealTimers(); + } + }); + it('auto hidden Dropdown', async () => { jest.useFakeTimers();