@regcheq/ui-vue-2 1.17.1 → 1.19.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/dist/index.css CHANGED
@@ -1320,14 +1320,76 @@
1320
1320
  color: #52525b;
1321
1321
  }
1322
1322
  .rq-tabs {
1323
+ position: relative;
1323
1324
  display: inline-flex;
1324
1325
  align-items: center;
1325
1326
  width: fit-content;
1327
+ max-width: 100%;
1326
1328
  background: rgba(0, 0, 0, 0.05);
1327
1329
  }
1328
- .rq-tabs--loading {
1330
+ .rq-tabs__viewport {
1331
+ display: flex;
1332
+ align-items: center;
1333
+ overflow-x: auto;
1334
+ overflow-y: hidden;
1335
+ padding: 3px;
1336
+ margin: -3px;
1337
+ min-width: 0;
1338
+ scrollbar-width: none;
1339
+ -ms-overflow-style: none;
1340
+ }
1341
+ .rq-tabs__viewport::-webkit-scrollbar {
1342
+ display: none;
1343
+ }
1344
+ .rq-tabs--loading .rq-tabs__viewport {
1329
1345
  gap: 4px;
1330
1346
  }
1347
+ .rq-tabs--scroll-prev .rq-tabs__viewport {
1348
+ -webkit-mask-image: linear-gradient(to right, transparent 0, #000 32px);
1349
+ mask-image: linear-gradient(to right, transparent 0, #000 32px);
1350
+ }
1351
+ .rq-tabs--scroll-next .rq-tabs__viewport {
1352
+ -webkit-mask-image: linear-gradient(to left, transparent 0, #000 32px);
1353
+ mask-image: linear-gradient(to left, transparent 0, #000 32px);
1354
+ }
1355
+ .rq-tabs--scroll-prev.rq-tabs--scroll-next .rq-tabs__viewport {
1356
+ -webkit-mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
1357
+ mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
1358
+ }
1359
+ .rq-tabs__scroll-btn {
1360
+ position: absolute;
1361
+ top: 50%;
1362
+ transform: translateY(-50%);
1363
+ z-index: 2;
1364
+ display: inline-flex;
1365
+ align-items: center;
1366
+ justify-content: center;
1367
+ flex-shrink: 0;
1368
+ padding: 0;
1369
+ background: #ffffff;
1370
+ border: 1px solid #e4e4e7;
1371
+ border-radius: 999px;
1372
+ box-shadow: 0px 1px 1px 0px rgba(15, 15, 21, 0.1);
1373
+ cursor: pointer;
1374
+ color: #18181b;
1375
+ outline: none;
1376
+ transition: background 100ms ease, box-shadow 100ms ease;
1377
+ }
1378
+ .rq-tabs__scroll-btn:hover {
1379
+ background: #fafafa;
1380
+ }
1381
+ .rq-tabs__scroll-btn:active {
1382
+ background: #f4f4f5;
1383
+ }
1384
+ .rq-tabs__scroll-btn:focus-visible {
1385
+ box-shadow: 0 0 0 2px #d4d4d8;
1386
+ }
1387
+ .rq-tabs__scroll-btn--prev {
1388
+ left: 2px;
1389
+ }
1390
+ .rq-tabs__scroll-btn--next {
1391
+ right: 2px;
1392
+ }
1331
1393
  .rq-tabs--regular {
1332
1394
  padding: 3px;
1333
1395
  border-radius: 12px;
@@ -1336,6 +1398,10 @@
1336
1398
  min-height: 29px;
1337
1399
  border-radius: 12px;
1338
1400
  }
1401
+ .rq-tabs--regular .rq-tabs__scroll-btn {
1402
+ width: 26px;
1403
+ height: 26px;
1404
+ }
1339
1405
  .rq-tabs--large {
1340
1406
  padding: 2px;
1341
1407
  border-radius: 12px;
@@ -1344,6 +1410,10 @@
1344
1410
  min-height: 32px;
1345
1411
  border-radius: 12px;
1346
1412
  }
1413
+ .rq-tabs--large .rq-tabs__scroll-btn {
1414
+ width: 28px;
1415
+ height: 28px;
1416
+ }
1347
1417
  .rq-tabs--small {
1348
1418
  padding: 2px;
1349
1419
  border-radius: 8px;
@@ -1352,11 +1422,16 @@
1352
1422
  min-height: 20px;
1353
1423
  border-radius: 8px;
1354
1424
  }
1425
+ .rq-tabs--small .rq-tabs__scroll-btn {
1426
+ width: 18px;
1427
+ height: 18px;
1428
+ }
1355
1429
  .rq-tabs__tab {
1356
1430
  display: inline-flex;
1357
1431
  align-items: center;
1358
1432
  justify-content: center;
1359
1433
  gap: 2px;
1434
+ flex-shrink: 0;
1360
1435
  padding: 2px 6px;
1361
1436
  background: transparent;
1362
1437
  border: none;
package/dist/index.esm.js CHANGED
@@ -33986,10 +33986,42 @@ const tabsVariants = cva('rq-tabs', {
33986
33986
  //
33987
33987
  //
33988
33988
  //
33989
+ //
33990
+ //
33991
+ //
33992
+ //
33993
+ //
33994
+ //
33995
+ //
33996
+ //
33997
+ //
33998
+ //
33999
+ //
34000
+ //
34001
+ //
34002
+ //
34003
+ //
34004
+ //
34005
+ //
34006
+ //
34007
+ //
34008
+ //
34009
+ //
34010
+ //
33989
34011
 
33990
34012
 
33991
34013
  // Anchos fijos para que los pills fantasma no se vean todos iguales.
33992
34014
  const SKELETON_WIDTH_PATTERN = [70, 90, 60];
34015
+
34016
+ // Proporción del ancho visible que avanza cada click en las flechas.
34017
+ const SCROLL_PAGE_RATIO = 0.8;
34018
+
34019
+ // Aire que queda entre el tab acercado y el borde del viewport.
34020
+ const SCROLL_EDGE_MARGIN = 8;
34021
+
34022
+ // Los anchos de scroll son subpixel: sin tolerancia la flecha queda visible
34023
+ // al final del recorrido por diferencias de decimales.
34024
+ const SCROLL_EPSILON = 1;
33993
34025
  var script$g = {
33994
34026
  name: 'Tabs',
33995
34027
  components: {
@@ -34019,17 +34051,34 @@ var script$g = {
34019
34051
  default: 3
34020
34052
  }
34021
34053
  },
34054
+ data() {
34055
+ return {
34056
+ canScrollPrev: false,
34057
+ canScrollNext: false
34058
+ };
34059
+ },
34022
34060
  computed: {
34023
34061
  classes() {
34024
34062
  return [tabsVariants({
34025
34063
  size: this.size
34026
34064
  }), {
34027
- 'rq-tabs--loading': this.loading
34065
+ 'rq-tabs--loading': this.loading,
34066
+ 'rq-tabs--scroll-prev': this.canScrollPrev,
34067
+ 'rq-tabs--scroll-next': this.canScrollNext
34028
34068
  }];
34029
34069
  },
34030
34070
  iconSize() {
34031
34071
  return this.size === 'small' ? 16 : 20;
34032
34072
  },
34073
+ scrollIconSize() {
34074
+ return this.size === 'small' ? 12 : 16;
34075
+ },
34076
+ chevronLeftIcon() {
34077
+ return ChevronLeft;
34078
+ },
34079
+ chevronRightIcon() {
34080
+ return ChevronRight;
34081
+ },
34033
34082
  processedTabs() {
34034
34083
  return this.tabs.map(tab => ({
34035
34084
  ...tab,
@@ -34045,6 +34094,27 @@ var script$g = {
34045
34094
  return this.size === 'small' ? '12px' : '16px';
34046
34095
  }
34047
34096
  },
34097
+ watch: {
34098
+ tabs() {
34099
+ this.$nextTick(this.updateScrollState);
34100
+ },
34101
+ loading() {
34102
+ this.$nextTick(this.updateScrollState);
34103
+ },
34104
+ value() {
34105
+ this.$nextTick(this.scrollActiveIntoView);
34106
+ }
34107
+ },
34108
+ mounted() {
34109
+ this.updateScrollState();
34110
+ this.observeViewportResize();
34111
+ },
34112
+ beforeDestroy() {
34113
+ if (this._resizeObserver) {
34114
+ this._resizeObserver.disconnect();
34115
+ this._resizeObserver = null;
34116
+ }
34117
+ },
34048
34118
  methods: {
34049
34119
  tabClasses(tab) {
34050
34120
  return ['rq-tabs__tab', {
@@ -34055,6 +34125,67 @@ var script$g = {
34055
34125
  },
34056
34126
  selectTab(tab) {
34057
34127
  if (!tab.disabled) this.$emit('input', tab.id);
34128
+ },
34129
+ // El ancho del viewport depende del contenedor padre, no de props: sin
34130
+ // observarlo las flechas quedan desfasadas al redimensionar.
34131
+ observeViewportResize() {
34132
+ const viewport = this.$refs.viewport;
34133
+ if (!viewport || typeof ResizeObserver === 'undefined') return;
34134
+ this._resizeObserver = new ResizeObserver(() => this.updateScrollState());
34135
+ this._resizeObserver.observe(viewport);
34136
+ },
34137
+ updateScrollState() {
34138
+ const viewport = this.$refs.viewport;
34139
+ if (!viewport || this.loading) {
34140
+ this.canScrollPrev = false;
34141
+ this.canScrollNext = false;
34142
+ return;
34143
+ }
34144
+ const maxScroll = viewport.scrollWidth - viewport.clientWidth;
34145
+ this.canScrollPrev = viewport.scrollLeft > SCROLL_EPSILON;
34146
+ this.canScrollNext = viewport.scrollLeft < maxScroll - SCROLL_EPSILON;
34147
+ },
34148
+ scrollByPage(direction) {
34149
+ const viewport = this.$refs.viewport;
34150
+ if (!viewport) return;
34151
+ const delta = direction * Math.round(viewport.clientWidth * SCROLL_PAGE_RATIO);
34152
+ if (typeof viewport.scrollBy === 'function') {
34153
+ viewport.scrollBy({
34154
+ left: delta,
34155
+ behavior: 'smooth'
34156
+ });
34157
+ } else {
34158
+ viewport.scrollLeft += delta;
34159
+ }
34160
+ },
34161
+ // Al cambiar de tab por fuera del click (teclado, v-model del consumidor)
34162
+ // el activo puede quedar fuera de vista.
34163
+ scrollActiveIntoView() {
34164
+ const viewport = this.$refs.viewport;
34165
+ if (!viewport) return;
34166
+ const active = viewport.querySelector('.rq-tabs__tab--active');
34167
+ if (!active) return;
34168
+ const start = active.offsetLeft;
34169
+ const end = start + active.offsetWidth;
34170
+ const viewStart = viewport.scrollLeft;
34171
+ const viewEnd = viewStart + viewport.clientWidth;
34172
+ if (start < viewStart) {
34173
+ this.scrollToLeft(start - SCROLL_EDGE_MARGIN);
34174
+ } else if (end > viewEnd) {
34175
+ this.scrollToLeft(end - viewport.clientWidth + SCROLL_EDGE_MARGIN);
34176
+ }
34177
+ },
34178
+ scrollToLeft(left) {
34179
+ const viewport = this.$refs.viewport;
34180
+ const target = Math.max(0, left);
34181
+ if (typeof viewport.scrollTo === 'function') {
34182
+ viewport.scrollTo({
34183
+ left: target,
34184
+ behavior: 'smooth'
34185
+ });
34186
+ } else {
34187
+ viewport.scrollLeft = target;
34188
+ }
34058
34189
  }
34059
34190
  }
34060
34191
  };
@@ -34069,7 +34200,29 @@ var __vue_render__$g = function () {
34069
34200
  var _c = _vm._self._c || _h;
34070
34201
  return _c("div", _vm._g({
34071
34202
  class: _vm.classes
34072
- }, _vm.$listeners), [_vm.loading ? _vm._l(_vm.skeletonWidths, function (w, i) {
34203
+ }, _vm.$listeners), [_vm.canScrollPrev ? _c("button", {
34204
+ staticClass: "rq-tabs__scroll-btn rq-tabs__scroll-btn--prev",
34205
+ attrs: {
34206
+ type: "button",
34207
+ "aria-label": "Desplazar tabs a la izquierda"
34208
+ },
34209
+ on: {
34210
+ click: function ($event) {
34211
+ return _vm.scrollByPage(-1);
34212
+ }
34213
+ }
34214
+ }, [_c(_vm.chevronLeftIcon, {
34215
+ tag: "component",
34216
+ attrs: {
34217
+ size: _vm.scrollIconSize
34218
+ }
34219
+ })], 1) : _vm._e(), _vm._v(" "), _c("div", {
34220
+ ref: "viewport",
34221
+ staticClass: "rq-tabs__viewport",
34222
+ on: {
34223
+ scroll: _vm.updateScrollState
34224
+ }
34225
+ }, [_vm.loading ? _vm._l(_vm.skeletonWidths, function (w, i) {
34073
34226
  return _c("div", {
34074
34227
  key: "skeleton-" + i,
34075
34228
  staticClass: "rq-tabs__tab rq-tabs__tab--skeleton"
@@ -34103,7 +34256,23 @@ var __vue_render__$g = function () {
34103
34256
  }, [_vm._v(_vm._s(tab.label))]) : _vm._e(), _vm._v(" "), tab.counter !== undefined ? _c("span", {
34104
34257
  staticClass: "rq-tabs__tab-counter"
34105
34258
  }, [_vm._v(_vm._s(tab.counter))]) : _vm._e()], 1);
34106
- })], 2);
34259
+ })], 2), _vm._v(" "), _vm.canScrollNext ? _c("button", {
34260
+ staticClass: "rq-tabs__scroll-btn rq-tabs__scroll-btn--next",
34261
+ attrs: {
34262
+ type: "button",
34263
+ "aria-label": "Desplazar tabs a la derecha"
34264
+ },
34265
+ on: {
34266
+ click: function ($event) {
34267
+ return _vm.scrollByPage(1);
34268
+ }
34269
+ }
34270
+ }, [_c(_vm.chevronRightIcon, {
34271
+ tag: "component",
34272
+ attrs: {
34273
+ size: _vm.scrollIconSize
34274
+ }
34275
+ })], 1) : _vm._e()]);
34107
34276
  };
34108
34277
  var __vue_staticRenderFns__$g = [];
34109
34278
  __vue_render__$g._withStripped = true;
package/dist/index.js CHANGED
@@ -33988,10 +33988,42 @@ const tabsVariants = cva('rq-tabs', {
33988
33988
  //
33989
33989
  //
33990
33990
  //
33991
+ //
33992
+ //
33993
+ //
33994
+ //
33995
+ //
33996
+ //
33997
+ //
33998
+ //
33999
+ //
34000
+ //
34001
+ //
34002
+ //
34003
+ //
34004
+ //
34005
+ //
34006
+ //
34007
+ //
34008
+ //
34009
+ //
34010
+ //
34011
+ //
34012
+ //
33991
34013
 
33992
34014
 
33993
34015
  // Anchos fijos para que los pills fantasma no se vean todos iguales.
33994
34016
  const SKELETON_WIDTH_PATTERN = [70, 90, 60];
34017
+
34018
+ // Proporción del ancho visible que avanza cada click en las flechas.
34019
+ const SCROLL_PAGE_RATIO = 0.8;
34020
+
34021
+ // Aire que queda entre el tab acercado y el borde del viewport.
34022
+ const SCROLL_EDGE_MARGIN = 8;
34023
+
34024
+ // Los anchos de scroll son subpixel: sin tolerancia la flecha queda visible
34025
+ // al final del recorrido por diferencias de decimales.
34026
+ const SCROLL_EPSILON = 1;
33995
34027
  var script$g = {
33996
34028
  name: 'Tabs',
33997
34029
  components: {
@@ -34021,17 +34053,34 @@ var script$g = {
34021
34053
  default: 3
34022
34054
  }
34023
34055
  },
34056
+ data() {
34057
+ return {
34058
+ canScrollPrev: false,
34059
+ canScrollNext: false
34060
+ };
34061
+ },
34024
34062
  computed: {
34025
34063
  classes() {
34026
34064
  return [tabsVariants({
34027
34065
  size: this.size
34028
34066
  }), {
34029
- 'rq-tabs--loading': this.loading
34067
+ 'rq-tabs--loading': this.loading,
34068
+ 'rq-tabs--scroll-prev': this.canScrollPrev,
34069
+ 'rq-tabs--scroll-next': this.canScrollNext
34030
34070
  }];
34031
34071
  },
34032
34072
  iconSize() {
34033
34073
  return this.size === 'small' ? 16 : 20;
34034
34074
  },
34075
+ scrollIconSize() {
34076
+ return this.size === 'small' ? 12 : 16;
34077
+ },
34078
+ chevronLeftIcon() {
34079
+ return ChevronLeft;
34080
+ },
34081
+ chevronRightIcon() {
34082
+ return ChevronRight;
34083
+ },
34035
34084
  processedTabs() {
34036
34085
  return this.tabs.map(tab => ({
34037
34086
  ...tab,
@@ -34047,6 +34096,27 @@ var script$g = {
34047
34096
  return this.size === 'small' ? '12px' : '16px';
34048
34097
  }
34049
34098
  },
34099
+ watch: {
34100
+ tabs() {
34101
+ this.$nextTick(this.updateScrollState);
34102
+ },
34103
+ loading() {
34104
+ this.$nextTick(this.updateScrollState);
34105
+ },
34106
+ value() {
34107
+ this.$nextTick(this.scrollActiveIntoView);
34108
+ }
34109
+ },
34110
+ mounted() {
34111
+ this.updateScrollState();
34112
+ this.observeViewportResize();
34113
+ },
34114
+ beforeDestroy() {
34115
+ if (this._resizeObserver) {
34116
+ this._resizeObserver.disconnect();
34117
+ this._resizeObserver = null;
34118
+ }
34119
+ },
34050
34120
  methods: {
34051
34121
  tabClasses(tab) {
34052
34122
  return ['rq-tabs__tab', {
@@ -34057,6 +34127,67 @@ var script$g = {
34057
34127
  },
34058
34128
  selectTab(tab) {
34059
34129
  if (!tab.disabled) this.$emit('input', tab.id);
34130
+ },
34131
+ // El ancho del viewport depende del contenedor padre, no de props: sin
34132
+ // observarlo las flechas quedan desfasadas al redimensionar.
34133
+ observeViewportResize() {
34134
+ const viewport = this.$refs.viewport;
34135
+ if (!viewport || typeof ResizeObserver === 'undefined') return;
34136
+ this._resizeObserver = new ResizeObserver(() => this.updateScrollState());
34137
+ this._resizeObserver.observe(viewport);
34138
+ },
34139
+ updateScrollState() {
34140
+ const viewport = this.$refs.viewport;
34141
+ if (!viewport || this.loading) {
34142
+ this.canScrollPrev = false;
34143
+ this.canScrollNext = false;
34144
+ return;
34145
+ }
34146
+ const maxScroll = viewport.scrollWidth - viewport.clientWidth;
34147
+ this.canScrollPrev = viewport.scrollLeft > SCROLL_EPSILON;
34148
+ this.canScrollNext = viewport.scrollLeft < maxScroll - SCROLL_EPSILON;
34149
+ },
34150
+ scrollByPage(direction) {
34151
+ const viewport = this.$refs.viewport;
34152
+ if (!viewport) return;
34153
+ const delta = direction * Math.round(viewport.clientWidth * SCROLL_PAGE_RATIO);
34154
+ if (typeof viewport.scrollBy === 'function') {
34155
+ viewport.scrollBy({
34156
+ left: delta,
34157
+ behavior: 'smooth'
34158
+ });
34159
+ } else {
34160
+ viewport.scrollLeft += delta;
34161
+ }
34162
+ },
34163
+ // Al cambiar de tab por fuera del click (teclado, v-model del consumidor)
34164
+ // el activo puede quedar fuera de vista.
34165
+ scrollActiveIntoView() {
34166
+ const viewport = this.$refs.viewport;
34167
+ if (!viewport) return;
34168
+ const active = viewport.querySelector('.rq-tabs__tab--active');
34169
+ if (!active) return;
34170
+ const start = active.offsetLeft;
34171
+ const end = start + active.offsetWidth;
34172
+ const viewStart = viewport.scrollLeft;
34173
+ const viewEnd = viewStart + viewport.clientWidth;
34174
+ if (start < viewStart) {
34175
+ this.scrollToLeft(start - SCROLL_EDGE_MARGIN);
34176
+ } else if (end > viewEnd) {
34177
+ this.scrollToLeft(end - viewport.clientWidth + SCROLL_EDGE_MARGIN);
34178
+ }
34179
+ },
34180
+ scrollToLeft(left) {
34181
+ const viewport = this.$refs.viewport;
34182
+ const target = Math.max(0, left);
34183
+ if (typeof viewport.scrollTo === 'function') {
34184
+ viewport.scrollTo({
34185
+ left: target,
34186
+ behavior: 'smooth'
34187
+ });
34188
+ } else {
34189
+ viewport.scrollLeft = target;
34190
+ }
34060
34191
  }
34061
34192
  }
34062
34193
  };
@@ -34071,7 +34202,29 @@ var __vue_render__$g = function () {
34071
34202
  var _c = _vm._self._c || _h;
34072
34203
  return _c("div", _vm._g({
34073
34204
  class: _vm.classes
34074
- }, _vm.$listeners), [_vm.loading ? _vm._l(_vm.skeletonWidths, function (w, i) {
34205
+ }, _vm.$listeners), [_vm.canScrollPrev ? _c("button", {
34206
+ staticClass: "rq-tabs__scroll-btn rq-tabs__scroll-btn--prev",
34207
+ attrs: {
34208
+ type: "button",
34209
+ "aria-label": "Desplazar tabs a la izquierda"
34210
+ },
34211
+ on: {
34212
+ click: function ($event) {
34213
+ return _vm.scrollByPage(-1);
34214
+ }
34215
+ }
34216
+ }, [_c(_vm.chevronLeftIcon, {
34217
+ tag: "component",
34218
+ attrs: {
34219
+ size: _vm.scrollIconSize
34220
+ }
34221
+ })], 1) : _vm._e(), _vm._v(" "), _c("div", {
34222
+ ref: "viewport",
34223
+ staticClass: "rq-tabs__viewport",
34224
+ on: {
34225
+ scroll: _vm.updateScrollState
34226
+ }
34227
+ }, [_vm.loading ? _vm._l(_vm.skeletonWidths, function (w, i) {
34075
34228
  return _c("div", {
34076
34229
  key: "skeleton-" + i,
34077
34230
  staticClass: "rq-tabs__tab rq-tabs__tab--skeleton"
@@ -34105,7 +34258,23 @@ var __vue_render__$g = function () {
34105
34258
  }, [_vm._v(_vm._s(tab.label))]) : _vm._e(), _vm._v(" "), tab.counter !== undefined ? _c("span", {
34106
34259
  staticClass: "rq-tabs__tab-counter"
34107
34260
  }, [_vm._v(_vm._s(tab.counter))]) : _vm._e()], 1);
34108
- })], 2);
34261
+ })], 2), _vm._v(" "), _vm.canScrollNext ? _c("button", {
34262
+ staticClass: "rq-tabs__scroll-btn rq-tabs__scroll-btn--next",
34263
+ attrs: {
34264
+ type: "button",
34265
+ "aria-label": "Desplazar tabs a la derecha"
34266
+ },
34267
+ on: {
34268
+ click: function ($event) {
34269
+ return _vm.scrollByPage(1);
34270
+ }
34271
+ }
34272
+ }, [_c(_vm.chevronRightIcon, {
34273
+ tag: "component",
34274
+ attrs: {
34275
+ size: _vm.scrollIconSize
34276
+ }
34277
+ })], 1) : _vm._e()]);
34109
34278
  };
34110
34279
  var __vue_staticRenderFns__$g = [];
34111
34280
  __vue_render__$g._withStripped = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@regcheq/ui-vue-2",
3
- "version": "1.17.1",
3
+ "version": "1.19.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "files": [