UNPKG

8.2 kBPlain TextView Raw
1<template>
2 <nav class="md-tab-bar">
3 <div class="md-tab-bar-inner" ref="wrapper">
4 <template v-if="scrollable">
5 <div class="md-tab-bar-start" v-show="maskStartShown"></div>
6 <div class="md-tab-bar-end" v-show="maskEndShown"></div>
7 </template>
8 <md-scroll-view
9 ref="scroller"
10 :scrolling-x="scrollable"
11 :scrolling-y="false"
12 :key="scrollerTmpKey"
13 @scroll="$_onScroll"
14 >
15 <div class="md-tab-bar-list" :style="{width: contentW + 'px'}">
16 <a
17 class="md-tab-bar-item"
18 :class="{
19 'is-active': currentName === item.name,
20 'is-disabled': !!item.disabled
21 }"
22 v-for="(item, index) in items"
23 :key="item.name"
24 ref="items"
25 @click="$_onClick(item, index)"
26 >
27 <slot
28 name="item"
29 :item="item"
30 :items="items"
31 :index="index"
32 :currentName="currentName"
33 >{{ item.label }}</slot>
34 </a>
35 </div>
36 <span
37 class="md-tab-bar-ink"
38 :class="{
39 'is-disabled': currentTab && currentTab.disabled
40 }"
41 v-if="hasInk"
42 :style="{
43 width: inkWidth + 'px',
44 transform: 'translateX(' + inkPos + 'px)',
45 }"
46 ></span>
47 </md-scroll-view>
48 </div>
49 </nav>
50</template>
51
52<script> import ScrollView from '../scroll-view'
53
54export default {
55 name: 'md-tab-bar',
56
57 components: {
58 [ScrollView.name]: ScrollView,
59 },
60
61 props: {
62 value: {
63 type: [String, Number],
64 default: '',
65 },
66 items: {
67 type: Array,
68 default: () => [],
69 },
70 hasInk: {
71 type: Boolean,
72 default: true,
73 },
74 inkLength: {
75 type: Number,
76 default: 100,
77 },
78 immediate: {
79 type: Boolean,
80 default: false,
81 },
82 },
83
84 data() {
85 return {
86 currentName: '',
87 wrapperW: 0,
88 contentW: 0,
89 inkWidth: 0,
90 inkPos: 0,
91 scrollerTmpKey: Date.now(),
92 maskStartShown: false,
93 maskEndShown: true,
94 }
95 },
96
97 computed: {
98 scrollable() {
99 return this.contentW > this.wrapperW
100 },
101 currentIndex() {
102 for (let i = 0, len = this.items.length; i < len; i++) {
103 if (this.items[i].name === this.currentName) {
104 return i
105 }
106 }
107 },
108 currentTab() {
109 if (this.currentIndex) {
110 return this.items[this.currentIndex]
111 }
112 },
113 },
114
115 watch: {
116 value: {
117 immediate: true,
118 handler(val) {
119 if (val !== this.currentName) {
120 this.currentName = val
121 }
122 },
123 },
124 inkWidth() {
125 /* istanbul ignore next */
126 this.$nextTick(function() {
127 this.reflow()
128 })
129 },
130 items() {
131 /* istanbul ignore next */
132 this.$nextTick(function() {
133 this.reflow()
134 })
135 },
136 currentIndex() {
137 this.$nextTick(function() {
138 this.reflow()
139 })
140 },
141 scrollable() {
142 /* istanbul ignore next */
143 this.scrollerTmpKey = Date.now()
144 },
145 },
146
147 created() {
148 if (this.currentName === '' && this.items.length) {
149 this.currentName = this.items[0].name
150 this.$emit('change', this.items[0], 0, 0)
151 }
152 },
153 mounted() {
154 this.$_resizeEnterBehavior()
155 },
156 activated() {
157 /* istanbul ignore next */
158 this.$_resizeEnterBehavior()
159 },
160 deactivated() {
161 /* istanbul ignore next */
162 this.$_resizeLeaveBehavior()
163 },
164 beforeDestroy() {
165 this.$_resizeLeaveBehavior()
166 },
167
168 methods: {
169 // MARK: private events
170 $_onScroll({scrollLeft}) /* istanbul ignore next */ {
171 if (scrollLeft > 0) {
172 this.maskStartShown = true
173 } else {
174 this.maskStartShown = false
175 }
176
177 if (scrollLeft >= this.$refs.scroller.contentW - this.$refs.scroller.containerW) {
178 this.maskEndShown = false
179 } else {
180 this.maskEndShown = true
181 }
182 },
183 $_onClick(item, index) {
184 if (item.disabled) {
185 return
186 }
187 this.$emit('change', item, index, this.currentIndex)
188 this.currentName = item.name
189 this.$emit('input', item.name)
190 },
191 $_resizeEnterBehavior() {
192 window.addEventListener('resize', this.reflow)
193 this.reflow()
194 /* istanbul ignore next */
195 if (this.immediate) {
196 this.$nextTick(() => {
197 this.$emit('change', this.items[this.currentIndex], this.currentIndex)
198 })
199 }
200 },
201 $_resizeLeaveBehavior() {
202 window.removeEventListener('resize', this.reflow)
203 },
204 // MARK: public methods
205 reflow() {
206 /* istanbul ignore next */
207 if (!this.$refs.items || this.$refs.items.length === 0) {
208 return
209 }
210
211 const wrapperReact = this.$refs.wrapper.getBoundingClientRect()
212 this.wrapperW = wrapperReact.width
213
214 let contentWidth = 0
215 for (let i = 0, len = this.items.length; i < len; i++) {
216 const {width} = this.$refs.items[i].getBoundingClientRect()
217 contentWidth += width
218 }
219 this.contentW = contentWidth
220 this.$refs.scroller.reflowScroller()
221 this.$nextTick(() => {
222 /* istanbul ignore next */
223 if (!this.$refs.items || !this.$refs.items[this.currentIndex]) {
224 return
225 }
226
227 const target = this.$refs.items[this.currentIndex]
228 this.inkWidth = target.offsetWidth * this.inkLength / 100
229 this.inkPos = target.offsetLeft + (target.offsetWidth - this.inkWidth) / 2
230
231 const prevTarget = this.$refs.items[this.currentIndex - 1]
232 const nextTarget = this.$refs.items[this.currentIndex + 1]
233
234 if (!prevTarget) {
235 this.$refs.scroller.scrollTo(0, 0, true)
236 return
237 }
238 /* istanbul ignore next */
239 if (!nextTarget) {
240 this.$refs.scroller.scrollTo(this.contentW, 0, true)
241 return
242 }
243
244 const wrapperRect = this.$refs.wrapper.getBoundingClientRect()
245 const prevTargetRect = prevTarget.getBoundingClientRect()
246 const nextTargetRect = nextTarget.getBoundingClientRect()
247
248 /* istanbul ignore next */
249 if (prevTargetRect && prevTargetRect.left < wrapperRect.left) {
250 this.$refs.scroller.scrollTo(prevTarget.offsetLeft, 0, true)
251 } else if (nextTargetRect && nextTargetRect.right > wrapperRect.right) {
252 this.$refs.scroller.scrollTo(nextTarget.offsetLeft + nextTarget.offsetWidth - this.wrapperW, 0, true)
253 }
254 })
255 },
256 },
257}
258 </script>
259
260<style lang="stylus">
261.md-tab-bar
262 position relative
263 padding-left tab-offset
264 padding-right tab-offset
265 background-color tab-bg
266
267.md-tab-bar-inner
268 position relative
269 width 100%
270 // line-height 0
271
272.md-tab-bar-list
273 display flex
274 justify-content space-between
275 min-width 100%
276
277.md-tab-bar-item
278 flex auto
279 flex-shrink 0
280 position relative
281 display inline-flex
282 align-items center
283 justify-content center
284 color tab-text-color
285 font-size tab-font-size
286 font-weight tab-font-weight
287 min-height tab-height
288 padding 0 tab-item-gap
289 margin 0 auto
290 box-sizing border-box
291 -webkit-user-select none
292 -webkit-tap-highlight-color transparent
293 &.is-active
294 color tab-active-color
295 &.is-disabled
296 color tab-disabled-color
297
298.md-tab-bar-ink
299 position absolute
300 bottom 0
301 left 0
302 display block
303 height tab-ink-height
304 background-color tab-active-color
305 transition all 300ms
306 &.is-disabled
307 background-color tab-disabled-color
308
309.md-tab-bar-start,
310.md-tab-bar-end
311 position absolute
312 top 0
313 left 0
314 width 14px
315 height tab-height
316 overflow hidden
317 &::after
318 content ''
319 display block
320 position absolute
321 left -14px
322 top 50%
323 width 14px
324 if tab-height is a 'unit'
325 margin-top 0 - tab-height * 0.4
326 height tab-height * 0.8
327 else
328 margin-top "calc(0 - %s * 0.4)" % tab-height
329 height "calc(%s * 0.8)" % tab-height
330 border-radius 50%
331 box-shadow: -1px 0 12px 0 rgba(0,0,0,0.2)
332.md-tab-bar-end
333 left auto
334 right 0
335 transform rotate(180deg)
336
337.md-tab-bar
338 .md-scroll-view
339 display block
340 .scroll-view-container
341 min-width 100%
342</style>