Skip to content
8 changes: 7 additions & 1 deletion src/TabNavList/OperationNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect, useState } from 'react';
import type { EditableConfig, Tab, TabsLocale, MoreProps } from '../interface';
import { getRemovable } from '../util';
import AddButton from './AddButton';
import type { SemanticName } from '../Tabs';

export interface OperationNodeProps {
prefixCls: string;
Expand All @@ -27,6 +28,8 @@ export interface OperationNodeProps {
getPopupContainer?: (node: HTMLElement) => HTMLElement;
popupClassName?: string;
popupStyle?: React.CSSProperties;
styles?: Pick<Partial<Record<SemanticName, React.CSSProperties>>, 'remove'>;
classNames?: Pick<Partial<Record<SemanticName, string>>, 'remove'>;
}

const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((props, ref) => {
Expand All @@ -47,6 +50,8 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
getPopupContainer,
popupClassName,
popupStyle,
classNames,
styles,
} = props;
// ======================== Dropdown ========================
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -98,7 +103,8 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
type="button"
aria-label={removeAriaLabel || 'remove'}
tabIndex={0}
className={`${dropdownPrefix}-menu-item-remove`}
className={clsx(`${dropdownPrefix}-menu-item-remove`, classNames?.remove)}
style={styles?.remove}
onClick={e => {
e.stopPropagation();
onRemoveTab(e, key);
Expand Down
16 changes: 9 additions & 7 deletions src/TabNavList/TabNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { clsx } from 'clsx';
import * as React from 'react';
import type { EditableConfig, Tab } from '../interface';
import { genDataNodeKey, getRemovable } from '../util';
import type { SemanticName } from '@/Tabs';

export interface TabNodeProps {
id: string;
Expand All @@ -23,8 +24,8 @@ export interface TabNodeProps {
onMouseUp: React.MouseEventHandler;
onFocus: React.FocusEventHandler;
onBlur: React.FocusEventHandler;
style?: React.CSSProperties;
className?: string;
styles?: Pick<Partial<Record<SemanticName, React.CSSProperties>>, 'item' | 'remove'>;
classNames?: Pick<Partial<Record<SemanticName, string>>, 'item' | 'remove'>;
}

const TabNode: React.FC<TabNodeProps> = props => {
Expand All @@ -44,8 +45,8 @@ const TabNode: React.FC<TabNodeProps> = props => {
onKeyDown,
onMouseDown,
onMouseUp,
style,
className,
styles,
classNames: tabNodeClassNames,
tabCount,
currentPosition,
} = props;
Expand Down Expand Up @@ -83,13 +84,13 @@ const TabNode: React.FC<TabNodeProps> = props => {
<div
key={key}
data-node-key={genDataNodeKey(key)}
className={clsx(tabPrefix, className, {
className={clsx(tabPrefix, tabNodeClassNames?.item, {
[`${tabPrefix}-with-remove`]: removable,
[`${tabPrefix}-active`]: active,
[`${tabPrefix}-disabled`]: disabled,
[`${tabPrefix}-focus`]: focus,
})}
style={style}
style={styles?.item}
onClick={onInternalClick}
>
{/* Primary Tab Button */}
Expand Down Expand Up @@ -130,7 +131,8 @@ const TabNode: React.FC<TabNodeProps> = props => {
type="button"
aria-label={removeAriaLabel || 'remove'}
tabIndex={active ? 0 : -1}
className={`${tabPrefix}-remove`}
className={clsx(`${tabPrefix}-remove`, tabNodeClassNames?.remove)}
style={styles?.remove}
onClick={e => {
e.stopPropagation();
onRemoveTab(e);
Expand Down
12 changes: 9 additions & 3 deletions src/TabNavList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,15 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
prefixCls={prefixCls}
key={key}
tab={tab}
className={tabsClassNames?.item}
/* first node should not have margin left */
style={i === 0 ? styles?.item : { ...tabNodeStyle, ...styles?.item }}
classNames={{
item: tabsClassNames?.item,
remove: tabsClassNames?.remove,
}}
styles={{
/* first node should not have margin left */
item: i === 0 ? styles?.item : { ...tabNodeStyle, ...styles?.item },
remove: styles?.remove,
}}
closable={tab.closable}
editable={editable}
active={key === activeKey}
Expand Down
2 changes: 1 addition & 1 deletion src/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
// Used for accessibility
let uuid = 0;

export type SemanticName = 'popup' | 'item' | 'indicator' | 'content' | 'header';
export type SemanticName = 'popup' | 'item' | 'indicator' | 'content' | 'header' | 'remove';

export interface TabsProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'children'> {
Expand Down
30 changes: 30 additions & 0 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -775,4 +775,34 @@ describe('Tabs.Basic', () => {
expect(content).toHaveStyle({ background: 'green' });
expect(header).toHaveStyle({ background: 'yellow' });
});

it('support classnames and styles for editable remove button', () => {
const customClassNames = {
remove: 'custom-remove',
};
const customStyles = {
remove: { background: 'red' },
};

const { container } = render(
<div style={{ width: 100 }}>
<Tabs
editable={{
onEdit: () => {},
}}
tabPosition="left"
items={Array.from({ length: 10 }).map((_, index) => ({
key: `test-${index}`,
label: `test-${index}`,
icon: 'test',
}))}
styles={customStyles}
classNames={customClassNames}
/>
</div>,
);

expect(container.querySelector('.rc-tabs-tab-remove')).toHaveClass('custom-remove');
expect(container.querySelector('.rc-tabs-tab-remove')).toHaveStyle({ background: 'red' });
});
});
27 changes: 27 additions & 0 deletions tests/overflow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,33 @@ describe('Tabs.Overflow', () => {
expect(document.querySelector('.rc-tabs-dropdown')).toHaveStyle('color: red');
});

it('should support classnames and styles for editable remove button', () => {
jest.useFakeTimers();
const { container } = render(
getTabs({
editable: { onEdit: () => {} },
classNames: { remove: 'custom-remove' },
styles: { remove: { color: 'red' } },
}),
);

triggerResize(container);
act(() => {
jest.runAllTimers();
});

fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more'));
act(() => {
jest.runAllTimers();
});
expect(document.querySelector('.rc-tabs-dropdown-menu-item-remove')).toHaveClass(
'custom-remove',
);
expect(document.querySelector('.rc-tabs-dropdown-menu-item-remove')).toHaveStyle({
color: 'red',
});
});

it('correct handle decimal', () => {
hackOffsetInfo.container = 29;
hackOffsetInfo.tabNodeList = 29;
Expand Down