Skip to content

Commit 7b3aacf

Browse files
authored
Merge branch 'master' into patch-1
2 parents 5e68025 + 61e2aec commit 7b3aacf

File tree

8 files changed

+153
-35
lines changed

8 files changed

+153
-35
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rc-tabs",
3-
"version": "12.5.10",
3+
"version": "12.10.0",
44
"description": "tabs ui component for react",
55
"engines": {
66
"node": ">=8.x"
@@ -41,6 +41,7 @@
4141
},
4242
"devDependencies": {
4343
"@rc-component/father-plugin": "^1.0.0",
44+
"@rc-component/trigger": "^1.10.0",
4445
"@testing-library/jest-dom": "^5.16.4",
4546
"@testing-library/react": "^13.0.0",
4647
"@types/classnames": "^2.2.10",
@@ -74,11 +75,11 @@
7475
"dependencies": {
7576
"@babel/runtime": "^7.11.2",
7677
"classnames": "2.x",
77-
"rc-dropdown": "~4.0.0",
78-
"rc-menu": "~9.8.0",
78+
"rc-dropdown": "~4.1.0",
79+
"rc-menu": "~9.11.0",
7980
"rc-motion": "^2.6.2",
8081
"rc-resize-observer": "^1.0.0",
81-
"rc-util": "^5.16.0"
82+
"rc-util": "^5.34.1"
8283
},
8384
"peerDependencies": {
8485
"react": ">=16.9.0",

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/hooks/useVisibleRange.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function useVisibleRange(
5353
}
5454
}
5555

56-
return [startIndex, endIndex];
56+
return startIndex >= endIndex ? [0, 0] : [startIndex, endIndex];
5757
}, [
5858
tabOffsets,
5959
visibleTabContentValue,

src/util.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type React from 'react';
1+
import type { ReactNode, default as React } from 'react';
2+
import type { EditableConfig } from './interface';
23

34
/**
45
* We trade Map as deps which may change with same value but different ref object.
@@ -37,4 +38,25 @@ export function getWheelDeltaOfPx(event: WheelEvent) {
3738
? deltaY * 100 / 3
3839
: deltaY;
3940
return [deltaXOfPx, deltaYOfPx]
40-
}
41+
}
42+
43+
export function getRemovable(
44+
closable?: boolean,
45+
closeIcon?: ReactNode,
46+
editable?: EditableConfig,
47+
disabled?: boolean,
48+
) {
49+
if (
50+
// Only editable tabs can be removed
51+
!editable ||
52+
// Tabs cannot be removed when disabled
53+
disabled ||
54+
// closable is false
55+
closable === false ||
56+
// If closable is undefined, the remove button should be hidden when closeIcon is null or false
57+
(closable === undefined && (closeIcon === false || closeIcon === null))
58+
) {
59+
return false;
60+
}
61+
return true;
62+
}

tests/common/util.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,24 @@ export const triggerResize = (container: Element) => {
158158
onEsResize([{ target } as ResizeObserverEntry]);
159159
});
160160
};
161+
162+
/**
163+
* Wait for a time delay. Will wait `advanceTime * times` ms.
164+
*
165+
* @param advanceTime Default 1000
166+
* @param times Default 20
167+
*/
168+
export async function waitFakeTimer(advanceTime = 1000, times = 20) {
169+
for (let i = 0; i < times; i += 1) {
170+
// eslint-disable-next-line no-await-in-loop
171+
await act(async () => {
172+
await Promise.resolve();
173+
174+
if (advanceTime > 0) {
175+
jest.advanceTimersByTime(advanceTime);
176+
} else {
177+
jest.runAllTimers();
178+
}
179+
});
180+
}
181+
}

tests/index.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,71 @@ 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+
{
486+
key: 'light5',
487+
closable: false,
488+
children: 'Light',
489+
},
490+
] as any,
491+
}),
492+
);
493+
494+
const removes = container.querySelectorAll('.rc-tabs-tab-remove');
495+
expect(removes.length).toBe(2);
496+
expect(container.querySelector('[data-node-key="light1"]').querySelector('.rc-tabs-tab-remove')).toBeFalsy();
497+
expect(container.querySelector('[data-node-key="light2"]').querySelector('.rc-tabs-tab-remove')).toBeFalsy();
498+
expect(container.querySelector('[data-node-key="light3"]').querySelector('.rc-tabs-tab-remove')).toBeTruthy();
499+
expect(container.querySelector('[data-node-key="light4"]').querySelector('.rc-tabs-tab-remove')).toBeTruthy();
500+
expect(container.querySelector('[data-node-key="light5"]').querySelector('.rc-tabs-tab-remove')).toBeFalsy();
501+
502+
});
438503
});
439504

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

tests/overflow.test.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
getTabs,
1212
getTransformX,
1313
getTransformY,
14-
triggerResize,
14+
triggerResize, waitFakeTimer,
1515
} from './common/util';
1616

1717
describe('Tabs.Overflow', () => {
@@ -25,7 +25,7 @@ describe('Tabs.Overflow', () => {
2525
});
2626
});
2727

28-
beforeAll(() => {
28+
beforeEach(() => {
2929
domSpy = spyElementPrototypes(HTMLElement, {
3030
scrollIntoView: () => {},
3131
offsetWidth: {
@@ -43,7 +43,7 @@ describe('Tabs.Overflow', () => {
4343
});
4444
});
4545

46-
afterAll(() => {
46+
afterEach(() => {
4747
domSpy.mockRestore();
4848
});
4949

@@ -363,10 +363,26 @@ describe('Tabs.Overflow', () => {
363363
});
364364
});
365365

366-
it('auto hidden Dropdown', () => {
366+
it('auto hidden Dropdown', async () => {
367367
jest.useFakeTimers();
368368

369-
const originItems: TabsProps['items'] = new Array(8).fill(0).map((_, index) => ({
369+
domSpy = spyElementPrototypes(HTMLElement, {
370+
scrollIntoView: () => {},
371+
offsetWidth: {
372+
get: getOffsetSizeFunc({ ...hackOffsetInfo, container: 45 }),
373+
},
374+
offsetHeight: {
375+
get: getOffsetSizeFunc(hackOffsetInfo),
376+
},
377+
offsetLeft: {
378+
get: btnOffsetPosition,
379+
},
380+
offsetTop: {
381+
get: btnOffsetPosition,
382+
},
383+
});
384+
385+
const originItems: TabsProps['items'] = new Array(2).fill(0).map((_, index) => ({
370386
key: `${index}`,
371387
label: `Tab ${index + 1}`,
372388
children: `Tab Content${index + 1}`,
@@ -406,20 +422,11 @@ describe('Tabs.Overflow', () => {
406422
jest.runAllTimers();
407423
});
408424

409-
while (true) {
410-
const remove = document.querySelector('.rc-tabs-dropdown-menu-item-remove');
411-
if (!remove) {
412-
break;
413-
}
414-
415-
act(() => {
416-
fireEvent.click(remove);
417-
});
425+
const remove = document.querySelector('.rc-tabs-dropdown-menu-item-remove');
418426

419-
act(() => {
420-
jest.runAllTimers();
421-
});
422-
}
427+
act(() => {
428+
fireEvent.click(remove);
429+
});
423430

424431
expect(document.querySelector('.rc-tabs-dropdown-hidden')).toBeTruthy();
425432

0 commit comments

Comments
 (0)