Merge pull request #674 from ant-design/feat-new-tabs-style

Feat new tabs style
This commit is contained in:
yiminghe 2015-12-15 19:24:51 +08:00
commit 6e8d9c4079
7 changed files with 257 additions and 150 deletions

View File

@ -1,107 +0,0 @@
# 动态的页签
- order: 4
演示添加删除和附加操作。
---
````jsx
import { Tabs, Button, Icon, message } from 'antd';
const TabPane = Tabs.TabPane;
let index = 0;
const closeStyle = {
position: 'absolute',
top: 8,
right: -9,
};
const addStyle = {
pointerEvents: 'auto',
color: '#2db7f5',
position: 'absolute',
top: 8,
left: 0,
marginLeft: -8,
};
const Test = React.createClass({
getInitialState() {
return {
tabs: [{
title: 'title ' + index,
content: 'content ' + index,
index: index
}],
activeKey: index.toString()
};
},
remove(targetIndex, e) {
e.stopPropagation();
let tabs = this.state.tabs;
let activeKey = this.state.activeKey;
let foundIndex = 0;
if(tabs.length === 1) {
message.error('仅剩一个,不能删除');
return;
}
const newTabs = tabs.filter(tab => {
if (tab.index !== targetIndex) {
return true;
} else {
foundIndex = targetIndex;
return false;
}
});
if (activeKey === targetIndex) {
activeKey = tabs[foundIndex - 1].index;
}
this.setState({
tabs: newTabs, activeKey
});
},
add() {
index += 1;
this.setState({
tabs: this.state.tabs.concat({
title: 'title ' + index,
content: 'content ' + index,
index: index,
}),
activeKey: index.toString(),
});
},
onChange(activeKey) {
console.log(activeKey);
this.setState({ activeKey });
},
render() {
const addBtn = <Icon style={addStyle} type="plus-circle" onClick={this.add} />;
const operations = <Button style={{ marginTop: 2 }}>操作</Button>;
return (
<Tabs onChange={this.onChange}
activeKey={this.state.activeKey}
tabBarExtraContent={operations}>
{
this.state.tabs.map(tab => (
<TabPane key={tab.index} tab={
<div>
{tab.title}
<Icon type="cross" style={closeStyle} onClick={this.remove.bind(this, tab.index)} />
</div>
}>{tab.content}</TabPane>
))
}
<TabPane key="__add" disabled tab={addBtn} />
</Tabs>
);
}
});
ReactDOM.render(<Test />, document.getElementById('components-tabs-demo-add'));
````

View File

@ -0,0 +1,24 @@
# 卡片式页签
- order: 8
另一种样式的页签,常用于容器顶部,可添加和关闭新标签,不提供对应的垂直样式。
---
````jsx
import { Tabs } from 'antd';
const TabPane = Tabs.TabPane;
function callback(key) {
console.log(key);
}
ReactDOM.render(
<Tabs onChange={callback} type="card">
<TabPane tab="选项卡一" key="1">选项卡一内容</TabPane>
<TabPane tab="选项卡二" key="2">选项卡二内容</TabPane>
<TabPane tab="选项卡三" key="3">选项卡三内容</TabPane>
</Tabs>
, document.getElementById('components-tabs-demo-card'));
````

View File

@ -0,0 +1,58 @@
# 新增和关闭页签
- order: 9
只有卡片样式的页签支持关闭选项。
---
````jsx
import { Tabs } from 'antd';
const TabPane = Tabs.TabPane;
const Demo = React.createClass({
getInitialState() {
this.newTabIndex = 0;
const panes = [
<TabPane tab="选项卡" key="1">选项卡一内容</TabPane>,
<TabPane tab="选项卡" key="2">选项卡二内容</TabPane>,
];
return {
activeKey: panes[0].key,
panes: panes,
};
},
onChange(activeKey) {
this.setState({ activeKey });
},
onEdit(targetKey, action) {
this[action](targetKey);
},
add(targetKey) {
const panes = this.state.panes;
const activeKey = 'newTab' + this.newTabIndex++;
panes.push(<TabPane tab="新建页签" key={activeKey}>新页面</TabPane>);
this.setState({ panes, activeKey });
},
remove(targetKey) {
let activeKey = this.state.activeKey;
let lastIndex = this.state.panes.findIndex(pane => pane.key === targetKey) - 1;
const panes = this.state.panes.filter(pane => pane.key !== targetKey);
if (activeKey === targetKey) {
activeKey = panes[lastIndex >= 0 ? lastIndex : 0].key;
}
this.setState({ panes, activeKey });
},
render() {
return (
<Tabs onChange={this.onChange} activeKey={this.state.activeKey}
type="editable-card" onEdit={this.onEdit}>
{this.state.panes}
</Tabs>
);
}
});
ReactDOM.render(<Demo />, document.getElementById('components-tabs-demo-editable-card'));
````

View File

@ -0,0 +1,21 @@
# 附加内容
- order: 4
可以在页签右边添加附加操作。
---
````jsx
import { Tabs, Button } from 'antd';
const TabPane = Tabs.TabPane;
const operations = <Button>额外操作</Button>;
ReactDOM.render(
<Tabs tabBarExtraContent={operations}>
<TabPane tab="选项卡一" key="1">选项卡一内容</TabPane>
<TabPane tab="选项卡二" key="2">选项卡二内容</TabPane>
<TabPane tab="选项卡三" key="3">选项卡三内容</TabPane>
</Tabs>, document.getElementById('components-tabs-demo-extra'));
````

View File

@ -1,26 +1,80 @@
import Tabs from 'rc-tabs';
import React from 'react';
const prefixCls = 'ant-tabs';
import React, { cloneElement } from 'react';
import classNames from 'classnames';
import Icon from '../icon';
class AntTabs extends React.Component {
render() {
let className = (this.props.className || '');
let animation = this.props.animation;
if (this.props.size === 'small' || this.props.size === 'mini') {
className += ' ' + prefixCls + '-mini';
constructor(props) {
super(props);
[
'createNewTab',
'removeTab',
'handleChange',
].forEach((method) => this[method] = this[method].bind(this));
}
createNewTab(targetKey) {
this.props.onEdit(targetKey, 'add');
}
removeTab(targetKey, e) {
e.stopPropagation();
if (!targetKey) {
return;
}
if (this.props.tabPosition === 'left' || this.props.tabPosition === 'right') {
className += ' ' + prefixCls + '-vertical';
this.props.onEdit(targetKey, 'remove');
}
handleChange(activeKey) {
this.props.onChange(activeKey);
}
render() {
let { prefixCls, size, tabPosition, animation, type,
children, tabBarExtraContent } = this.props;
let className = classNames({
[this.props.className]: !!this. props.className,
[prefixCls + '-mini']: size === 'small' || size === 'mini',
[prefixCls + '-vertical']: tabPosition === 'left' || tabPosition === 'right',
[prefixCls + '-card']: type.indexOf('card') >= 0,
});
if (tabPosition === 'left' || tabPosition === 'right' || type.indexOf('card') >= 0) {
animation = null;
}
return <Tabs {...this.props} className={className} animation={animation}/>;
// only card type tabs can be added and closed
if (type === 'editable-card') {
if (children.length > 1) {
children = children.map((child, index) => {
return cloneElement(child, {
tab: <div>
{child.props.tab}
<Icon type="cross" onClick={this.removeTab.bind(this, child.key)} />
</div>,
key: child.key || index,
});
});
}
// Add new tab handler
tabBarExtraContent = <span>
<Icon type="plus" className={prefixCls + '-new-tab'} onClick={this.createNewTab} />
{tabBarExtraContent}
</span>;
}
// Wrap the extra content
tabBarExtraContent = <div className={prefixCls + '-extra-content'}>
{tabBarExtraContent}
</div>;
return <Tabs {...this.props}
className={className}
tabBarExtraContent={tabBarExtraContent}
onChange={this.handleChange}
animation={animation}>{children}</Tabs>;
}
}
AntTabs.defaultProps = {
prefixCls: prefixCls,
prefixCls: 'ant-tabs',
size: 'default',
animation: 'slide-horizontal',
type: 'line', // or 'card' 'editable-card'
onChange() {},
onEdit() {},
};
AntTabs.TabPane = Tabs.TabPane;

View File

@ -12,7 +12,11 @@
提供平级的区域将大块内容进行收纳和展现,保持界面整洁。
> [RadioButton](/components/radio#demo-radiobutton) 可以作为更次级的页签来使用。
Ant Design 提供了三级选项卡,分别用于不同的场景。
- 卡片式的页签,用于容器顶部。
- 标准线条式页签,用于容器内部的主功能切换。
- [RadioButton](/components/radio/#demo-radiobutton) 作为更次级的页签来使用。
## API
@ -25,6 +29,8 @@
| onChange | 切换面板的回调 | Function | 无 |
| onTabClick | tab 被点击的回调 | Function | 无 |
| tabBarExtraContent | tab bar 上额外的元素 | React Node | 无 |
| type | 页签的基本样式,可选 `line`、`card` `editable-card` 类型 | String | 'line' |
| onEdit | 新增和删除页签的回调,在 `type="editable-card"` 时有效 | Function(targetKey, action) | 无 |
### Tabs.TabPane

View File

@ -8,6 +8,7 @@
position: relative;
overflow: hidden;
.clearfix;
color: @text-color;
&-ink-bar {
z-index: 1;
@ -30,7 +31,7 @@
}
&-tabs-bar {
border-bottom: 1px solid #e9e9e9;
border-bottom: 1px solid @border-color-base;
margin-bottom: 16px;
}
@ -83,10 +84,6 @@
font-family: "anticon" !important;
}
}
&:hover {
color: tint(@primary-color, 20%);
}
}
&-tab-btn-disabled {
@ -111,7 +108,6 @@
&-icon:before {
content: "\e601";
}
:root & {
filter: none;
}
@ -146,16 +142,7 @@
clear: both;
}
div.@{tab-prefix-cls}-tab-active {
> .@{tab-prefix-cls}-tab-inner,
> .@{tab-prefix-cls}-tab-inner:hover {
color: tint(@primary-color, 20%);
cursor: pointer;
text-decoration: none;
}
}
div.@{tab-prefix-cls}-tab-disabled {
.@{tab-prefix-cls}-tab-disabled {
pointer-events: none;
cursor: default;
@ -171,29 +158,31 @@
box-sizing: border-box;
position: relative;
> .@{tab-prefix-cls}-tab-inner {
&-inner {
padding: 8px 20px;
transition: color 0.3s @ease-in-out;
display: block;
color: #666;
cursor: pointer;
text-decoration: none;
&:hover {
color: tint(@primary-color, 20%);
}
&:active {
color: shade(@primary-color, 5%);
}
.anticon {
width: 14px;
height: 14px;
margin-right: 8px;
line-height: 1.5;
}
}
}
> .@{tab-prefix-cls}-tab-inner:hover {
color: tint(@primary-color, 30%);
cursor: pointer;
}
> .@{tab-prefix-cls}-tab-inner:hover {
text-decoration: none;
}
.@{tab-prefix-cls}-tab-active .@{tab-prefix-cls}-tab-inner {
color: @primary-color;
}
}
@ -203,7 +192,7 @@
&-mini &-tab {
margin-right: 24px;
> .@{tab-prefix-cls}-tab-inner {
.@{tab-prefix-cls}-tab-inner {
padding: 8px 16px;
}
}
@ -281,7 +270,7 @@
&:last-child {
margin-bottom: 0;
}
> .@{tab-prefix-cls}-tab-inner {
.@{tab-prefix-cls}-tab-inner {
padding: 8px 24px;
}
}
@ -331,7 +320,7 @@
float: left;
}
.@{tab-prefix-cls}-tab {
> .@{tab-prefix-cls}-tab-inner {
.@{tab-prefix-cls}-tab-inner {
text-align: right;
}
}
@ -374,4 +363,66 @@
border-right: 1px solid #e9e9e9;
}
}
// card style
&&-card &-ink-bar {
visibility: hidden;
}
&&-card &-nav {
border-left: 1px solid @border-color-base;
}
&&-card &-tab {
margin: 0;
border: 1px solid @border-color-base;
border-bottom: 0;
border-left: 0;
border-radius: 2px 2px 0 0;
transition: border-color 0.3s @ease-in-out;
}
&&-card &-tab-inner {
padding: 7px 16px;
}
&&-card &-tab-active {
background: #fff;
border-top: 2px solid @primary-color;
}
&&-card &-tab-active &-tab-inner {
padding-top: 6px;
}
&&-card &-nav-wrap {
margin-bottom: 0;
}
&&-card &-tab-inner .anticon-cross {
margin-right: 0;
margin-left: 4px;
font-size: 12px;
width: 12px;
color: #999;
transition: color 0.3s ease;
&:hover {
color: @primary-color;
}
}
&-extra-content {
float: right;
line-height: 32px;
.@{tab-prefix-cls}-new-tab {
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
cursor: pointer;
border-radius: 3px;
border: 1px solid @border-color-base;
font-size: 12px;
.iconfont-size-under-12px(10px);
transition: color 0.3s ease;
&:hover {
color: @primary-color;
}
}
}
}