Skip to content

Commit dd243be

Browse files
authored
refactor: refactor closeIcon (#665)
* refactor: refactor closeIcon * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code
1 parent 2b08148 commit dd243be

File tree

4 files changed

+93
-11
lines changed

4 files changed

+93
-11
lines changed

src/TabNavList/OperationNode.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as React from 'react';
66
import { useEffect, useState } from 'react';
77
import type { EditableConfig, Tab, TabsLocale } from '../interface';
88
import AddButton from './AddButton';
9+
import { getRemovable } from '../util';
910

1011
export interface OperationNodeProps {
1112
prefixCls: string;
@@ -83,17 +84,18 @@ function OperationNode(
8384
aria-label={dropdownAriaLabel !== undefined ? dropdownAriaLabel : 'expanded dropdown'}
8485
>
8586
{tabs.map(tab => {
86-
const removable = editable && tab.closable !== false && !tab.disabled;
87+
const { closable, disabled, closeIcon, key, label } = tab;
88+
const removable = getRemovable(closable, closeIcon, editable, disabled);
8789
return (
8890
<MenuItem
89-
key={tab.key}
90-
id={`${popupId}-${tab.key}`}
91+
key={key}
92+
id={`${popupId}-${key}`}
9193
role="option"
92-
aria-controls={id && `${id}-panel-${tab.key}`}
93-
disabled={tab.disabled}
94+
aria-controls={id && `${id}-panel-${key}`}
95+
disabled={disabled}
9496
>
9597
{/* {tab.tab} */}
96-
<span>{tab.label}</span>
98+
<span>{label}</span>
9799
{removable && (
98100
<button
99101
type="button"
@@ -102,10 +104,10 @@ function OperationNode(
102104
className={`${dropdownPrefix}-menu-item-remove`}
103105
onClick={e => {
104106
e.stopPropagation();
105-
onRemoveTab(e, tab.key);
107+
onRemoveTab(e, key);
106108
}}
107109
>
108-
{tab.closeIcon || editable.removeIcon || '×'}
110+
{closeIcon || editable.removeIcon || '×'}
109111
</button>
110112
)}
111113
</MenuItem>

src/TabNavList/TabNode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import classNames from 'classnames';
22
import KeyCode from 'rc-util/lib/KeyCode';
33
import * as React from 'react';
44
import type { EditableConfig, Tab } from '../interface';
5-
import { genDataNodeKey } from '../util';
5+
import { genDataNodeKey, getRemovable } from '../util';
66

77
export interface TabNodeProps {
88
id: string;
@@ -35,7 +35,7 @@ function TabNode({
3535
}: TabNodeProps) {
3636
const tabPrefix = `${prefixCls}-tab`;
3737

38-
const removable = editable && closable !== false && !disabled;
38+
const removable = getRemovable(closable, closeIcon, editable, disabled);
3939

4040
function onInternalClick(e: React.MouseEvent | React.KeyboardEvent) {
4141
if (disabled) {

src/util.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import React from 'react';
1+
import type React from 'react';
2+
import type { ReactNode } from 'react';
3+
import type { EditableConfig } from './interface';
24

35
/**
46
* We trade Map as deps which may change with same value but different ref object.
@@ -23,3 +25,22 @@ const RC_TABS_DOUBLE_QUOTE = 'TABS_DQ';
2325
export function genDataNodeKey(key: React.Key): string {
2426
return String(key).replace(/"/g, RC_TABS_DOUBLE_QUOTE);
2527
}
28+
29+
export function getRemovable(
30+
closable?: boolean,
31+
closeIcon?: ReactNode,
32+
editable?: EditableConfig,
33+
disabled?: boolean,
34+
) {
35+
if (
36+
// Only editable tabs can be removed
37+
!editable ||
38+
// Tabs cannot be removed when disabled
39+
disabled ||
40+
// If closable is not explicitly set to true, the remove button should be hidden when closeIcon is null or false
41+
(closable !== true && (closeIcon === false || closeIcon === null))
42+
) {
43+
return false;
44+
}
45+
return true;
46+
}

tests/index.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,65 @@ describe('Tabs.Basic', () => {
435435
container.querySelector('.rc-tabs-tab-remove').querySelector('.close-light'),
436436
).toBeTruthy();
437437
});
438+
it('customize closeIcon', () => {
439+
const onEdit = jest.fn();
440+
const { container } = render(
441+
getTabs({
442+
editable: { onEdit },
443+
items: [
444+
{
445+
key: 'light',
446+
closeIcon: <span className="close-light" />,
447+
children: 'Light',
448+
},
449+
] as any,
450+
}),
451+
);
452+
453+
expect(
454+
container.querySelector('.rc-tabs-tab-remove').querySelector('.close-light'),
455+
).toBeTruthy();
456+
});
457+
it('should hide closeIcon when closeIcon is set to null or false', () => {
458+
const onEdit = jest.fn();
459+
const { container } = render(
460+
getTabs({
461+
editable: { onEdit },
462+
items: [
463+
{
464+
key: 'light1',
465+
closeIcon: null,
466+
children: 'Light',
467+
},
468+
{
469+
key: 'light2',
470+
closeIcon: false,
471+
children: 'Light',
472+
},
473+
{
474+
key: 'light3',
475+
closeIcon: null,
476+
closable: true,
477+
children: 'Light',
478+
},
479+
{
480+
key: 'light4',
481+
closeIcon: false,
482+
closable: true,
483+
children: 'Light',
484+
},
485+
] as any,
486+
}),
487+
);
488+
489+
const removes = container.querySelectorAll('.rc-tabs-tab-remove');
490+
expect(removes.length).toBe(2);
491+
expect(container.querySelector('[data-node-key="light1"]').querySelector('.rc-tabs-tab-remove')).toBeFalsy();
492+
expect(container.querySelector('[data-node-key="light2"]').querySelector('.rc-tabs-tab-remove')).toBeFalsy();
493+
expect(container.querySelector('[data-node-key="light3"]').querySelector('.rc-tabs-tab-remove')).toBeTruthy();
494+
expect(container.querySelector('[data-node-key="light4"]').querySelector('.rc-tabs-tab-remove')).toBeTruthy();
495+
496+
});
438497
});
439498

440499
it('extra', () => {

0 commit comments

Comments
 (0)