feat: Tabs render tab bar (#11856)

* feat: Customized bar of tab.

* docs: 更新关于renderTabBar的中文文档

* docs: 更新关于renderTabBar的英文文档

* update: 优化代码

* docs: 更新关于renderTabBar的文档

* update: 完善renderTabBar代码

* update: 完成自定义tabBar

* docs: 去掉旧的DefaultTabBar参数说明

* update: 修复CI测试

* fix: 去掉>选择器,解决自定义tabBar后样式错误

* update: 优化代码质量

* update: 添加测试

* fix: lint

* fix: 避免tab嵌套bug

* update: 把DefaultTabBar放在renderTabBar里
pull/11934/head
mushan0x0 7 years ago committed by 偏右
parent d47a5be712
commit 62d68b049f

@ -674,7 +674,7 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large ant-tabs-line"
>
<div
class="ant-tabs-bar"
class="ant-tabs-bar ant-card-head-tabs"
role="tablist"
tabindex="0"
>
@ -809,7 +809,7 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
class="ant-tabs ant-tabs-top ant-card-head-tabs ant-tabs-large ant-tabs-line"
>
<div
class="ant-tabs-bar"
class="ant-tabs-bar ant-card-head-tabs"
role="tablist"
tabindex="0"
>

@ -0,0 +1,51 @@
import * as React from 'react';
import Icon from '../icon';
import ScrollableInkTabBar from 'rc-tabs/lib/ScrollableInkTabBar';
import { TabsProps } from './index';
export default class TabBar extends React.Component<TabsProps> {
render() {
const {
tabBarStyle,
animated = true,
renderTabBar,
tabBarExtraContent,
tabPosition,
prefixCls,
} = this.props;
const inkBarAnimated = typeof animated === 'object' ? animated.inkBar : animated;
const isVertical = tabPosition === 'left' || tabPosition === 'right';
const prevIconType = isVertical ? 'up' : 'left';
const nextIconType = isVertical ? 'down' : 'right';
const prevIcon = (
<span className={`${prefixCls}-tab-prev-icon`}>
<Icon type={prevIconType} className={`${prefixCls}-tab-prev-icon-target`} />
</span>
);
const nextIcon = (
<span className={`${prefixCls}-tab-next-icon`}>
<Icon type={nextIconType} className={`${prefixCls}-tab-next-icon-target`} />
</span>
);
const renderProps = {
...this.props,
inkBarAnimated,
extraContent: tabBarExtraContent,
style: tabBarStyle,
prevIcon,
nextIcon,
};
let RenderTabBar: React.ReactElement<any>;
if (renderTabBar) {
RenderTabBar = renderTabBar(renderProps, ScrollableInkTabBar);
} else {
RenderTabBar = <ScrollableInkTabBar {...renderProps} />;
}
return React.cloneElement(RenderTabBar);
}
}

@ -576,6 +576,145 @@ exports[`renders ./components/tabs/demo/custom-add-trigger.md correctly 1`] = `
</div>
`;
exports[`renders ./components/tabs/demo/custom-tab-bar.md correctly 1`] = `
<div>
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
>
<div>
<div />
<div
class="ant-tabs-bar"
role="tablist"
style="z-index:1;background:#fff"
tabindex="0"
>
<div
class="ant-tabs-nav-container"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<i
class="anticon anticon-left ant-tabs-tab-prev-icon-target"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M724.4 895.7c-9.6 0-19.3-3.2-27.4-9.6L273.2 545.2c-10.3-8.3-16.3-20.8-16.3-34.1 0-13.2 6-25.8 16.3-34l422-339.1c18.8-15.1 46.3-12.1 61.4 6.7 15.1 18.8 12.1 46.3-6.7 61.4l-379.6 305L751.8 818c18.8 15.1 21.8 42.6 6.7 61.4-8.7 10.7-21.3 16.3-34.1 16.3z"
/>
</svg>
</i>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<i
class="anticon anticon-right ant-tabs-tab-next-icon-target"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M300.6 895.7c9.6 0 19.3-3.2 27.4-9.6l423.8-340.9c10.3-8.3 16.3-20.8 16.3-34.1 0-13.2-6-25.8-16.3-34L329.8 138c-18.8-15.1-46.3-12.1-61.4 6.7-15.1 18.8-12.1 46.3 6.7 61.4l379.6 305.1L273.2 818c-18.8 15.1-21.8 42.6-6.7 61.4 8.7 10.7 21.3 16.3 34.1 16.3z"
/>
</svg>
</i>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-scroll"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
role="tab"
>
Tab 1
</div>
<div
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
role="tab"
>
Tab 2
</div>
<div
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
role="tab"
>
Tab 3
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-tabs-content ant-tabs-content-animated"
style="margin-left:0%"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
style="height:200px"
>
Content of Tab Pane 1
</div>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
/>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
/>
</div>
</div>
</div>
`;
exports[`renders ./components/tabs/demo/disabled.md correctly 1`] = `
<div
class="ant-tabs ant-tabs-top ant-tabs-line"

@ -1,5 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tabs renderTabBar custom-tab-bar 1`] = `
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
>
<div>
custom-tab-bar
</div>
<div
class="ant-tabs-content ant-tabs-content-animated"
style="margin-left:0%"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
>
foo
</div>
</div>
</div>
`;
exports[`Tabs tabPosition remove card 1`] = `
<div
class="ant-tabs ant-tabs-left ant-tabs-vertical ant-tabs-line"

@ -39,4 +39,15 @@ describe('Tabs', () => {
expect(wrapper).toMatchSnapshot();
});
});
describe('renderTabBar', () => {
it('custom-tab-bar', () => {
const wrapper = render(
<Tabs renderTabBar={() => <div>custom-tab-bar</div>}>
<TabPane tab="foo" key="1">foo</TabPane>
</Tabs>
);
expect(wrapper).toMatchSnapshot();
});
});
});

@ -0,0 +1,39 @@
---
order: 12
title:
zh-CN: 自定义页签头
en-US: Customized bar of tab
---
## zh-CN
使用 react-sticky 组件实现吸顶效果。
## en-US
use react-sticky.
````jsx
import { Tabs } from 'antd';
import { StickyContainer, Sticky } from 'react-sticky';
const TabPane = Tabs.TabPane;
const renderTabBar = (props, DefaultTabBar) => (
<Sticky bottomOffset={80}>
{({ style }) => (
<DefaultTabBar {...props} style={{ ...style, zIndex: 1, background: '#fff' }} />
)}
</Sticky>
);
ReactDOM.render(
<StickyContainer>
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar}>
<TabPane tab="Tab 1" key="1" style={{ height: 200 }}>Content of Tab Pane 1</TabPane>
<TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
<TabPane tab="Tab 3" key="3">Content of Tab Pane 3</TabPane>
</Tabs>
</StickyContainer>,
mountNode);
````

@ -23,6 +23,7 @@ Ant Design has 3 types of Tabs for different situations.
| -------- | ----------- | ---- | ------- |
| activeKey | Current TabPane's key | string | - |
| animated | Whether to change tabs with animation. Only works while `tabPosition="top"\|"bottom"` | boolean \| {inkBar:boolean, tabPane:boolean} | `true`, `false` when `type="card"` |
| renderTabBar | replace the TabBar | (props: DefaultTabBarProps, DefaultTabBar: React.ReactNode) => React.ReactNode | - |
| defaultActiveKey | Initial active TabPane's key, if `activeKey` is not set. | string | - |
| hideAdd | Hide plus icon or not. Only works while `type="editable-card"` | boolean | `false` |
| size | preset tab bar size | `large` \| `default` \| `small` | `default` |

@ -1,8 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import RcTabs, { TabPane } from 'rc-tabs';
import ScrollableInkTabBar from 'rc-tabs/lib/ScrollableInkTabBar';
import TabContent from 'rc-tabs/lib/TabContent';
import TabBar from './TabBar';
import classNames from 'classnames';
import Icon from '../icon';
import warning from '../_util/warning';
@ -30,6 +30,7 @@ export interface TabsProps {
className?: string;
animated?: boolean | { inkBar: boolean; tabPane: boolean; };
tabBarGutter?: number;
renderTabBar?: (props: TabsProps, DefaultTabBar: React.ReactNode) => React.ReactElement<any>;
}
// Tabs
@ -51,13 +52,6 @@ export default class Tabs extends React.Component<TabsProps, any> {
hideAdd: false,
};
createNewTab = (targetKey: React.MouseEvent<HTMLElement>) => {
const onEdit = this.props.onEdit;
if (onEdit) {
onEdit(targetKey, 'add');
}
}
removeTab = (targetKey: string, e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
if (!targetKey) {
@ -77,6 +71,13 @@ export default class Tabs extends React.Component<TabsProps, any> {
}
}
createNewTab = (targetKey: React.MouseEvent<HTMLElement>) => {
const { onEdit } = this.props;
if (onEdit) {
onEdit(targetKey, 'add');
}
}
componentDidMount() {
const NO_FLEX = ' no-flex';
const tabNode = ReactDOM.findDOMNode(this) as Element;
@ -93,21 +94,12 @@ export default class Tabs extends React.Component<TabsProps, any> {
type = 'line',
tabPosition,
children,
animated = true,
tabBarExtraContent,
tabBarStyle,
hideAdd,
onTabClick,
onPrevClick,
onNextClick,
animated = true,
tabBarGutter,
} = this.props;
let { inkBarAnimated, tabPaneAnimated } = typeof animated === 'object' ? {
inkBarAnimated: animated.inkBar, tabPaneAnimated: animated.tabPane,
} : {
inkBarAnimated: animated, tabPaneAnimated: animated,
};
let tabPaneAnimated = typeof animated === 'object' ? animated.tabPane : animated;
// card tabs should not have animation
if (type !== 'line') {
@ -166,41 +158,12 @@ export default class Tabs extends React.Component<TabsProps, any> {
</div>
) : null;
const renderTabBar = () => {
const isVertical = tabPosition === 'left' || tabPosition === 'right';
const prevIconType = isVertical ? 'up' : 'left';
const nextIconType = isVertical ? 'down' : 'right';
const prevIcon = (
<span className={`${prefixCls}-tab-prev-icon`}>
<Icon type={prevIconType} className={`${prefixCls}-tab-prev-icon-target`} />
</span>
);
const nextIcon = (
<span className={`${prefixCls}-tab-next-icon`}>
<Icon type={nextIconType} className={`${prefixCls}-tab-next-icon-target`} />
</span>
);
return (
<ScrollableInkTabBar
inkBarAnimated={inkBarAnimated}
extraContent={tabBarExtraContent}
onTabClick={onTabClick}
onPrevClick={onPrevClick}
onNextClick={onNextClick}
style={tabBarStyle}
tabBarGutter={tabBarGutter}
prevIcon={prevIcon}
nextIcon={nextIcon}
/>
);
};
return (
<RcTabs
{...this.props}
className={cls}
tabBarPosition={tabPosition}
renderTabBar={renderTabBar}
renderTabBar={() => <TabBar {...this.props} tabBarExtraContent={tabBarExtraContent}/>}
renderTabContent={() => <TabContent animated={tabPaneAnimated} animatedWithMargin />}
onChange={this.handleChange}
>

@ -26,6 +26,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| --- | --- | --- | --- |
| activeKey | 当前激活 tab 面板的 key | string | 无 |
| animated | 是否使用动画切换 Tabs`tabPosition=top|bottom` 时有效 | boolean \| {inkBar:boolean, tabPane:boolean} | true, 当 type="card" 时为 false |
| renderTabBar | 替换TabBar用于二次封装标签头 | (props: DefaultTabBarProps, DefaultTabBar: React.ReactNode) => React.ReactNode | 无 |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | 第一个面板 |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false |
| size | 大小,提供 `large` `default``small` 三种大小 | string | 'default' |

@ -164,6 +164,7 @@
"react-intl": "^2.0.1",
"react-resizable": "^1.7.5",
"react-router-dom": "^4.2.2",
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.0",
"react-virtualized": "~9.20.0",
"remark-frontmatter": "^1.1.0",

Loading…
Cancel
Save