((props, ref) => {
const { prefixCls, className, style, id, active, tabKey, children } = props;
+ const hasContent = React.Children.count(children) > 0;
+
return (
= props => {
- const { id, activeKey, animated, tabPosition, destroyInactiveTabPane } = props;
+ const {
+ id,
+ activeKey,
+ animated,
+ tabPosition,
+ destroyInactiveTabPane,
+ contentStyle,
+ contentClassName,
+ } = props;
const { prefixCls, tabs } = React.useContext(TabContext);
const tabPaneAnimated = animated.tabPane;
@@ -54,8 +64,8 @@ const TabPanelList: React.FC = props => {
tabKey={key}
animated={tabPaneAnimated}
active={active}
- style={{ ...paneStyle, ...motionStyle }}
- className={classNames(paneClassName, motionClassName)}
+ style={{ ...contentStyle, ...paneStyle, ...motionStyle }}
+ className={classNames(contentClassName, paneClassName, motionClassName)}
ref={ref}
/>
)}
diff --git a/src/Tabs.tsx b/src/Tabs.tsx
index 7d6df768..144dd3b7 100644
--- a/src/Tabs.tsx
+++ b/src/Tabs.tsx
@@ -34,11 +34,15 @@ import type {
// Used for accessibility
let uuid = 0;
+export type SemanticName = 'popup' | 'item' | 'indicator' | 'content' | 'header';
+
export interface TabsProps
extends Omit, 'onChange' | 'children'> {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
+ classNames?: Partial>;
+ styles?: Partial>;
id?: string;
items?: Tab[];
@@ -99,6 +103,8 @@ const Tabs = React.forwardRef((props, ref) => {
getPopupContainer,
popupClassName,
indicator,
+ classNames: tabsClassNames,
+ styles,
...restProps
} = props;
const tabs = React.useMemo(
@@ -178,10 +184,11 @@ const Tabs = React.forwardRef((props, ref) => {
onTabScroll,
extra: tabBarExtraContent,
style: tabBarStyle,
- panes: null,
getPopupContainer,
- popupClassName,
+ popupClassName: classNames(popupClassName, tabsClassNames?.popup),
indicator,
+ styles,
+ classNames: tabsClassNames,
};
return (
@@ -205,6 +212,8 @@ const Tabs = React.forwardRef((props, ref) => {
diff --git a/src/interface.ts b/src/interface.ts
index ea7958af..6c62e24e 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -1,15 +1,15 @@
-import type { CSSMotionProps } from 'rc-motion';
+import type { CSSMotionProps } from '@rc-component/motion';
+import { DropdownProps } from '@rc-component/dropdown/lib/Dropdown';
import type React from 'react';
import type { TabNavListProps } from './TabNavList';
import type { TabPaneProps } from './TabPanelList/TabPane';
-import { DropdownProps } from 'rc-dropdown/lib/Dropdown';
export type TriggerProps = {
trigger?: 'hover' | 'click';
-}
+};
export type moreIcon = React.ReactNode;
export type MoreProps = {
- icon?: moreIcon,
+ icon?: moreIcon;
} & Omit;
export type SizeInfo = [width: number, height: number];
@@ -45,14 +45,12 @@ type RenderTabBarProps = {
mobile: boolean;
editable: EditableConfig;
locale: TabsLocale;
- more: MoreProps,
+ more: MoreProps;
tabBarGutter: number;
onTabClick: (key: string, e: React.MouseEvent | React.KeyboardEvent) => void;
onTabScroll: OnTabScroll;
extra: TabBarExtraContent;
style: React.CSSProperties;
- /** @deprecated It do not pass real TabPane node. Only for compatible usage. */
- panes: React.ReactNode;
};
export type RenderTabBar = (
diff --git a/tests/accessibility.test.tsx b/tests/accessibility.test.tsx
index 25081cda..809a2409 100644
--- a/tests/accessibility.test.tsx
+++ b/tests/accessibility.test.tsx
@@ -91,9 +91,10 @@ describe('Tabs.Accessibility', () => {
it('should activate tab on Enter/Space', async () => {
const onTabClick = jest.fn();
+ const onChange = jest.fn();
const user = userEvent.setup();
- render(createTabs({ onTabClick }));
+ render(createTabs({ onTabClick, onChange }));
// jump to first tab
await user.tab();
@@ -101,6 +102,7 @@ describe('Tabs.Accessibility', () => {
// activate tab
await user.keyboard(' ');
expect(onTabClick).toHaveBeenCalledTimes(1);
+ expect(onChange).not.toHaveBeenCalled();
// move focus to second tab
await user.keyboard('{ArrowRight}');
@@ -108,6 +110,7 @@ describe('Tabs.Accessibility', () => {
// activate tab
await user.keyboard('{Enter}');
expect(onTabClick).toHaveBeenCalledTimes(2);
+ expect(onChange).not.toHaveBeenCalled();
});
it('should not navigate to disabled tabs', async () => {
@@ -268,4 +271,16 @@ describe('Tabs.Accessibility', () => {
const firstTab = getByRole('tab', { name: /Tab1/i });
expect(firstTab).toHaveFocus();
});
+
+ it('should not focus on tab panel when it is empty', async () => {
+ const user = userEvent.setup();
+ const { getByRole } = render(
+ ,
+ );
+
+ const tabPanel = getByRole('tabpanel', { name: /Tab1/i });
+ await user.tab();
+ await user.tab();
+ expect(tabPanel).not.toHaveFocus();
+ });
});
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
index db5bc7d9..81cb8c99 100644
--- a/tests/index.test.tsx
+++ b/tests/index.test.tsx
@@ -310,23 +310,6 @@ describe('Tabs.Basic', () => {
expect(container.querySelector('.my-node')).toBeTruthy();
expect(renderTabBar).toHaveBeenCalled();
});
- it('has panes property in props', () => {
- const renderTabBar = props => {
- return (
-
- {props.panes.map(pane => (
-
- tab
-
- ))}
-
- );
- };
- const { container } = render(getTabs({ renderTabBar }));
- expect(container.querySelector('[data-key="light"]')).toBeTruthy();
- expect(container.querySelector('[data-key="bamboo"]')).toBeTruthy();
- expect(container.querySelector('[data-key="cute"]')).toBeTruthy();
- });
});
it('destroyInactiveTabPane', () => {
@@ -706,4 +689,40 @@ describe('Tabs.Basic', () => {
expect(parseInt(startBar.style.top)).toBeLessThanOrEqual(parseInt(centerBar.style.top));
expect(parseInt(centerBar.style.top)).toBeLessThanOrEqual(parseInt(endBar.style.top));
});
+ it('support classnames and styles', () => {
+ const customClassNames = {
+ indicator: 'custom-indicator',
+ item: 'custom-item',
+ content: 'custom-content',
+ header: 'custom-header',
+ };
+ const customStyles = {
+ indicator: { background: 'red' },
+ item: { color: 'blue' },
+ content: { background: 'green' },
+ header: { background: 'yellow' },
+ };
+ const { container } = render(
+ ,
+ );
+ const indicator = container.querySelector('.rc-tabs-ink-bar') as HTMLElement;
+ const item = container.querySelector('.rc-tabs-tab') as HTMLElement;
+ const content = container.querySelector('.rc-tabs-tabpane') as HTMLElement;
+ const header = container.querySelector('.rc-tabs-nav') as HTMLElement;
+
+ expect(indicator).toHaveClass('custom-indicator');
+ expect(item).toHaveClass('custom-item');
+ expect(content).toHaveClass('custom-content');
+ expect(header).toHaveClass('custom-header');
+
+ expect(indicator).toHaveStyle({ background: 'red' });
+ expect(item).toHaveStyle({ color: 'blue' });
+ expect(content).toHaveStyle({ background: 'green' });
+ expect(header).toHaveStyle({ background: 'yellow' });
+ });
});
diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx
index 509bbae6..cc8165e4 100644
--- a/tests/overflow.test.tsx
+++ b/tests/overflow.test.tsx
@@ -95,12 +95,14 @@ describe('Tabs.Overflow', () => {
it('should open dropdown on click when moreTrigger is set to click', () => {
jest.useFakeTimers();
const onChange = jest.fn();
- const { container, unmount } = render(getTabs({ onChange, more: {icon: '...', trigger: 'click'} }));
+ const { container, unmount } = render(
+ getTabs({ onChange, more: { icon: '...', trigger: 'click' } }),
+ );
triggerResize(container);
act(() => {
jest.runAllTimers();
});
- const button = container.querySelector('.rc-tabs-nav-more')
+ const button = container.querySelector('.rc-tabs-nav-more');
fireEvent.click(button);
act(() => {
jest.runAllTimers();
@@ -504,7 +506,13 @@ describe('Tabs.Overflow', () => {
it('should support popupClassName', () => {
jest.useFakeTimers();
- const { container } = render(getTabs({ popupClassName: 'custom-popup' }));
+ const { container } = render(
+ getTabs({
+ popupClassName: 'custom-popup',
+ classNames: { popup: 'classnames-popup' },
+ styles: { popup: { color: 'red' } },
+ }),
+ );
triggerResize(container);
act(() => {
@@ -516,6 +524,8 @@ describe('Tabs.Overflow', () => {
jest.runAllTimers();
});
expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('custom-popup');
+ expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('classnames-popup');
+ expect(document.querySelector('.rc-tabs-dropdown')).toHaveStyle('color: red');
});
it('correct handle decimal', () => {
diff --git a/tsconfig.json b/tsconfig.json
index aeeacaa4..7a55add9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,6 +20,9 @@
],
"rc-tabs": [
"src/"
+ ],
+ "@rc-component/tabs": [
+ "src/"
]
}
},