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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ui/kSelect.vue +59 -21
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.19",
4
+ "version": "1.0.20",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -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
- <input
42
- ref="searchInput"
43
- type="text"
44
- v-model="searchQuery"
45
- placeholder="Αναζήτηση..."
46
- class="w-full px-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"
47
- role="searchbox"
48
- aria-label="Search options"
49
- @keydown="handleSearchKeydown"
50
- @keyup.stop
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 overflow-y-auto"
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="`${option[optionValue]}-${index}`"
69
+ :key="option[optionValue]"
62
70
  :value="option[optionValue]"
63
- class="select-item relative flex items-center gap-1.5 rounded-[4px] py-1.5 pl-6 pr-3 text-sm cursor-pointer outline-none select-none text-slate-700
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-1.5 flex items-center justify-center w-4 h-4 shrink-0">
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-2 text-sm text-gray-400 text-center">
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 props.options.filter((o) => String(o?.[props.optionLabel] ?? "").toLowerCase().includes(q));
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
+