Skip to content

Commit

Permalink
feat(tabs): refact tabs and add activeKey defaultActiveKey prop (#134)
Browse files Browse the repository at this point in the history
* feat(tabnav): add defaultActiveKey, activeKey, onTabClick prop

affects: @gio-design/components

ISSUES CLOSED: #107

* feat(tabs): refact tabs and add activeKey defaultActiveKey prop

affects: @gio-design/components, website

ISSUES CLOSED: #111
  • Loading branch information
Lee Hon authored Aug 21, 2020
1 parent f370141 commit 24ce9e2
Show file tree
Hide file tree
Showing 10 changed files with 2,283 additions and 5,537 deletions.
1 change: 0 additions & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"rc-menu": "^8.5.1",
"rc-notification": "^3.3.1",
"rc-table": "^7.9.1",
"rc-tabs": "^11.5.2",
"rc-tooltip": "^4.2.1",
"rc-upload": "^3.2.0",
"rc-util": "^5.0.5",
Expand Down
1 change: 0 additions & 1 deletion packages/components/src/components/tabnav/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface TabNavProps {
export interface TabNavItemProps {
prefixCls?: string;
children?: React.ReactNode;
key?: string | number;
className?: string;
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
Expand Down
15 changes: 6 additions & 9 deletions packages/components/src/components/tabs/TabPane.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React from 'react';
import classNames from 'classnames';
import { TabPaneProps } from './interface';
import { TabPane as RcTabPane } from 'rc-tabs';

const TabPane = (props: TabPaneProps) => {
const { icon, tab, children, ...rest } = props;
return (
<RcTabPane tab={icon ? icon : tab} {...rest}>
{children}
</RcTabPane>
);
};
const TabPane = ({ children, prefixCls, style, className }: TabPaneProps) => (
<div className={classNames(`${prefixCls}-tabpane`, className)} style={style}>
{children}
</div>
);

export default TabPane;
82 changes: 70 additions & 12 deletions packages/components/src/components/tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,93 @@
import React, { useContext } from 'react';
import React, { useContext, useMemo, useState } from 'react';
import classNames from 'classnames';
import RcTabs from 'rc-tabs';
import toArray from 'rc-util/lib/Children/toArray';
import { isNil } from 'lodash';
import { ConfigContext } from '../config-provider';
import TabNav from '../tabnav';
import TabPane from './TabPane';
import { TabProps, TabPaneProps } from './interface';

const Tabs = (props: TabProps, ref: React.Ref<HTMLDivElement>) => {
const { type = 'block', size = 'large', children, ...rest } = props;
const {
type = 'block',
size = 'large',
children,
prefixCls: customizePrefixCls,
className,
activeKey,
defaultActiveKey = '',
style,
onTabClick,
onChange,
} = props;
const { getPrefixCls } = useContext(ConfigContext);
const prefixCls = getPrefixCls('tabs');
const classString = classNames({
const [localActiveKey, setLocalActiveKey] = useState<string | number>(activeKey ? activeKey : defaultActiveKey);
const prefixCls = getPrefixCls('tabs', customizePrefixCls);
const classString = classNames(prefixCls, className, {
[`${prefixCls}-${type}`]: true,
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-md`]: size === 'middle',
[`${prefixCls}-lg`]: size === 'large',
});

const getRcTabPane = (children: React.ReactNode) =>
toArray(children).map((node: React.ReactElement<TabPaneProps>) => {
const [tabNav, tabPane] = useMemo(() => {
const _tabItem: JSX.Element[] = [];
const _tabPane = toArray(children).map((node: React.ReactElement<TabPaneProps>) => {
if (React.isValidElement(node) && node.type === TabPane) {
const { icon, tab, ...nodeType } = node.props;
return <TabPane key={node.key as string | number | undefined} tab={icon ? icon : tab} {...nodeType} />;
const { tab, className: paneClassName, disabled, style: paneStyle, ...restProps } = node.props;
_tabItem.push(
<TabNav.Item key={node.key} disabled={disabled}>
{tab}
</TabNav.Item>
);
return (
<TabPane
prefixCls={prefixCls}
className={classNames(paneClassName, {
[`${prefixCls}-tabpane-active`]: localActiveKey === node.key,
})}
key={node.key as string | number | undefined}
style={localActiveKey === node.key ? paneStyle : { ...paneStyle, display: 'none' }}
{...restProps}
/>
);
}
return null;
});
return [_tabItem, _tabPane];
}, [children, localActiveKey]);

const tabNavKeys = useMemo(() => tabNav.map((item) => item.key!), [tabNav]);
useMemo(() => {
if (!tabNavKeys.includes(localActiveKey)) {
setLocalActiveKey(tabNavKeys[0]);
}
}, []);

useMemo(() => {
if (!isNil(activeKey) && tabNavKeys.includes(activeKey)) {
setLocalActiveKey(activeKey);
}
}, [activeKey]);

return (
<RcTabs className={classString} prefixCls={prefixCls} ref={ref} {...rest}>
{getRcTabPane(children)}
</RcTabs>
<div className={classString} ref={ref} style={style}>
<TabNav
size={size}
type={type}
activeKey={localActiveKey}
onTabClick={onTabClick}
onChange={(_key: string | number) => {
if (isNil(activeKey)) {
setLocalActiveKey(_key);
}
onChange?.(_key);
}}
>
{tabNav}
</TabNav>
<div className={`${prefixCls}-content`}>{tabPane}</div>
</div>
);
};

Expand Down
68 changes: 52 additions & 16 deletions packages/components/src/components/tabs/__tests__/Tabs.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import React from 'react';
import Tabs, { TabPane } from '../index';
import '@gio-design/components/es/components/Tabs/style/index.css';
import { act } from 'react-dom/test-utils';
import { mount, render, shallow } from 'enzyme';

async function waitForComponentToPaint(wrapper, amount = 500) {
await act(async () => new Promise((resolve) => setTimeout(resolve, amount)).then(() => wrapper.update()));
}

describe('Testing Tabs', () => {
const getTabs = () => (
<Tabs ref={React.createRef()}>
<TabPane tab="我的" key="1">
<TabPane tab="我的" key="0">
111
</TabPane>
<TabPane tab="全部" key="2">
<TabPane tab="全部" key="1">
222
</TabPane>
<TabPane tab="共享" key="3">
<TabPane tab="共享" key="2">
333
</TabPane>
<TabPane disabled tab="预置" key="4">
<TabPane disabled tab="预置" key="3">
444
</TabPane>
</Tabs>
Expand Down Expand Up @@ -56,18 +51,59 @@ describe('Testing Tabs', () => {
expect(wrapper.exists('.gio-tabs-sm')).toBe(true);
});

test('prop defaultActiveKey', () => {
const wrapper = mount(
<Tabs defaultActiveKey="1">
<TabPane tab="我的" key="0">
111
</TabPane>
<TabPane tab="全部" key="1">
222
</TabPane>
<TabPane tab="共享" key="2">
333
</TabPane>
<TabPane disabled tab="预置" key="3">
444
</TabPane>
</Tabs>
);
expect(wrapper.find('.gio-tabnav').childAt(1).exists('.gio-tabnav-item-active')).toBe(true);
});

test('prop activeKey', () => {
const wrapper = mount(getTabs());
wrapper.setProps({ activeKey: '1' });
expect(wrapper.find('.gio-tabnav').childAt(1).exists('.gio-tabnav-item-active')).toBe(true);
wrapper.setProps({ activeKey: '2' });
expect(wrapper.find('.gio-tabnav').childAt(2).exists('.gio-tabnav-item-active')).toBe(true);
wrapper.find('.gio-tabnav-item').at(1).simulate('click');
expect(wrapper.find('.gio-tabnav').childAt(0).exists('.gio-tabnav-item-active')).toBe(false);
});

test('prop onChange and onTabClick', () => {
const onChange = jest.fn();
const onTabClick = jest.fn();
const wrapper = mount(getTabs());
wrapper.setProps({ onChange, onTabClick });
wrapper.find('.gio-tabnav-item').at(1).simulate('click');
expect(onChange).not.toHaveBeenCalled();
expect(onTabClick).toHaveBeenCalled();
wrapper.find('.gio-tabnav-item').at(2).simulate('click');
expect(onChange).toHaveBeenCalled();
});

it('should be render rightly', () => {
const wrapper = render(getTabs());
expect(wrapper.find('.gio-tabs-tab')).toHaveLength(4);
expect(wrapper.find('.gio-tabs-tab').eq(0).hasClass('gio-tabs-tab-active')).toBe(true);
expect(wrapper.find('.gio-tabs-tab').eq(3).hasClass('gio-tabs-tab-disabled')).toBe(true);
expect(wrapper.find('.gio-tabnav-item')).toHaveLength(4);
expect(wrapper.find('.gio-tabnav-item').eq(0).hasClass('gio-tabnav-item-active')).toBe(true);
expect(wrapper.find('.gio-tabnav-item').eq(3).hasClass('gio-tabnav-item-disabled')).toBe(true);
});

it('should be render content rightly', () => {
const wrapper = mount(getTabs());
waitForComponentToPaint(wrapper);
expect(wrapper.find('.gio-tabs-tabpane-active').text()).toBe('111');
wrapper.find('.gio-tabs-tab').at(1).simulate('click');
expect(wrapper.find('.gio-tabs-tabpane-active').text()).toBe('222');
expect(wrapper.find('.gio-tabs-tabpane-active').at(0).text()).toBe('111');
wrapper.find('.gio-tabnav-item').at(2).simulate('click');
expect(wrapper.find('.gio-tabs-tabpane-active').at(0).text()).toBe('222');
});
});
Loading

0 comments on commit 24ce9e2

Please sign in to comment.