Skip to content

Commit 2db4629

Browse files
committed
feat: add CTabs activeTab prop, change animation logic
1 parent 7b2c553 commit 2db4629

File tree

8 files changed

+196
-148
lines changed

8 files changed

+196
-148
lines changed

src/components/index.d.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -587,12 +587,11 @@ export declare class CTabs extends Vue {
587587
addTabsWrapperClasses: string | Array<any> | object
588588
addTabsClasses: string | Array<any> | object
589589
addTabClasses: string | Array<any> | object
590+
activeTab: number
590591
}
591592

592-
export declare class CTab extends Vue {
593-
title: string
594-
active: boolean
595-
disabled: boolean
593+
export declare class CTab extends CLink {
594+
title: string
596595
}
597596

598597
declare class ToastProps extends Vue {

src/components/tabs/CDistributor.vue

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<component :is="tag">
3+
<slot></slot>
4+
</component>
5+
</template>
6+
7+
<script>
8+
export default {
9+
name: 'CDistributor',
10+
inheritAttrs: false,
11+
props: {
12+
tag: {
13+
type: String,
14+
default: 'div'
15+
}
16+
},
17+
provide () {
18+
return {
19+
distributed: this.props
20+
}
21+
},
22+
computed: {
23+
props () {
24+
return this.$attrs
25+
}
26+
}
27+
}
28+
</script>

src/components/tabs/CTab.vue

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,93 @@
1+
<template>
2+
<li
3+
v-if="distributed.header"
4+
@click="navClick"
5+
class="nav-item"
6+
>
7+
<CLink class="nav-link" v-bind="navLinkProps">
8+
<slot name="title">{{title}}</slot>
9+
</CLink>
10+
</li>
11+
<transition
12+
v-else
13+
:name="tabs.fade ? 'fade' : ''"
14+
mode="out-in"
15+
>
16+
<div
17+
v-show="isActive"
18+
:class="paneClasses"
19+
>
20+
<slot></slot>
21+
</div>
22+
</transition>
23+
</template>
24+
125
<script>
26+
import CLink, { propsFactory } from '../link/CLink'
27+
28+
const props = Object.assign(propsFactory(), {
29+
title: String
30+
})
31+
232
export default {
333
name: 'CTab',
4-
props: {
5-
title: String,
6-
active: Boolean,
7-
disabled: Boolean
34+
inject: ['distributed', 'tabs'],
35+
components: {
36+
CLink
837
},
9-
render (h) {
10-
return h(false)
38+
beforeMount () {
39+
this.index = Array.from(this.$parent.$children).indexOf(this)
40+
if (
41+
this.active && this.distributed.header
42+
&& typeof this.tabs.activeTab !== 'number'
43+
) {
44+
this.distributed.changeTabTo(this.index)
45+
}
46+
},
47+
data () {
48+
return {
49+
index: null
50+
}
51+
},
52+
props,
53+
computed: {
54+
navLinkProps () {
55+
return Object.assign(
56+
{}, this._props, { active: this.isActive }, { title: null }
57+
)
58+
},
59+
paneClasses () {
60+
return [
61+
this.distributed.addClasses,
62+
'tab-pane',
63+
{ 'active': this.isActive }
64+
]
65+
},
66+
isActive () {
67+
return this.tabs.activeTab === this.index
68+
}
69+
},
70+
methods: {
71+
navClick () {
72+
if (!this.isActive && !this.disabled) {
73+
this.distributed.changeTabTo(this.index)
74+
}
75+
}
1176
}
1277
}
1378
</script>
79+
80+
<style scoped>
81+
.fade-enter {
82+
display: none;
83+
opacity: 0;
84+
}
85+
.fade-enter-active {
86+
transition: opacity 0.3s;
87+
}
88+
.fade-leave, .fade-leave-active {
89+
position: absolute;
90+
display: none;
91+
opacity: 0;
92+
}
93+
</style>

src/components/tabs/CTabContent.vue

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/components/tabs/CTabNav.vue

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/components/tabs/CTabs.vue

Lines changed: 38 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,43 @@
11
<template>
22
<div :class="wrapperClasses">
33
<div :class="navWrapperClasses">
4-
<ul :class="navClasses">
5-
<CTabNav
6-
v-for="(tab, key) in ctabInstances"
7-
@click.native="tabClick(tab, key)"
8-
v-bind="tab.$attrs"
9-
:title="tab.title"
10-
:custom-title-slot="tab.$scopedSlots.title"
11-
:active="tab === activeTab"
12-
:disabled="tab.disabled"
13-
:key="key"
14-
/>
15-
</ul>
4+
<CDistributor
5+
tag="ul"
6+
:class="navClasses"
7+
:header="true"
8+
:changeTabTo="changeTabTo"
9+
>
10+
<slot></slot>
11+
</CDistributor>
1612
</div>
1713
<div :class="[addTabsWrapperClasses, gridClasses.content]">
18-
<div :class="tabsClasses">
19-
<transition :name="fade ? 'fade' : ''" mode="out-in">
20-
<KeepAlive>
21-
<template v-for="(tab, key) in ctabInstances">
22-
<CTabContent
23-
v-if="activeTab === tab"
24-
:content="tab.$scopedSlots.default"
25-
:key="key"
26-
:class="[addTabClasses, 'tab-pane active']"
27-
/>
28-
</template>
29-
</KeepAlive>
30-
</transition>
31-
</div>
14+
<CDistributor
15+
:class="tabsClasses"
16+
:addClasses="addTabClasses"
17+
>
18+
<slot></slot>
19+
</CDistributor>
3220
</div>
33-
<!-- needed to instantiate CTab components, shouldn't render anything -->
34-
<slot></slot>
3521
</div>
3622
</template>
3723

3824
<script>
39-
import CTabNav from './CTabNav'
40-
import CTabContent from './CTabContent'
25+
import CDistributor from './CDistributor'
4126
4227
export default {
4328
name: 'CTabs',
4429
components: {
45-
CTabNav,
46-
CTabContent
30+
CDistributor
31+
},
32+
provide () {
33+
const tabs = {}
34+
Object.defineProperty(tabs, 'activeTab', {
35+
get: () => this.activeTabIndex
36+
})
37+
Object.defineProperty(tabs, 'fade', {
38+
get: () => this.fade
39+
})
40+
return { tabs }
4741
},
4842
props: {
4943
fill: Boolean,
@@ -62,12 +56,17 @@ export default {
6256
addNavClasses: [String, Array, Object],
6357
addTabsWrapperClasses: [String, Array, Object],
6458
addTabsClasses: [String, Array, Object],
65-
addTabClasses: [String, Array, Object]
59+
addTabClasses: [String, Array, Object],
60+
activeTab: Number
6661
},
6762
data () {
6863
return {
69-
defaultSlotNodes: null,
70-
activatedTab: null
64+
activeTabIndex: this.activeTab
65+
}
66+
},
67+
watch: {
68+
activeTab (val) {
69+
this.activeTabIndex = val
7170
}
7271
},
7372
computed: {
@@ -100,51 +99,21 @@ export default {
10099
}
101100
]
102101
},
103-
activeTab () {
104-
return this.activatedTab || this.ctabInstances.filter(el => el.active)[0]
105-
},
106102
gridClasses () {
107103
if (this.vertical === true) {
108104
return { navs: 'col-sm-4', content: 'col-sm-8'}
109105
} else {
110106
return this.vertical || {}
111107
}
112-
},
113-
ctabInstances () {
114-
if (this.defaultSlotNodes) {
115-
return this.defaultSlotNodes.map(node => {
116-
const instance = node.componentInstance
117-
if (instance && instance.$options._componentTag === 'CTab') {
118-
return instance
119-
}
120-
}).filter(el => el)
121-
} else {
122-
return []
123-
}
124108
}
125109
},
126-
mounted () {
127-
this.defaultSlotNodes = this.$slots.default
128-
},
129-
updated () {
130-
this.defaultSlotNodes = this.$slots.default
131-
},
132110
methods: {
133-
tabClick (tab, key) {
134-
if (!tab.disabled) {
135-
this.activatedTab = tab
136-
this.$emit('update:show', key)
137-
}
111+
changeTabTo (tab) {
112+
this.activeTabIndex = tab
113+
this.$emit('update:activeTab', tab)
138114
}
139115
}
140116
}
141117
</script>
142118

143-
<style scoped>
144-
.fade-enter-active, .fade-leave-active {
145-
transition: opacity .3s;
146-
}
147-
.fade-enter, .fade-leave-to {
148-
opacity: 0;
149-
}
150-
</style>
119+

src/components/tabs/tests/CTabs.spec.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const defaultWrapper = mount(CTabs)
2323
// default: [CTab, CTab, customTab]
2424
// }
2525
// })
26-
2726
const App = Vue.extend({
2827
components: { CTabs, CTab },
2928
render (h) {
@@ -44,8 +43,8 @@ const App = Vue.extend({
4443
}
4544
},
4645
[
47-
h('CTab', { props: { active: true, title: 'tab1' }}, ['tab1 content']),
48-
h('CTab', { props: { title: 'tab2' }}, ['tab2 content']),
46+
h('CTab', { props: { title: 'tab1' }}, ['tab1 content']),
47+
h('CTab', { props: { active: true, title: 'tab2' }}, ['tab2 content']),
4948
h('CTab', { props: { title: 'tab3' }}),
5049
h(
5150
'CTab',
@@ -63,6 +62,7 @@ const App = Vue.extend({
6362
})
6463

6564
const customWrapper = mount(App)
65+
const tabsComponent = customWrapper.vm.$children[0]
6666

6767
describe(ComponentName, () => {
6868
it('has a name', () => {
@@ -79,10 +79,14 @@ describe(ComponentName, () => {
7979
tabs.at(3).trigger('click')
8080
expect(customWrapper.find('.tab-pane').text()).toBe('tab1 content')
8181
})
82-
it('changes tab on click', () => {
82+
it('changes active tab corectly', () => {
8383
const tabs = customWrapper.findAll('.nav-item')
8484
tabs.at(2).trigger('click')
85-
expect(customWrapper.find('.tab-pane').text()).toBe('')
85+
expect(tabsComponent.activeTabIndex).toBe(2)
86+
defaultWrapper.setProps({
87+
activeTab: 1
88+
})
89+
expect(defaultWrapper.vm.activeTabIndex).toBe(1)
8690
})
8791
it('properly changes vertical classes', () => {
8892
defaultWrapper.setProps({ vertical: true })

0 commit comments

Comments
 (0)