ketekny-ui-kit 1.0.26 → 1.0.28

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,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.26",
4
+ "version": "1.0.28",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -7,16 +7,20 @@
7
7
  h1, h2, h3, h4{
8
8
  color: theme('colors.primary');
9
9
  font-weight: bold;
10
+ line-height: 1.2;
10
11
  }
11
12
  h1{
12
- font-size: 2.5em;
13
+ font-size: clamp(1.625rem, 4vw + 0.75rem, 2.5rem);
13
14
  }
14
15
 
15
16
  h2{
16
- font-size: 2em;
17
+ font-size: clamp(1.375rem, 3vw + 0.7rem, 2rem);
17
18
  }
18
19
  h3{
19
- font-size: 1.5em;
20
+ font-size: clamp(1.125rem, 2vw + 0.6rem, 1.5rem);
21
+ }
22
+ h4{
23
+ font-size: clamp(1rem, 1.5vw + 0.55rem, 1.25rem);
20
24
  }
21
25
 
22
26
  .group{
@@ -36,4 +40,4 @@ li{
36
40
  @apply p-2 rounded cursor-pointer ;
37
41
 
38
42
  }
39
- }
43
+ }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :class="['rounded-lg border border-primary/15', disabled ? 'pointer-events-none opacity-60' : '']">
2
+ <div ref="tableWrapper" :class="['k-datatable-wrapper rounded-lg border border-primary/15', disabled ? 'pointer-events-none opacity-60' : '']">
3
3
  <EasyDataTable
4
4
  v-bind="$attrs"
5
5
  :headers="headers"
@@ -8,8 +8,10 @@
8
8
  :show-select="effectiveSelectionMode !== 'none'"
9
9
  :single-select="effectiveSelectionMode === 'single'"
10
10
  v-if="effectiveSelectionMode !== 'none'"
11
- v-model:items-selected="internalSelection"
11
+ :items-selected="internalSelection"
12
+ @update:items-selected="handleSelectionUpdate"
12
13
  :itemSelectable="itemSelectable"
14
+ :body-row-class-name="composeBodyRowClassName"
13
15
  alternating
14
16
  buttons-pagination
15
17
  table-class-name="customize-table"
@@ -21,7 +23,7 @@
21
23
  </EasyDataTable>
22
24
 
23
25
  <!-- Render separate table without v-model when no selection -->
24
- <EasyDataTable v-else v-bind="$attrs" :headers="headers" :items="items" :search="search" :show-select="false" alternating buttons-pagination table-class-name="customize-table">
26
+ <EasyDataTable v-else v-bind="$attrs" :headers="headers" :items="items" :search="search" :show-select="false" :body-row-class-name="composeBodyRowClassName" alternating buttons-pagination table-class-name="customize-table">
25
27
  <template v-for="(_, name) in $slots" :key="name" v-slot:[name]="slotProps">
26
28
  <slot :name="name" v-bind="slotProps" />
27
29
  </template>
@@ -69,8 +71,20 @@ export default {
69
71
  data() {
70
72
  return {
71
73
  internalSelection: this.normalizeModelValue(this.modelValue),
74
+ pendingSelectionInteraction: null,
75
+ lastSelectionAnchor: null,
76
+ lastSelectionAnchorRowNumber: null,
72
77
  };
73
78
  },
79
+ created() {
80
+ this.currentPageRowsCache = [];
81
+ },
82
+ mounted() {
83
+ this.bindSelectionInteractionListener();
84
+ },
85
+ beforeUnmount() {
86
+ this.unbindSelectionInteractionListener();
87
+ },
74
88
  computed: {
75
89
  effectiveSelectionMode() {
76
90
  return this.disabled ? "none" : this.selectionMode;
@@ -81,16 +95,183 @@ export default {
81
95
  modelValue(val) {
82
96
  this.internalSelection = this.normalizeModelValue(val);
83
97
  },
84
- internalSelection(newVal) {
98
+ },
99
+
100
+ methods: {
101
+ handleSelectionUpdate(newSelection) {
85
102
  if (this.selectionMode === "single") {
86
- this.$emit("update:modelValue", newVal[0] || null);
87
- } else if (this.selectionMode === "multiple") {
88
- this.$emit("update:modelValue", newVal);
103
+ const nextSelection = Array.isArray(newSelection) ? newSelection : [];
104
+ this.internalSelection = nextSelection;
105
+ this.$emit("update:modelValue", nextSelection[0] || null);
106
+ return;
107
+ }
108
+
109
+ if (this.selectionMode === "multiple") {
110
+ const oldSelection = Array.isArray(this.internalSelection) ? this.internalSelection : [];
111
+ const candidateSelection = Array.isArray(newSelection) ? newSelection : [];
112
+ const nextSelection = this.resolveShiftRangeSelection(candidateSelection, oldSelection);
113
+ this.internalSelection = nextSelection;
114
+ this.$emit("update:modelValue", nextSelection);
89
115
  }
90
116
  },
91
- },
117
+ bindSelectionInteractionListener() {
118
+ this.$refs.tableWrapper?.addEventListener("click", this.captureSelectionInteraction, true);
119
+ },
120
+ unbindSelectionInteractionListener() {
121
+ this.$refs.tableWrapper?.removeEventListener("click", this.captureSelectionInteraction, true);
122
+ },
123
+ captureSelectionInteraction(event) {
124
+ if (this.selectionMode !== "multiple" || this.disabled || !(event.target instanceof Element)) {
125
+ return;
126
+ }
92
127
 
93
- methods: {
128
+ const checkbox = event.target.closest(".easy-checkbox");
129
+ if (!checkbox) return;
130
+
131
+ // Ignore the header "select all" checkbox.
132
+ const row = checkbox.closest("tbody tr");
133
+ if (!row) return;
134
+
135
+ const rowNumber = this.resolveClickedRowNumber(row);
136
+ if (!rowNumber) return;
137
+ const clickedItem = this.currentPageRowsCache[rowNumber - 1];
138
+
139
+ if (!event.shiftKey && clickedItem) {
140
+ this.lastSelectionAnchor = clickedItem;
141
+ this.lastSelectionAnchorRowNumber = rowNumber;
142
+ }
143
+
144
+ if (event.shiftKey && this.lastSelectionAnchor && clickedItem) {
145
+ const anchorIndex = this.findItemIndex(this.lastSelectionAnchor);
146
+ const targetIndex = this.findItemIndex(clickedItem);
147
+ if (anchorIndex !== -1 && targetIndex !== -1) {
148
+ event.preventDefault();
149
+ event.stopPropagation();
150
+ const shouldSelectRange = !this.containsItem(this.internalSelection, clickedItem);
151
+ const nextSelection = this.applyRangeSelection(this.internalSelection, anchorIndex, targetIndex, rowNumber, shouldSelectRange);
152
+ this.pendingSelectionInteraction = null;
153
+ this.internalSelection = nextSelection;
154
+ this.$emit("update:modelValue", nextSelection);
155
+ return;
156
+ }
157
+ }
158
+
159
+ this.pendingSelectionInteraction = {
160
+ shiftKey: event.shiftKey,
161
+ rowNumber,
162
+ };
163
+ },
164
+ resolveClickedRowNumber(rowElement) {
165
+ const tbody = rowElement.parentElement;
166
+ if (!tbody) return null;
167
+
168
+ const selectableRows = Array.from(tbody.querySelectorAll("tr")).filter((row) => row.querySelector(".easy-checkbox"));
169
+ const rowIndex = selectableRows.indexOf(rowElement);
170
+ return rowIndex === -1 ? null : rowIndex + 1;
171
+ },
172
+ composeBodyRowClassName(item, rowNumber) {
173
+ if (rowNumber === 1) {
174
+ this.currentPageRowsCache = [];
175
+ }
176
+ this.currentPageRowsCache[rowNumber - 1] = this.normalizeItem(item);
177
+
178
+ const externalBodyRowClass = this.$attrs["body-row-class-name"] ?? this.$attrs.bodyRowClassName;
179
+ if (typeof externalBodyRowClass === "function") {
180
+ return externalBodyRowClass(item, rowNumber);
181
+ }
182
+ if (typeof externalBodyRowClass === "string") {
183
+ return externalBodyRowClass;
184
+ }
185
+ return "";
186
+ },
187
+ resolveShiftRangeSelection(newSelection, oldSelection = []) {
188
+ if (this.selectionMode !== "multiple") return newSelection;
189
+
190
+ const interaction = this.pendingSelectionInteraction;
191
+ this.pendingSelectionInteraction = null;
192
+
193
+ const toggledItemFromInteraction = interaction?.rowNumber ? this.currentPageRowsCache[interaction.rowNumber - 1] : null;
194
+ const toggledItem = toggledItemFromInteraction ?? this.getToggledItem(oldSelection, newSelection);
195
+ if (!toggledItem) return newSelection;
196
+
197
+ if (!interaction?.shiftKey || !this.lastSelectionAnchor) {
198
+ this.lastSelectionAnchor = toggledItem;
199
+ this.lastSelectionAnchorRowNumber = interaction?.rowNumber ?? null;
200
+ return newSelection;
201
+ }
202
+
203
+ const anchorIndex = this.findItemIndex(this.lastSelectionAnchor);
204
+ const targetIndex = this.findItemIndex(toggledItem);
205
+ if (anchorIndex === -1 || targetIndex === -1) {
206
+ this.lastSelectionAnchor = toggledItem;
207
+ this.lastSelectionAnchorRowNumber = interaction?.rowNumber ?? null;
208
+ return newSelection;
209
+ }
210
+
211
+ const rangeItems = this.getRangeItems(anchorIndex, targetIndex, interaction.rowNumber);
212
+ if (!rangeItems.length) return newSelection;
213
+ const shouldSelectRange = this.containsItem(newSelection, toggledItem);
214
+ return this.applyRangeSelection(newSelection, anchorIndex, targetIndex, interaction.rowNumber, shouldSelectRange);
215
+ },
216
+ applyRangeSelection(baseSelection, anchorIndex, targetIndex, targetRowNumber, shouldSelectRange) {
217
+ const rangeItems = this.getRangeItems(anchorIndex, targetIndex, targetRowNumber);
218
+ if (!rangeItems.length) return baseSelection;
219
+
220
+ let nextSelection = [...baseSelection];
221
+ if (shouldSelectRange) {
222
+ rangeItems.forEach((item) => {
223
+ if (!this.containsItem(nextSelection, item)) {
224
+ nextSelection.push(this.normalizeItem(item));
225
+ }
226
+ });
227
+ } else {
228
+ nextSelection = nextSelection.filter((selectedItem) => !rangeItems.some((item) => this.areItemsEqual(selectedItem, item)));
229
+ }
230
+
231
+ return this.areSelectionsEqual(nextSelection, baseSelection) ? baseSelection : nextSelection;
232
+ },
233
+ getRangeItems(anchorIndex, targetIndex, targetRowNumber) {
234
+ const anchorRowNumber = this.lastSelectionAnchorRowNumber;
235
+ if (anchorRowNumber && targetRowNumber) {
236
+ const [start, end] = anchorRowNumber <= targetRowNumber ? [anchorRowNumber, targetRowNumber] : [targetRowNumber, anchorRowNumber];
237
+ const visibleRange = this.currentPageRowsCache.slice(start - 1, end).filter((item) => item);
238
+ if (visibleRange.length === end - start + 1) {
239
+ return visibleRange;
240
+ }
241
+ }
242
+
243
+ const [start, end] = anchorIndex <= targetIndex ? [anchorIndex, targetIndex] : [targetIndex, anchorIndex];
244
+ return this.items.slice(start, end + 1);
245
+ },
246
+ normalizeItem(item) {
247
+ if (!item || typeof item !== "object") return item;
248
+ const normalized = { ...item };
249
+ delete normalized.checkbox;
250
+ delete normalized.index;
251
+ return normalized;
252
+ },
253
+ areItemsEqual(left, right) {
254
+ return JSON.stringify(this.normalizeItem(left)) === JSON.stringify(this.normalizeItem(right));
255
+ },
256
+ containsItem(collection, item) {
257
+ return collection.some((current) => this.areItemsEqual(current, item));
258
+ },
259
+ getToggledItem(previousSelection, nextSelection) {
260
+ const added = nextSelection.filter((item) => !this.containsItem(previousSelection, item));
261
+ if (added.length === 1) return this.normalizeItem(added[0]);
262
+
263
+ const removed = previousSelection.filter((item) => !this.containsItem(nextSelection, item));
264
+ if (removed.length === 1) return this.normalizeItem(removed[0]);
265
+
266
+ return null;
267
+ },
268
+ findItemIndex(itemToFind) {
269
+ return this.items.findIndex((item) => this.areItemsEqual(item, itemToFind));
270
+ },
271
+ areSelectionsEqual(first, second) {
272
+ if (first.length !== second.length) return false;
273
+ return first.every((item) => this.containsItem(second, item));
274
+ },
94
275
  normalizeModelValue(val) {
95
276
  if (this.selectionMode === "none") return [];
96
277
  if (this.selectionMode === "single") return val ? [val] : [];
@@ -102,6 +283,15 @@ export default {
102
283
  </script>
103
284
 
104
285
  <style>
286
+ .k-datatable-wrapper {
287
+ overflow-x: auto;
288
+ -webkit-overflow-scrolling: touch;
289
+ }
290
+
291
+ .k-datatable-wrapper .customize-table {
292
+ min-width: 640px;
293
+ }
294
+
105
295
  .customize-table {
106
296
  --easy-table-border: none;
107
297
  --easy-table-row-border: 1px solid #d1d5db;
@@ -143,13 +333,6 @@ export default {
143
333
  --easy-table-loading-mask-background-color: rgba(255, 255, 255, 0.6);
144
334
  }
145
335
 
146
- .customize-table {
147
- --easy-table-border: none;
148
- --easy-table-row-border: 1px solid #d1d5db;
149
- /* ... your other variables ... */
150
- --easy-table-footer-padding: 1rem;
151
- }
152
-
153
336
  /* Target the rows-per-page dropdown */
154
337
  .customize-table .easy-data-table__footer select {
155
338
  background-color: #fff; /* white dropdown background */
@@ -164,4 +347,15 @@ export default {
164
347
  background-color: #fff;
165
348
  color: #1f2937;
166
349
  }
350
+
351
+ @media (max-width: 640px) {
352
+ .k-datatable-wrapper .customize-table {
353
+ --easy-table-header-font-size: 12px;
354
+ --easy-table-header-item-padding: 0.5rem 0.625rem;
355
+ --easy-table-body-row-font-size: 12px;
356
+ --easy-table-body-item-padding: 0.5rem 0.625rem;
357
+ --easy-table-footer-font-size: 12px;
358
+ --easy-table-footer-padding: 0.75rem;
359
+ }
360
+ }
167
361
  </style>
@@ -8,7 +8,7 @@
8
8
  <transition name="dialog">
9
9
  <div
10
10
  ref="dialogPanel"
11
- class="relative bg-white dark:bg-slate-900 shadow-lg overflow-hidden rounded-2xl flex flex-col max-h-[90vh] m-4"
11
+ class="relative bg-white dark:bg-slate-900 shadow-lg overflow-hidden rounded-2xl flex flex-col max-h-[calc(100dvh-1rem)] sm:max-h-[90vh] m-2 sm:m-4"
12
12
  :class="dialogClasses"
13
13
  :role="'dialog'"
14
14
  :aria-modal="'true'"
@@ -17,8 +17,8 @@
17
17
  v-show="visible"
18
18
  >
19
19
  <!-- Header -->
20
- <div class="flex flex-row items-center p-4 text-white bg-sky-800 shrink-0">
21
- <div :id="titleId" class="text-xl font-semibold !text-white">{{ title }}</div>
20
+ <div class="flex flex-row items-center p-3 text-white bg-sky-800 shrink-0 sm:p-4">
21
+ <div :id="titleId" class="text-base font-semibold !text-white sm:text-xl">{{ title }}</div>
22
22
  <div class="flex-1" />
23
23
  <button
24
24
  type="button"
@@ -36,12 +36,15 @@
36
36
  </div>
37
37
 
38
38
  <!-- Scrollable slot content -->
39
- <div class="flex-1 p-4 overflow-auto bg-secondary dark:bg-slate-800/60">
39
+ <div class="flex-1 p-3 overflow-auto bg-secondary dark:bg-slate-800/60 sm:p-4">
40
40
  <slot></slot>
41
41
  </div>
42
42
 
43
43
  <!-- Footer -->
44
- <div v-if="$slots.footer" class="flex flex-row justify-end gap-2 p-4 shrink-0 border-t border-slate-100 dark:border-slate-700">
44
+ <div
45
+ v-if="$slots.footer"
46
+ class="flex flex-col-reverse gap-2 p-3 border-t shrink-0 border-slate-100 dark:border-slate-700 sm:flex-row sm:justify-end sm:p-4"
47
+ >
45
48
  <slot name="footer"></slot>
46
49
  </div>
47
50
  </div>
@@ -109,15 +112,15 @@ export default {
109
112
  },
110
113
  computed: {
111
114
  computedWidth() {
112
- return 'w-[600px] max-w-[calc(100vw-2rem)]'
115
+ return 'w-[96vw] sm:w-[600px] max-w-[calc(100vw-1rem)] sm:max-w-[calc(100vw-2rem)]'
113
116
  },
114
117
  dialogClasses() {
115
118
  if (this.width) {
116
- return this.width
119
+ return `${this.width} max-w-[calc(100vw-1rem)] sm:max-w-[calc(100vw-2rem)]`
117
120
  } else if (this.maximized) {
118
- return 'w-[calc(100vw-2rem)] max-w-none h-[90vh]'
121
+ return 'w-[calc(100vw-1rem)] sm:w-[calc(100vw-2rem)] max-w-none h-[calc(100dvh-1rem)] sm:h-[90vh]'
119
122
  } else {
120
- return this.computedWidth + ' max-h-[90vh]'
123
+ return this.computedWidth + ' max-h-[calc(100dvh-1rem)] sm:max-h-[90vh]'
121
124
  }
122
125
  },
123
126
  },
@@ -7,22 +7,24 @@
7
7
  <!-- <div class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm" @click="close"></div> -->
8
8
 
9
9
  <!-- Drawer -->
10
- <div class="fixed top-0 bottom-0 z-50 flex flex-col bg-white dark:bg-slate-900 shadow-xl w-[400px] transition-all duration-500" :class="side === 'right' ? 'right-0' : 'left-0'">
10
+ <div
11
+ class="fixed top-0 bottom-0 z-50 flex w-[calc(100vw-1rem)] max-w-full flex-col bg-white shadow-xl transition-all duration-500 dark:bg-slate-900 sm:w-[400px]"
12
+ :class="side === 'right' ? 'right-0' : 'left-0'">
11
13
  <!-- Header -->
12
- <div class="flex items-center justify-between p-4 text-white bg-primary dark:bg-sky-800">
13
- <div class="text-lg font-semibold">{{ title }}</div>
14
+ <div class="flex items-center justify-between p-3 text-white bg-primary dark:bg-sky-800 sm:p-4">
15
+ <div class="text-base font-semibold sm:text-lg">{{ title }}</div>
14
16
  <button @click="close" class="text-white hover:text-gray-200">
15
17
  <X class="w-5 h-5" />
16
18
  </button>
17
19
  </div>
18
20
 
19
21
  <!-- Content -->
20
- <div class="flex-1 p-4 overflow-auto bg-secondary dark:bg-slate-800/60">
22
+ <div class="flex-1 p-3 overflow-auto bg-secondary dark:bg-slate-800/60 sm:p-4">
21
23
  <slot></slot>
22
24
  </div>
23
25
 
24
26
  <!-- Footer -->
25
- <div class="p-4 bg-gray-100 dark:bg-slate-800 border-t border-slate-200 dark:border-slate-700">
27
+ <div class="p-3 bg-gray-100 border-t border-slate-200 dark:bg-slate-800 dark:border-slate-700 sm:p-4">
26
28
  <slot name="footer"></slot>
27
29
  </div>
28
30
  </div>
package/src/ui/kMenu.vue CHANGED
@@ -24,7 +24,7 @@
24
24
  <div
25
25
  v-if="isOpen"
26
26
  ref="menuPanelRef"
27
- class="z-[9999] overflow-visible border rounded-lg shadow-lg bg-white/95 backdrop-blur-sm border-primary/20 ring-1 ring-primary/10"
27
+ class="z-[9999] overflow-auto border rounded-lg shadow-lg bg-white/95 backdrop-blur-sm border-primary/20 ring-1 ring-primary/10 max-h-[70vh]"
28
28
  :style="panelStyle"
29
29
  role="menu">
30
30
  <span
@@ -153,12 +153,14 @@ export default {
153
153
  const rect = trigger.getBoundingClientRect();
154
154
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
155
155
  const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
156
- const panelWidth = this.$refs.menuPanelRef?.offsetWidth || 224;
156
+ const preferredPanelWidth = this.$refs.menuPanelRef?.offsetWidth || 224;
157
157
  const viewportWidth = window.innerWidth;
158
158
  const viewportHeight = window.innerHeight;
159
159
  const pad = 8;
160
160
  const gap = 8;
161
161
  const panelHeight = this.$refs.menuPanelRef?.offsetHeight || 0;
162
+ const maxPanelWidth = Math.max(120, viewportWidth - pad * 2);
163
+ const panelWidth = Math.min(preferredPanelWidth, maxPanelWidth);
162
164
 
163
165
  // bottom-end (default): align panel right edge with trigger right edge, below trigger.
164
166
  const preferredLeft = rect.right + scrollLeft - panelWidth;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="inline-flex overflow-hidden border rounded-md border-primary/25" style="width: fit-content;" :aria-disabled="disabled.toString()">
2
+ <div class="k-select-button inline-flex max-w-full overflow-x-auto border rounded-md border-primary/25" :aria-disabled="disabled.toString()">
3
3
 
4
4
  <button
5
5
  v-for="(option, index) in options"
@@ -34,7 +34,7 @@ export default {
34
34
  emits: ["update:modelValue"],
35
35
  computed: {
36
36
  baseClasses() {
37
- return "px-4 py-2 text-sm font-medium border-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-inset";
37
+ return "shrink-0 whitespace-nowrap px-3 py-2.5 text-sm font-medium border-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-inset sm:px-4 sm:py-2";
38
38
  },
39
39
  activeClasses() {
40
40
  return "bg-primary text-white";
@@ -68,3 +68,10 @@ export default {
68
68
  },
69
69
  };
70
70
  </script>
71
+
72
+ <style scoped>
73
+ .k-select-button {
74
+ -webkit-overflow-scrolling: touch;
75
+ scrollbar-width: thin;
76
+ }
77
+ </style>
package/src/ui/kTabs.vue CHANGED
@@ -1,11 +1,16 @@
1
1
  <template>
2
- <div class="w-full overflow-hidden bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-md">
3
- <div class="flex">
2
+ <div class="w-full overflow-hidden bg-white border border-slate-200 rounded-md dark:bg-slate-800 dark:border-slate-700">
3
+ <div class="k-tabs-scroll flex overflow-x-auto">
4
4
  <button
5
5
  v-for="tab in tabs"
6
6
  :key="tab.id"
7
7
  @click="selectTab(tab.id)"
8
- :class="['px-8 py-4 text-lg font-medium', modelValue === tab.id ? 'bg-primary text-secondary' : 'text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700/50']"
8
+ :class="[
9
+ 'shrink-0 whitespace-nowrap px-4 py-3 text-sm font-medium sm:px-8 sm:py-4 sm:text-lg',
10
+ modelValue === tab.id
11
+ ? 'bg-primary text-secondary'
12
+ : 'text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700/50',
13
+ ]"
9
14
  >
10
15
  {{ tab.title }}
11
16
  </button>
@@ -34,3 +39,10 @@ export default {
34
39
  },
35
40
  };
36
41
  </script>
42
+
43
+ <style scoped>
44
+ .k-tabs-scroll {
45
+ -webkit-overflow-scrolling: touch;
46
+ scrollbar-width: thin;
47
+ }
48
+ </style>