add tabs scroll controls

pull/2674/head
baiyaaaaa 2017-01-30 17:52:21 +08:00 committed by 杨奕
parent 0bec05ada3
commit 6702726dcf
4 changed files with 242 additions and 46 deletions

View File

@ -3,9 +3,12 @@
</template> </template>
<script> <script>
export default { export default {
name: 'TabBar',
props: { props: {
tabs: Array tabs: Array
}, },
computed: { computed: {
barStyle: { barStyle: {
cache: false, cache: false,

View File

@ -0,0 +1,174 @@
<script>
import TabBar from './tab-bar';
function noop() {}
export default {
name: 'TabNav',
components: {
TabBar
},
props: {
panes: Array,
currentName: String,
editable: Boolean,
onTabClick: {
type: Function,
default: noop
},
onTabRemove: {
type: Function,
default: noop
},
type: String
},
data() {
return {
scrollable: false,
navStyle: {
transform: ''
}
};
},
methods: {
scrollPrev() {
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (!currentOffset) return;
let newOffset = currentOffset > containerWidth
? currentOffset - containerWidth
: 0;
this.setOffset(newOffset);
},
scrollNext() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (navWidth - currentOffset <= containerWidth) return;
let newOffset = navWidth - currentOffset > containerWidth * 2
? currentOffset + containerWidth
: (navWidth - containerWidth);
this.setOffset(newOffset);
},
scrollToActiveTab() {
if (!this.scrollable) return;
const nav = this.$refs.nav;
const activeTab = this.$el.querySelector('.is-active');
const navScroll = this.$refs.navScroll;
const activeTabBounding = activeTab.getBoundingClientRect();
const navScrollBounding = navScroll.getBoundingClientRect();
const navBounding = nav.getBoundingClientRect();
const currentOffset = this.getCurrentScrollOffset();
let newOffset = currentOffset;
if (activeTabBounding.left < navScrollBounding.left) {
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
}
if (activeTabBounding.right > navScrollBounding.right) {
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
}
if (navBounding.right < navScrollBounding.right) {
newOffset = nav.offsetWidth - navScrollBounding.width;
}
this.setOffset(Math.max(newOffset, 0));
},
getCurrentScrollOffset() {
const { navStyle } = this;
return navStyle.transform
? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
: 0;
},
setOffset(value) {
this.navStyle.transform = `translateX(-${value}px)`;
}
},
updated() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (containerWidth < navWidth) {
const currentOffset = this.getCurrentScrollOffset();
this.scrollable = this.scrollable || {};
this.scrollable.prev = currentOffset;
this.scrollable.next = currentOffset + containerWidth < navWidth;
if (navWidth - currentOffset < containerWidth) {
this.setOffset(navWidth - containerWidth);
}
} else if (currentOffset > 0) {
this.setOffset(0);
}
},
render(h) {
const {
type,
panes,
editable,
onTabClick,
onTabRemove,
navStyle,
scrollable,
scrollNext,
scrollPrev
} = this;
const scrollBtn = scrollable
? [
<span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>,
<span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span>
] : null;
const tabs = this._l(panes, (pane, index) => {
let tabName = pane.name || pane.index || index;
const closable = pane.isClosable || editable;
pane.index = `${index}`;
const btnClose = closable
? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span>
: null;
const tabLabelContent = pane.$slots.label || pane.label;
return (
<div
class={{
'el-tabs__item': true,
'is-active': pane.active,
'is-disabled': pane.disabled,
'is-closable': closable
}}
ref="tabs"
refInFor
on-click={(ev) => { onTabClick(pane, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
</div>
);
});
return (
<div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '']}>
{scrollBtn}
<div class={['el-tabs__nav-scroll']} ref="navScroll">
<div class="el-tabs__nav" ref="nav" style={navStyle}>
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
{tabs}
</div>
</div>
</div>
);
}
};
</script>

View File

@ -1,10 +1,11 @@
<script> <script>
import TabBar from './tab-bar'; import TabNav from './tab-nav';
module.exports = { module.exports = {
name: 'ElTabs', name: 'ElTabs',
components: { components: {
TabBar TabNav
}, },
props: { props: {
@ -18,7 +19,6 @@
data() { data() {
return { return {
children: null,
currentName: this.value || this.activeName, currentName: this.value || this.activeName,
panes: [] panes: []
}; };
@ -30,6 +30,13 @@
}, },
value(value) { value(value) {
this.setCurrentName(value); this.setCurrentName(value);
},
currentName(value) {
if (this.$refs.nav) {
this.$nextTick(_ => {
this.$refs.nav.scrollToActiveTab();
});
}
} }
}, },
@ -78,7 +85,7 @@
const newButton = editable || addable const newButton = editable || addable
? ( ? (
<span <span
class="el-tabs__new-button" class="el-tabs__new-tab"
on-click={ handleTabAdd } on-click={ handleTabAdd }
> >
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
@ -86,38 +93,17 @@
) )
: null; : null;
const tabs = this._l(panes, (pane, index) => { const navData = {
let tabName = pane.name || pane.index || index; props: {
const closable = pane.isClosable || editable; currentName,
onTabClick: handleTabClick,
if (currentName === undefined && index === 0) { onTabRemove: handleTabRemove,
this.setCurrentName(tabName); editable,
} type,
panes
pane.index = index; },
ref: 'nav'
const btnClose = closable };
? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(pane, ev); }}></span>
: null;
const tabLabelContent = pane.$slots.label || pane.label;
return (
<div
class={{
'el-tabs__item': true,
'is-active': pane.active,
'is-disabled': pane.disabled,
'is-closable': closable
}}
ref="tabs"
refInFor
on-click={(ev) => { handleTabClick(pane, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
</div>
);
});
return ( return (
<div class={{ <div class={{
@ -126,15 +112,19 @@
'el-tabs--border-card': type === 'border-card' 'el-tabs--border-card': type === 'border-card'
}}> }}>
<div class="el-tabs__header"> <div class="el-tabs__header">
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
{tabs}
{newButton} {newButton}
<tab-nav { ...navData }></tab-nav>
</div> </div>
<div class="el-tabs__content"> <div class="el-tabs__content">
{this.$slots.default} {this.$slots.default}
</div> </div>
</div> </div>
); );
},
created() {
if (!this.currentName) {
this.setCurrentName('1');
}
} }
}; };
</script> </script>

View File

@ -8,7 +8,6 @@
padding: 0; padding: 0;
position: relative; position: relative;
margin: 0 0 15px; margin: 0 0 15px;
@utils-clearfix;
} }
@e active-bar { @e active-bar {
position: absolute; position: absolute;
@ -20,8 +19,8 @@
transition: transform .3s cubic-bezier(.645,.045,.355,1); transition: transform .3s cubic-bezier(.645,.045,.355,1);
list-style: none; list-style: none;
} }
@e new-button { @e new-tab {
float: left; float: right;
border: 1px solid #d3dce6; border: 1px solid #d3dce6;
height: 18px; height: 18px;
width: @height; width: @height;
@ -42,16 +41,46 @@
color: var(--color-primary); color: var(--color-primary);
} }
} }
@e nav-wrap {
overflow: hidden;
margin-bottom: -1px;
position: relative;
@when scrollable {
padding: 0 15px;
}
}
@e nav-scroll {
overflow: hidden;
}
@e nav-next, nav-prev {
position: absolute;
cursor: pointer;
line-height: 44px;
font-size: 12px;
color: var(--color-base-silver);
}
@e nav-next {
right: 0;
}
@e nav-prev {
left: 0;
}
@e nav {
white-space: nowrap;
position: relative;
float: left;
transition: transform .3s;
}
@e item { @e item {
padding: 0 16px; padding: 0 16px;
height: 42px; height: 42px;
box-sizing: border-box; box-sizing: border-box;
line-height: @height; line-height: @height;
float: left; display: inline-block;
list-style: none; list-style: none;
font-size: 14px; font-size: 14px;
color: var(--color-base-silver); color: var(--color-base-silver);
margin-bottom: -1px;
position: relative; position: relative;
& .el-icon-close { & .el-icon-close {
@ -89,10 +118,10 @@
position: relative; position: relative;
} }
@m card { @m card {
&>.el-tabs__header>.el-tabs__active-bar { .el-tabs__nav .el-tabs__active-bar {
display: none; display: none;
} }
&>.el-tabs__header>.el-tabs__item .el-icon-close { .el-tabs__nav .el-tabs__item .el-icon-close {
position: relative; position: relative;
font-size: 12px; font-size: 12px;
width: 0; width: 0;
@ -104,7 +133,7 @@
right: -2px; right: -2px;
transform-origin: 100% 50%; transform-origin: 100% 50%;
} }
&>.el-tabs__header>.el-tabs__item { .el-tabs__nav .el-tabs__item {
border: 1px solid transparent; border: 1px solid transparent;
transition: all .3s cubic-bezier(.645,.045,.355,1); transition: all .3s cubic-bezier(.645,.045,.355,1);