@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nixweb/nixloc-ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Componentes UI",
5
5
  "author": "Fábio Ávila <fabio@nixweb.com.br>",
6
6
  "private": false,
@@ -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
- let tabs = [];
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
- var container = document.getElementById("custom-tab__BV_tab_controls_");
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 == "Auditoria") {
96
+ if (title === "Auditoria") {
61
97
  this.addEvent({ name: "auditLogGetAll" });
62
98
  }
63
99
  },
100
+
64
101
  title(index) {
65
- var ret = this.initialTabs.find((x) => x.index == index);
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
- var container = document.getElementById("custom-tab__BV_tab_controls_");
70
- this.sideScroll(container, "right", 25, 100, 10);
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
- var container = document.getElementById("custom-tab__BV_tab_controls_");
74
- this.sideScroll(container, "left", 25, 100, 10);
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
- sideScroll(element, direction, speed, distance, step) {
77
- let scrollAmount = 0;
78
- var slideTimer = setInterval(function () {
79
- if (direction == "left") {
80
- element.scrollLeft -= step;
81
- } else {
82
- element.scrollLeft += step;
83
- }
84
- scrollAmount += step;
85
- if (scrollAmount >= distance) {
86
- window.clearInterval(slideTimer);
87
- }
88
- }, speed);
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>