ketekny-ui-kit 1.0.19 → 1.0.20
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/ui/kSelect.vue +59 -21
package/package.json
CHANGED
package/src/ui/kSelect.vue
CHANGED
|
@@ -38,34 +38,42 @@
|
|
|
38
38
|
class="select-content z-[9999] bg-white border border-gray-200 rounded-lg shadow-[0_10px_38px_-10px_rgba(22,23,24,0.35),0_10px_20px_-15px_rgba(22,23,24,0.2)] overflow-hidden"
|
|
39
39
|
>
|
|
40
40
|
<div v-if="searchable" class="sticky top-0 z-10 p-1.5 border-b border-gray-100 bg-white">
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
<div class="relative">
|
|
42
|
+
<Search class="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400 pointer-events-none" />
|
|
43
|
+
<input
|
|
44
|
+
ref="searchInput"
|
|
45
|
+
type="text"
|
|
46
|
+
v-model="searchQuery"
|
|
47
|
+
placeholder="Αναζήτηση..."
|
|
48
|
+
class="w-full pl-7 pr-2.5 py-1.5 text-sm border border-gray-200 rounded-md focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/30 transition-colors placeholder-gray-400"
|
|
49
|
+
role="searchbox"
|
|
50
|
+
aria-label="Search options"
|
|
51
|
+
@keydown="handleSearchKeydown"
|
|
52
|
+
@keyup.stop
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
52
55
|
</div>
|
|
53
56
|
|
|
57
|
+
<SelectScrollUpButton class="flex items-center justify-center py-1 text-gray-400 hover:text-gray-600 cursor-default bg-white border-b border-gray-100">
|
|
58
|
+
<ChevronUp class="w-4 h-4" />
|
|
59
|
+
</SelectScrollUpButton>
|
|
60
|
+
|
|
54
61
|
<SelectViewport
|
|
55
62
|
ref="viewportRef"
|
|
56
|
-
class="p-1
|
|
63
|
+
class="p-1"
|
|
57
64
|
:style="{ maxHeight: dropdownHeight }"
|
|
65
|
+
@keydown="handleViewportKeydown"
|
|
58
66
|
>
|
|
59
67
|
<SelectItem
|
|
60
68
|
v-for="(option, index) in filteredOptions"
|
|
61
|
-
:key="
|
|
69
|
+
:key="option[optionValue]"
|
|
62
70
|
:value="option[optionValue]"
|
|
63
|
-
class="select-item relative flex items-center gap-1.5 rounded-[4px] py-
|
|
71
|
+
class="select-item relative flex items-center gap-1.5 rounded-[4px] py-2.5 pl-7 pr-3 text-sm cursor-pointer outline-none select-none text-slate-700
|
|
64
72
|
data-[highlighted]:bg-primary data-[highlighted]:text-white
|
|
65
|
-
data-[state=checked]:text-primary
|
|
66
|
-
data-[state=checked]:data-[highlighted]:text-white"
|
|
73
|
+
data-[state=checked]:bg-primary/5 data-[state=checked]:text-primary
|
|
74
|
+
data-[state=checked]:data-[highlighted]:bg-primary data-[state=checked]:data-[highlighted]:text-white"
|
|
67
75
|
>
|
|
68
|
-
<span class="absolute left-
|
|
76
|
+
<span class="absolute left-2 flex items-center justify-center w-4 h-4 shrink-0">
|
|
69
77
|
<SelectItemIndicator>
|
|
70
78
|
<Check class="w-3.5 h-3.5" />
|
|
71
79
|
</SelectItemIndicator>
|
|
@@ -73,10 +81,15 @@
|
|
|
73
81
|
<SelectItemText class="truncate">{{ option[optionLabel] }}</SelectItemText>
|
|
74
82
|
</SelectItem>
|
|
75
83
|
|
|
76
|
-
<div v-if="filteredOptions.length === 0" class="px-3 py-
|
|
77
|
-
|
|
84
|
+
<div v-if="filteredOptions.length === 0" class="flex flex-col items-center gap-1.5 px-3 py-4 text-sm text-gray-400 text-center">
|
|
85
|
+
<SearchX class="w-5 h-5 text-gray-300" />
|
|
86
|
+
<span>Δεν βρέθηκαν επιλογές</span>
|
|
78
87
|
</div>
|
|
79
88
|
</SelectViewport>
|
|
89
|
+
|
|
90
|
+
<SelectScrollDownButton class="flex items-center justify-center py-1 text-gray-400 hover:text-gray-600 cursor-default bg-white border-t border-gray-100">
|
|
91
|
+
<ChevronDown class="w-4 h-4" />
|
|
92
|
+
</SelectScrollDownButton>
|
|
80
93
|
</SelectContent>
|
|
81
94
|
</SelectPortal>
|
|
82
95
|
</SelectRoot>
|
|
@@ -100,11 +113,13 @@ import {
|
|
|
100
113
|
SelectItemText,
|
|
101
114
|
SelectPortal,
|
|
102
115
|
SelectRoot,
|
|
116
|
+
SelectScrollDownButton,
|
|
117
|
+
SelectScrollUpButton,
|
|
103
118
|
SelectTrigger,
|
|
104
119
|
SelectValue,
|
|
105
120
|
SelectViewport,
|
|
106
121
|
} from "reka-ui";
|
|
107
|
-
import { X, ChevronDown, Check } from "lucide-vue-next";
|
|
122
|
+
import { X, ChevronDown, ChevronUp, Check, Search, SearchX } from "lucide-vue-next";
|
|
108
123
|
|
|
109
124
|
const props = defineProps({
|
|
110
125
|
options: { type: Array, required: true },
|
|
@@ -146,17 +161,29 @@ const internalValue = computed({
|
|
|
146
161
|
},
|
|
147
162
|
});
|
|
148
163
|
|
|
164
|
+
const normalizedOptions = computed(() =>
|
|
165
|
+
props.options.map((o) => ({
|
|
166
|
+
option: o,
|
|
167
|
+
normalized: String(o?.[props.optionLabel] ?? "").toLowerCase(),
|
|
168
|
+
}))
|
|
169
|
+
);
|
|
170
|
+
|
|
149
171
|
const filteredOptions = computed(() => {
|
|
150
172
|
if (!props.searchable) return props.options;
|
|
151
173
|
const q = searchQuery.value.trim().toLowerCase();
|
|
152
174
|
if (!q) return props.options;
|
|
153
|
-
return
|
|
175
|
+
return normalizedOptions.value
|
|
176
|
+
.filter(({ normalized }) => normalized.includes(q))
|
|
177
|
+
.map(({ option }) => option);
|
|
154
178
|
});
|
|
155
179
|
|
|
156
180
|
watch(open, (val) => {
|
|
157
181
|
if (val) {
|
|
158
182
|
nextTick(() => {
|
|
159
183
|
if (props.searchable) searchInput.value?.focus();
|
|
184
|
+
const el = viewportRef.value?.$el ?? viewportRef.value;
|
|
185
|
+
const checked = el?.querySelector('[data-state="checked"]');
|
|
186
|
+
checked?.scrollIntoView({ block: "nearest" });
|
|
160
187
|
});
|
|
161
188
|
return;
|
|
162
189
|
}
|
|
@@ -220,6 +247,15 @@ function handleSearchKeydown(event) {
|
|
|
220
247
|
if (focused) focused.click();
|
|
221
248
|
}
|
|
222
249
|
}
|
|
250
|
+
|
|
251
|
+
function handleViewportKeydown(event) {
|
|
252
|
+
if (!props.searchable || !searchInput.value || event.target === searchInput.value) return;
|
|
253
|
+
const isPrintable = event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
254
|
+
if (!isPrintable) return;
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
searchQuery.value += event.key;
|
|
257
|
+
nextTick(() => searchInput.value?.focus());
|
|
258
|
+
}
|
|
223
259
|
</script>
|
|
224
260
|
|
|
225
261
|
<style scoped>
|
|
@@ -261,4 +297,6 @@ function handleSearchKeydown(event) {
|
|
|
261
297
|
.select-item {
|
|
262
298
|
transition: background-color 80ms, color 80ms;
|
|
263
299
|
}
|
|
300
|
+
|
|
264
301
|
</style>
|
|
302
|
+
|