Skip to content

Commit 7823c37

Browse files
author
Pablo Henrique
authored
Merge pull request vuematerial#450 from igor-ribeiro/tabs-navigation-arrows
Tabs navigation arrows
2 parents 571635c + 500e5b5 commit 7823c37

File tree

4 files changed

+128
-25
lines changed

4 files changed

+128
-25
lines changed

docs/src/pages/components/Tabs.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252
<md-table-cell><code>Number</code></md-table-cell>
5353
<md-table-cell>Add a shadow on the navigation with an whiteframe. Default <code>0</code></md-table-cell>
5454
</md-table-row>
55+
56+
<md-table-row>
57+
<md-table-cell>md-navigation</md-table-cell>
58+
<md-table-cell><code>Boolean</code></md-table-cell>
59+
<md-table-cell>Display the navigation arrows for horizontal scroll. Default <code>true</code></md-table-cell>
60+
</md-table-row>
5561
</md-table-body>
5662
</md-table>
5763

src/components/mdTabs/mdTabs.scss

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ $tab-max-width: 264px;
2929
transition: $swift-ease-out;
3030
overflow: hidden;
3131

32+
&.md-has-navigation-scroll {
33+
.md-tab-header-navigation-button.md-left {
34+
order: 1;
35+
}
36+
37+
.md-tabs-navigation-container {
38+
order: 2;
39+
}
40+
41+
.md-tab-header-navigation-button.md-right {
42+
order: 3;
43+
}
44+
45+
.md-tabs-navigation-container {
46+
@include layout-small-and-up {
47+
margin-bottom: -15px;
48+
}
49+
}
50+
}
51+
3252
&.md-has-icon.md-has-label {
3353
min-height: 72px;
3454

@@ -60,11 +80,11 @@ $tab-max-width: 264px;
6080

6181
.md-tabs-navigation-container {
6282
display: flex;
63-
overflow-x: scroll;
83+
overflow-x: auto;
84+
}
6485

65-
@include layout-small-and-up {
66-
margin-bottom: -15px;
67-
}
86+
.md-tabs-navigation-scroll-container {
87+
display: flex;
6888
}
6989

7090
.md-tab-header {
@@ -126,6 +146,26 @@ $tab-max-width: 264px;
126146
}
127147
}
128148

149+
.md-tab-header-navigation-button {
150+
border: none;
151+
height: 100%;
152+
cursor: pointer;
153+
position: relative;
154+
155+
&.md-left {
156+
left: 0;
157+
}
158+
159+
&.md-right {
160+
right: 0;
161+
}
162+
163+
&.md-disabled {
164+
pointer-events: none;
165+
opacity: .4;
166+
}
167+
}
168+
129169
.md-tabs-content {
130170
width: 100%;
131171
height: 0;

src/components/mdTabs/mdTabs.theme

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
.md-tab-indicator {
2020
background-color: #{'ACCENT-COLOR'};
2121
}
22+
23+
.md-tab-header-navigation-button {
24+
color: #{'PRIMARY-CONTRAST-0.54'};
25+
background-color: #{'PRIMARY-COLOR'};
26+
}
2227
}
2328

2429
&.md-transparent {

src/components/mdTabs/mdTabs.vue

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
<template>
22
<div class="md-tabs" :class="[themeClass, tabClasses]">
33
<md-whiteframe md-tag="nav" class="md-tabs-navigation" :md-elevation="mdElevation" :class="navigationClasses" ref="tabNavigation">
4-
<div class="md-tabs-navigation-container" ref="tabsContainer" @scroll="calculateIndicatorPos">
5-
<button
6-
v-for="header in tabList"
7-
:key="header.id"
8-
type="button"
9-
class="md-tab-header"
10-
:class="getHeaderClass(header)"
11-
:disabled="header.disabled"
12-
@click="setActiveTab(header)"
13-
ref="tabHeader">
14-
<md-ink-ripple :md-disabled="header.disabled"></md-ink-ripple>
15-
<div class="md-tab-header-container">
16-
<slot name="header-item" :header="header">
17-
<md-icon v-if="header.icon">{{ header.icon }}</md-icon>
18-
<span v-if="header.label">{{ header.label }}</span>
19-
</slot>
20-
<md-tooltip v-if="header.tooltip" :md-direction="header.tooltipDirection" :md-delay="header.tooltipDelay">{{ header.tooltip }}</md-tooltip>
21-
</div>
22-
</button>
4+
<div class="md-tabs-navigation-container" ref="tabsContainer" @scroll="handleNavigationScroll">
5+
<div class="md-tabs-navigation-scroll-container">
6+
<button
7+
v-for="header in tabList"
8+
:key="header.id"
9+
type="button"
10+
class="md-tab-header"
11+
:class="getHeaderClass(header)"
12+
:disabled="header.disabled"
13+
@click="setActiveTab(header)"
14+
ref="tabHeader">
15+
<md-ink-ripple :md-disabled="header.disabled"></md-ink-ripple>
16+
<div class="md-tab-header-container">
17+
<md-icon v-if="header.icon">{{ header.icon }}</md-icon>
18+
<span v-if="header.label">{{ header.label }}</span>
19+
<md-tooltip v-if="header.tooltip" :md-direction="header.tooltipDirection" :md-delay="header.tooltipDelay">{{ header.tooltip }}</md-tooltip>
20+
</div>
21+
</button>
2322

24-
<span class="md-tab-indicator" :class="indicatorClasses" ref="indicator"></span>
23+
<span class="md-tab-indicator" :class="indicatorClasses" ref="indicator"></span>
24+
</div>
2525
</div>
26+
<button v-if="mdNavigation && hasNavigationScroll" @click="navigationScrollLeft" class="md-tab-header-navigation-button md-left" :class="navigationLeftButtonClasses">
27+
<md-icon>keyboard_arrow_left</md-icon>
28+
</button>
29+
<button v-if="mdNavigation && hasNavigationScroll" @click="navigationScrollRight" class="md-tab-header-navigation-button md-right" :class="navigationRightButtonClasses">
30+
<md-icon>keyboard_arrow_right</md-icon>
31+
</button>
2632
</md-whiteframe>
2733

2834
<div class="md-tabs-content" ref="tabContent" :style="{ height: contentHeight }">
@@ -45,6 +51,10 @@
4551
mdFixed: Boolean,
4652
mdCentered: Boolean,
4753
mdRight: Boolean,
54+
mdNavigation: {
55+
type: Boolean,
56+
default: true
57+
},
4858
mdDynamicHeight: {
4959
type: Boolean,
5060
default: true
@@ -61,6 +71,9 @@
6171
activeTabNumber: 0,
6272
hasIcons: false,
6373
hasLabel: false,
74+
hasNavigationScroll: false,
75+
isNavigationOnStart: true,
76+
isNavigationOnEnd: false,
6477
transitionControl: null,
6578
transitionOff: false,
6679
contentHeight: '0px',
@@ -79,7 +92,8 @@
7992
'md-has-label': this.hasLabel,
8093
'md-fixed': this.mdFixed,
8194
'md-right': !this.mdCentered && this.mdRight,
82-
'md-centered': this.mdCentered || this.mdFixed
95+
'md-centered': this.mdCentered || this.mdFixed,
96+
'md-has-navigation-scroll': this.hasNavigationScroll
8397
};
8498
},
8599
indicatorClasses() {
@@ -92,6 +106,16 @@
92106
'md-to-right': !toLeft,
93107
'md-to-left': toLeft
94108
};
109+
},
110+
navigationLeftButtonClasses() {
111+
return {
112+
'md-disabled': this.isNavigationOnStart
113+
};
114+
},
115+
navigationRightButtonClasses() {
116+
return {
117+
'md-disabled': this.isNavigationOnEnd
118+
};
95119
}
96120
},
97121
methods: {
@@ -178,6 +202,7 @@
178202
this.calculateIndicatorPos();
179203
this.calculateTabsWidthAndPosition();
180204
this.calculateContentHeight();
205+
this.checkNavigationScroll();
181206
});
182207
},
183208
debounceTransition() {
@@ -195,13 +220,40 @@
195220
this.transitionOff = true;
196221
this.calculateOnWatch();
197222
},
223+
calculateScrollPos() {
224+
const { scrollLeft, scrollWidth, clientWidth } = this.$refs.tabsContainer;
225+
226+
this.isNavigationOnStart = scrollLeft < 32;
227+
this.isNavigationOnEnd = scrollWidth - scrollLeft - 32 < clientWidth;
228+
},
229+
handleNavigationScroll() {
230+
window.requestAnimationFrame(() => {
231+
this.calculateIndicatorPos();
232+
this.calculateScrollPos();
233+
});
234+
},
235+
checkNavigationScroll() {
236+
const { scrollWidth, clientWidth } = this.$refs.tabsContainer;
237+
238+
this.hasNavigationScroll = scrollWidth > clientWidth;
239+
},
198240
setActiveTab(tabData) {
199241
this.hasIcons = !!tabData.icon;
200242
this.hasLabel = !!tabData.label;
201243
this.activeTab = tabData.id;
202244
this.activeTabNumber = this.getTabIndex(this.activeTab);
203245
this.calculatePosition();
204246
this.$emit('change', this.activeTabNumber);
247+
},
248+
navigationScrollLeft() {
249+
const { scrollLeft, clientWidth } = this.$refs.tabsContainer;
250+
251+
this.$refs.tabsContainer.scrollLeft = Math.max(0, scrollLeft - clientWidth);
252+
},
253+
navigationScrollRight() {
254+
const { scrollLeft, clientWidth, scrollWidth } = this.$refs.tabsContainer;
255+
256+
this.$refs.tabsContainer.scrollLeft = Math.min(scrollWidth, scrollLeft + clientWidth);
205257
}
206258
},
207259
mounted() {

0 commit comments

Comments
 (0)