Skip to content

Commit ca9a372

Browse files
authored
feat: tab support classnames and styles (#784)
* feat: tab support classnames and styles * fix: test * fix * fix * fix
1 parent ffde754 commit ca9a372

File tree

6 files changed

+65
-10
lines changed

6 files changed

+65
-10
lines changed

src/TabNavList/OperationNode.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface OperationNodeProps {
2626
tabMoving?: boolean;
2727
getPopupContainer?: (node: HTMLElement) => HTMLElement;
2828
popupClassName?: string;
29+
popupStyle?: React.CSSProperties;
2930
}
3031

3132
const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((props, ref) => {
@@ -45,6 +46,7 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
4546
onTabClick,
4647
getPopupContainer,
4748
popupClassName,
49+
popupStyle,
4850
} = props;
4951
// ======================== Dropdown ========================
5052
const [open, setOpen] = useState(false);
@@ -182,7 +184,7 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
182184
moreStyle.order = 1;
183185
}
184186

185-
const overlayClassName = classNames({
187+
const overlayClassName = classNames(popupClassName, {
186188
[`${dropdownPrefix}-rtl`]: rtl,
187189
});
188190

@@ -192,7 +194,8 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
192194
overlay={menu}
193195
visible={tabs.length ? open : false}
194196
onVisibleChange={setOpen}
195-
overlayClassName={classNames(overlayClassName, popupClassName)}
197+
overlayClassName={overlayClassName}
198+
overlayStyle={popupStyle}
196199
mouseEnterDelay={0.1}
197200
mouseLeaveDelay={0.1}
198201
getPopupContainer={getPopupContainer}

src/TabNavList/TabNode.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface TabNodeProps {
2424
onFocus: React.FocusEventHandler;
2525
onBlur: React.FocusEventHandler;
2626
style?: React.CSSProperties;
27+
className?: string;
2728
}
2829

2930
const TabNode: React.FC<TabNodeProps> = props => {
@@ -44,6 +45,7 @@ const TabNode: React.FC<TabNodeProps> = props => {
4445
onMouseDown,
4546
onMouseUp,
4647
style,
48+
className,
4749
tabCount,
4850
currentPosition,
4951
} = props;
@@ -81,7 +83,7 @@ const TabNode: React.FC<TabNodeProps> = props => {
8183
<div
8284
key={key}
8385
data-node-key={genDataNodeKey(key)}
84-
className={classNames(tabPrefix, {
86+
className={classNames(tabPrefix, className, {
8587
[`${tabPrefix}-with-remove`]: removable,
8688
[`${tabPrefix}-active`]: active,
8789
[`${tabPrefix}-disabled`]: disabled,

src/TabNavList/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import AddButton from './AddButton';
3030
import ExtraContent from './ExtraContent';
3131
import OperationNode from './OperationNode';
3232
import TabNode from './TabNode';
33+
import type { SemanticName } from '../Tabs';
3334

3435
export interface TabNavListProps {
3536
id: string;
@@ -55,6 +56,8 @@ export interface TabNavListProps {
5556
size?: GetIndicatorSize;
5657
align?: 'start' | 'center' | 'end';
5758
};
59+
classNames?: Partial<Record<SemanticName, string>>;
60+
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
5861
}
5962

6063
const getTabSize = (tab: HTMLElement, containerRect: { left: number; top: number }) => {
@@ -109,6 +112,8 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
109112
onTabClick,
110113
onTabScroll,
111114
indicator,
115+
classNames: tabsClassNames,
116+
styles,
112117
} = props;
113118

114119
const { prefixCls, tabs } = React.useContext(TabContext);
@@ -417,8 +422,9 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
417422
prefixCls={prefixCls}
418423
key={key}
419424
tab={tab}
425+
className={tabsClassNames?.item}
420426
/* first node should not have margin left */
421-
style={i === 0 ? undefined : tabNodeStyle}
427+
style={i === 0 ? styles?.item : { ...tabNodeStyle, ...styles?.item }}
422428
closable={tab.closable}
423429
editable={editable}
424430
active={key === activeKey}
@@ -607,10 +613,10 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
607613
}}
608614
/>
609615
<div
610-
className={classNames(`${prefixCls}-ink-bar`, {
616+
className={classNames(`${prefixCls}-ink-bar`, tabsClassNames?.indicator, {
611617
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
612618
})}
613-
style={indicatorStyle}
619+
style={{ ...indicatorStyle, ...styles?.indicator }}
614620
/>
615621
</div>
616622
</ResizeObserver>
@@ -624,6 +630,7 @@ const TabNavList = React.forwardRef<HTMLDivElement, TabNavListProps>((props, ref
624630
prefixCls={prefixCls}
625631
tabs={hiddenTabs}
626632
className={!hasDropdown && operationsHiddenClassName}
633+
popupStyle={styles?.popup}
627634
tabMoving={!!lockAnimation}
628635
/>
629636

src/Tabs.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ import type {
3434
// Used for accessibility
3535
let uuid = 0;
3636

37+
export type SemanticName = 'popup' | 'item' | 'indicator';
38+
3739
export interface TabsProps
3840
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'children'> {
3941
prefixCls?: string;
4042
className?: string;
4143
style?: React.CSSProperties;
44+
classNames?: Partial<Record<SemanticName, string>>;
45+
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
4246
id?: string;
4347

4448
items?: Tab[];
@@ -99,6 +103,8 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
99103
getPopupContainer,
100104
popupClassName,
101105
indicator,
106+
classNames: tabsClassNames,
107+
styles,
102108
...restProps
103109
} = props;
104110
const tabs = React.useMemo<Tab[]>(
@@ -179,7 +185,9 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
179185
extra: tabBarExtraContent,
180186
style: tabBarStyle,
181187
getPopupContainer,
182-
popupClassName,
188+
popupClassName: classNames(popupClassName, tabsClassNames?.popup),
189+
styles,
190+
classNames: tabsClassNames,
183191
indicator,
184192
};
185193

tests/index.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,4 +689,29 @@ describe('Tabs.Basic', () => {
689689
expect(parseInt(startBar.style.top)).toBeLessThanOrEqual(parseInt(centerBar.style.top));
690690
expect(parseInt(centerBar.style.top)).toBeLessThanOrEqual(parseInt(endBar.style.top));
691691
});
692+
it('support classnames and styles', () => {
693+
const customClassNames = {
694+
indicator: 'custom-indicator',
695+
item: 'custom-item',
696+
};
697+
const customStyles = {
698+
indicator: { background: 'red' },
699+
item: { color: 'blue' },
700+
};
701+
const { container } = render(
702+
<Tabs
703+
tabPosition="left"
704+
items={[{ key: 'test', label: 'test', icon: 'test' }]}
705+
styles={customStyles}
706+
classNames={customClassNames}
707+
/>,
708+
);
709+
const indicator = container.querySelector('.rc-tabs-ink-bar') as HTMLElement;
710+
const item = container.querySelector('.rc-tabs-tab') as HTMLElement;
711+
712+
expect(indicator).toHaveClass('custom-indicator');
713+
expect(item).toHaveClass('custom-item');
714+
expect(indicator).toHaveStyle({ background: 'red' });
715+
expect(item).toHaveStyle({ color: 'blue' });
716+
});
692717
});

tests/overflow.test.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,14 @@ describe('Tabs.Overflow', () => {
9595
it('should open dropdown on click when moreTrigger is set to click', () => {
9696
jest.useFakeTimers();
9797
const onChange = jest.fn();
98-
const { container, unmount } = render(getTabs({ onChange, more: {icon: '...', trigger: 'click'} }));
98+
const { container, unmount } = render(
99+
getTabs({ onChange, more: { icon: '...', trigger: 'click' } }),
100+
);
99101
triggerResize(container);
100102
act(() => {
101103
jest.runAllTimers();
102104
});
103-
const button = container.querySelector('.rc-tabs-nav-more')
105+
const button = container.querySelector('.rc-tabs-nav-more');
104106
fireEvent.click(button);
105107
act(() => {
106108
jest.runAllTimers();
@@ -504,7 +506,13 @@ describe('Tabs.Overflow', () => {
504506

505507
it('should support popupClassName', () => {
506508
jest.useFakeTimers();
507-
const { container } = render(getTabs({ popupClassName: 'custom-popup' }));
509+
const { container } = render(
510+
getTabs({
511+
popupClassName: 'custom-popup',
512+
classNames: { popup: 'classnames-popup' },
513+
styles: { popup: { color: 'red' } },
514+
}),
515+
);
508516

509517
triggerResize(container);
510518
act(() => {
@@ -516,6 +524,8 @@ describe('Tabs.Overflow', () => {
516524
jest.runAllTimers();
517525
});
518526
expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('custom-popup');
527+
expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('classnames-popup');
528+
expect(document.querySelector('.rc-tabs-dropdown')).toHaveStyle('color: red');
519529
});
520530

521531
it('correct handle decimal', () => {

0 commit comments

Comments
 (0)