mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
Merge pull request #674 from ant-design/feat-new-tabs-style
Feat new tabs style
This commit is contained in:
commit
6e8d9c4079
@ -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'));
|
||||
````
|
24
components/tabs/demo/card.md
Normal file
24
components/tabs/demo/card.md
Normal 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'));
|
||||
````
|
58
components/tabs/demo/editable-card.md
Normal file
58
components/tabs/demo/editable-card.md
Normal 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'));
|
||||
````
|
||||
|
21
components/tabs/demo/extra.md
Normal file
21
components/tabs/demo/extra.md
Normal 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'));
|
||||
````
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user