@nixweb/nixloc-ui 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/component/layout/Tab.vue +144 -29
package/package.json
CHANGED
|
@@ -3,19 +3,35 @@
|
|
|
3
3
|
<b-tabs v-model="tabIndex" id="custom-tab">
|
|
4
4
|
<template #tabs-start v-if="tabs.length > 8">
|
|
5
5
|
<div class="arrow-left">
|
|
6
|
-
<span class="icon">
|
|
6
|
+
<span v-if="hiddenLeft > 0" class="icon">
|
|
7
7
|
<i class="fa-solid fa-circle-chevron-left" @click="back()"></i>
|
|
8
8
|
</span>
|
|
9
|
+
<span v-else>
|
|
10
|
+
<span class="icon-disabled">
|
|
11
|
+
<i class="fa-solid fa-circle-chevron-left"></i>
|
|
12
|
+
</span>
|
|
13
|
+
</span>
|
|
14
|
+
<span v-if="hiddenLeft > 0" class="badge-tabs badge-left">
|
|
15
|
+
<i class="fa-solid fa-layer-plus"></i>
|
|
16
|
+
</span>
|
|
9
17
|
</div>
|
|
10
18
|
</template>
|
|
19
|
+
|
|
11
20
|
<b-tab @click="execute(title(i).title)" v-for="i in tabs" :key="'dyn-tab-' + i" :title="title(i).title">
|
|
12
21
|
<slot :name="i"></slot>
|
|
13
22
|
</b-tab>
|
|
23
|
+
|
|
14
24
|
<template #tabs-end v-if="tabs.length > 8">
|
|
15
25
|
<div class="arrow-right">
|
|
16
|
-
<span class="icon">
|
|
26
|
+
<span v-if="hiddenRight > 0" class="icon">
|
|
17
27
|
<i class="fa-solid fa-circle-chevron-right" @click="prev()"></i>
|
|
18
28
|
</span>
|
|
29
|
+
<span v-else class="icon-disabled">
|
|
30
|
+
<i class="fa-solid fa-circle-chevron-right"></i>
|
|
31
|
+
</span>
|
|
32
|
+
<span v-if="hiddenRight > 0" class="badge-tabs badge-right">
|
|
33
|
+
<i class="fa-solid fa-layer-plus"></i>
|
|
34
|
+
</span>
|
|
19
35
|
</div>
|
|
20
36
|
</template>
|
|
21
37
|
</b-tabs>
|
|
@@ -23,8 +39,7 @@
|
|
|
23
39
|
</template>
|
|
24
40
|
|
|
25
41
|
<script>
|
|
26
|
-
|
|
27
|
-
import { mapMutations } from "vuex";
|
|
42
|
+
import { mapMutations } from "vuex";
|
|
28
43
|
|
|
29
44
|
export default {
|
|
30
45
|
props: {
|
|
@@ -34,6 +49,9 @@ export default {
|
|
|
34
49
|
data() {
|
|
35
50
|
return {
|
|
36
51
|
tabIndex: 0,
|
|
52
|
+
hiddenLeft: 0,
|
|
53
|
+
hiddenRight: 0,
|
|
54
|
+
_resizeObs: null,
|
|
37
55
|
};
|
|
38
56
|
},
|
|
39
57
|
created() {
|
|
@@ -41,60 +59,124 @@ export default {
|
|
|
41
59
|
},
|
|
42
60
|
computed: {
|
|
43
61
|
tabs() {
|
|
44
|
-
|
|
45
|
-
this.initialTabs.forEach((item) =>
|
|
46
|
-
tabs.push(item.index);
|
|
47
|
-
});
|
|
62
|
+
const tabs = [];
|
|
63
|
+
this.initialTabs.forEach((item) => tabs.push(item.index));
|
|
48
64
|
return tabs;
|
|
49
65
|
},
|
|
50
66
|
},
|
|
51
67
|
mounted() {
|
|
52
|
-
|
|
68
|
+
const container = document.getElementById("custom-tab__BV_tab_controls_");
|
|
69
|
+
if (!container) return;
|
|
70
|
+
|
|
53
71
|
container.classList.remove("nav");
|
|
54
72
|
if (this.tabs.length > 8) container.classList.add("nav-custom");
|
|
55
73
|
if (this.tabs.length <= 8) container.classList.add("nav-default");
|
|
74
|
+
|
|
75
|
+
// Atualiza contadores ao rolar e ao redimensionar
|
|
76
|
+
container.addEventListener("scroll", this.updateHiddenTabs, { passive: true });
|
|
77
|
+
window.addEventListener("resize", this.updateHiddenTabs);
|
|
78
|
+
|
|
79
|
+
// Observa mudanças de tamanho do container para recalcular
|
|
80
|
+
this._resizeObs = new ResizeObserver(() => this.updateHiddenTabs());
|
|
81
|
+
this._resizeObs.observe(container);
|
|
82
|
+
|
|
83
|
+
// Inicial
|
|
84
|
+
this.$nextTick(this.updateHiddenTabs);
|
|
85
|
+
},
|
|
86
|
+
beforeDestroy() {
|
|
87
|
+
const container = document.getElementById("custom-tab__BV_tab_controls_");
|
|
88
|
+
if (container) container.removeEventListener("scroll", this.updateHiddenTabs);
|
|
89
|
+
window.removeEventListener("resize", this.updateHiddenTabs);
|
|
90
|
+
if (this._resizeObs) this._resizeObs.disconnect();
|
|
56
91
|
},
|
|
57
92
|
methods: {
|
|
58
93
|
...mapMutations("generic", ["addEvent"]),
|
|
94
|
+
|
|
59
95
|
execute(title) {
|
|
60
|
-
if (title
|
|
96
|
+
if (title === "Auditoria") {
|
|
61
97
|
this.addEvent({ name: "auditLogGetAll" });
|
|
62
98
|
}
|
|
63
99
|
},
|
|
100
|
+
|
|
64
101
|
title(index) {
|
|
65
|
-
|
|
66
|
-
return ret;
|
|
102
|
+
return this.initialTabs.find((x) => x.index == index);
|
|
67
103
|
},
|
|
104
|
+
|
|
105
|
+
// 👉 Scroll para a direita (próximas tabs)
|
|
68
106
|
prev() {
|
|
69
|
-
|
|
70
|
-
|
|
107
|
+
const container = document.getElementById("custom-tab__BV_tab_controls_");
|
|
108
|
+
if (!container) return;
|
|
109
|
+
const chunk = this._calcScrollChunk(container); // ~90% da largura visível
|
|
110
|
+
this._smoothScrollBy(container, chunk);
|
|
111
|
+
// updateHiddenTabs será disparado pelo evento 'scroll'
|
|
71
112
|
},
|
|
113
|
+
|
|
114
|
+
// 👉 Scroll para a esquerda (tabs anteriores)
|
|
72
115
|
back() {
|
|
73
|
-
|
|
74
|
-
|
|
116
|
+
const container = document.getElementById("custom-tab__BV_tab_controls_");
|
|
117
|
+
if (!container) return;
|
|
118
|
+
const chunk = this._calcScrollChunk(container);
|
|
119
|
+
this._smoothScrollBy(container, -chunk);
|
|
75
120
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
121
|
+
|
|
122
|
+
// ===== CÁLCULO DO "SALTO" IDEAL =====
|
|
123
|
+
_calcScrollChunk(container) {
|
|
124
|
+
// Rola ~90% da área visível; no mínimo 240px
|
|
125
|
+
return Math.max(Math.floor(container.clientWidth * 0.9), 240);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// ===== SMOOTH SCROLL COM FALLBACK =====
|
|
129
|
+
_smoothScrollBy(el, dx) {
|
|
130
|
+
// preferir nativo (quando disponível)
|
|
131
|
+
try {
|
|
132
|
+
el.scrollBy({ left: dx, behavior: "smooth" });
|
|
133
|
+
return;
|
|
134
|
+
} catch (_) {
|
|
135
|
+
// fallback
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// fallback com requestAnimationFrame
|
|
139
|
+
const duration = 250; // ms
|
|
140
|
+
const start = el.scrollLeft;
|
|
141
|
+
const target = start + dx;
|
|
142
|
+
const startTime = performance.now();
|
|
143
|
+
|
|
144
|
+
const step = (now) => {
|
|
145
|
+
const t = Math.min(1, (now - startTime) / duration);
|
|
146
|
+
// ease-out cúbico
|
|
147
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
148
|
+
el.scrollLeft = start + (target - start) * eased;
|
|
149
|
+
if (t < 1) requestAnimationFrame(step);
|
|
150
|
+
};
|
|
151
|
+
requestAnimationFrame(step);
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// ===== Indicação de tabs ocultas =====
|
|
155
|
+
updateHiddenTabs() {
|
|
156
|
+
const container = document.getElementById("custom-tab__BV_tab_controls_");
|
|
157
|
+
if (!container) return;
|
|
158
|
+
|
|
159
|
+
const totalTabs = this.tabs.length || 1; // evita divisão por zero
|
|
160
|
+
const avgWidth = container.scrollWidth / totalTabs;
|
|
161
|
+
|
|
162
|
+
// Ocultas à esquerda (pixels já rolados / largura média)
|
|
163
|
+
this.hiddenLeft = Math.max(0, Math.floor(container.scrollLeft / avgWidth));
|
|
164
|
+
|
|
165
|
+
// Ocultas à direita
|
|
166
|
+
const hiddenPx = container.scrollWidth - (container.scrollLeft + container.clientWidth);
|
|
167
|
+
this.hiddenRight = Math.max(0, Math.floor(hiddenPx / avgWidth));
|
|
89
168
|
},
|
|
90
169
|
},
|
|
91
170
|
watch: {
|
|
92
171
|
tabIndex(tabIndex) {
|
|
93
172
|
this.$emit("input", tabIndex);
|
|
173
|
+
// após troca, recalcula (caso mude largura ativa, etc.)
|
|
174
|
+
this.$nextTick(this.updateHiddenTabs);
|
|
94
175
|
},
|
|
95
176
|
},
|
|
96
177
|
};
|
|
97
178
|
</script>
|
|
179
|
+
|
|
98
180
|
<style>
|
|
99
181
|
.arrow-left {
|
|
100
182
|
position: absolute;
|
|
@@ -141,4 +223,37 @@ export default {
|
|
|
141
223
|
.icon:hover {
|
|
142
224
|
color: #577696;
|
|
143
225
|
}
|
|
226
|
+
|
|
227
|
+
.icon-disabled {
|
|
228
|
+
color: #c1c1c7;
|
|
229
|
+
font-size: 24px;
|
|
230
|
+
padding-left: 10px;
|
|
231
|
+
opacity: 0.5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.badge-tabs {
|
|
235
|
+
display: inline-block;
|
|
236
|
+
width: 20px;
|
|
237
|
+
height: 20px;
|
|
238
|
+
margin-left: 8px;
|
|
239
|
+
padding: 2px 5px;
|
|
240
|
+
font-size: 15px;
|
|
241
|
+
line-height: 1;
|
|
242
|
+
color: #D98621;
|
|
243
|
+
background: transparent;
|
|
244
|
+
border-radius: 10px;
|
|
245
|
+
vertical-align: middle;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.badge-left {
|
|
249
|
+
position: absolute;
|
|
250
|
+
margin-left: -60px;
|
|
251
|
+
margin-top: 13px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.badge-right {
|
|
255
|
+
position: absolute;
|
|
256
|
+
margin-left: 4px;
|
|
257
|
+
margin-top: 13px;
|
|
258
|
+
}
|
|
144
259
|
</style>
|