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 +1 -1
- package/src/style/style.css +8 -4
- package/src/ui/kDatatable.vue +210 -16
- package/src/ui/kDialog.vue +12 -9
- package/src/ui/kDrawer.vue +7 -5
- package/src/ui/kMenu.vue +4 -2
- package/src/ui/kSelectButton.vue +9 -2
- package/src/ui/kTabs.vue +15 -3
package/package.json
CHANGED
package/src/style/style.css
CHANGED
|
@@ -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.
|
|
13
|
+
font-size: clamp(1.625rem, 4vw + 0.75rem, 2.5rem);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
h2{
|
|
16
|
-
font-size:
|
|
17
|
+
font-size: clamp(1.375rem, 3vw + 0.7rem, 2rem);
|
|
17
18
|
}
|
|
18
19
|
h3{
|
|
19
|
-
font-size: 1.
|
|
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
|
+
}
|
package/src/ui/kDatatable.vue
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
methods: {
|
|
101
|
+
handleSelectionUpdate(newSelection) {
|
|
85
102
|
if (this.selectionMode === "single") {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.$emit("update:modelValue",
|
|
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
|
-
|
|
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>
|
package/src/ui/kDialog.vue
CHANGED
|
@@ -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-
|
|
21
|
-
<div :id="titleId" class="text-
|
|
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-
|
|
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
|
|
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
|
},
|
package/src/ui/kDrawer.vue
CHANGED
|
@@ -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
|
|
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-
|
|
13
|
-
<div class="text-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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;
|
package/src/ui/kSelectButton.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="inline-flex overflow-
|
|
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-
|
|
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
|
|
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="[
|
|
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>
|