ketekny-ui-kit 1.0.17 → 1.0.19
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 +3 -2
- package/src/ui/kSelect.vue +230 -232
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ketekny-ui-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.19",
|
|
5
5
|
"description": "A Vue 3 UI component library with Tailwind CSS styling",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@primeuix/themes": "^1.1.1",
|
|
42
42
|
"@vuepic/vue-datepicker": "^11.0.2",
|
|
43
|
-
"json-editor-vue": "^0.18.1",
|
|
44
43
|
"he-tree-vue": "^3.1.2",
|
|
44
|
+
"json-editor-vue": "^0.18.1",
|
|
45
45
|
"lucide-vue-next": "^0.511.0",
|
|
46
46
|
"moment": "^2.30.1",
|
|
47
47
|
"primeicons": "^7.0.0",
|
|
48
48
|
"primevue": "^4.3.4",
|
|
49
49
|
"quill": "^2.0.3",
|
|
50
|
+
"reka-ui": "^2.8.2",
|
|
50
51
|
"simple-code-editor": "^2.0.9",
|
|
51
52
|
"vue-router": "^4.6.4",
|
|
52
53
|
"vue3-easy-data-table": "^1.5.47"
|
package/src/ui/kSelect.vue
CHANGED
|
@@ -1,266 +1,264 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative w-full">
|
|
3
|
-
<!-- Label -->
|
|
4
3
|
<label v-if="label != null" :for="computedId" class="inputLabel" :class="hasError ? 'text-red-500' : 'text-gray-700'">
|
|
5
4
|
{{ label }}
|
|
6
5
|
</label>
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
:aria-disabled="disabled.toString()"
|
|
18
|
-
:aria-controls="dropdownId"
|
|
19
|
-
:role="'combobox'"
|
|
20
|
-
:tabindex="disabled ? -1 : 0"
|
|
21
|
-
>
|
|
22
|
-
<span class="block pr-10 truncate">{{ selectedLabel || placeholder }}</span>
|
|
7
|
+
<SelectRoot v-model="internalValue" v-model:open="open" :disabled="disabled">
|
|
8
|
+
<SelectTrigger as-child>
|
|
9
|
+
<div
|
|
10
|
+
:id="computedId"
|
|
11
|
+
class="relative w-full px-3 py-2 text-gray-700 bg-white border rounded-lg cursor-pointer trigger-box"
|
|
12
|
+
:class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']"
|
|
13
|
+
:aria-label="label || 'Select option'"
|
|
14
|
+
>
|
|
15
|
+
<SelectValue :placeholder="placeholder" class="block pr-10 truncate" />
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<!-- Dropdown icon -->
|
|
35
|
-
<span class="absolute text-gray-400 -translate-y-1/2 pointer-events-none right-3 top-1/2">
|
|
36
|
-
<ChevronDown class="w-5 h-5" v-if="!isOpen" />
|
|
37
|
-
<ChevronUp class="w-5 h-5" v-else />
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
17
|
+
<button
|
|
18
|
+
v-if="clearable && isSelectionSet && !disabled"
|
|
19
|
+
type="button"
|
|
20
|
+
class="absolute text-gray-400 -translate-y-1/2 right-8 top-1/2 hover:text-red-500 transition-colors"
|
|
21
|
+
aria-label="Clear selection"
|
|
22
|
+
@pointerdown.stop.prevent="clearSelection"
|
|
23
|
+
@click.stop.prevent="clearSelection"
|
|
24
|
+
>
|
|
25
|
+
<X class="w-4 h-4" />
|
|
26
|
+
</button>
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
v-if="isOpen"
|
|
45
|
-
ref="dropdown"
|
|
46
|
-
:id="dropdownId"
|
|
47
|
-
class="absolute z-[9999] mt-1 overflow-auto bg-white border border-gray-200 rounded-lg shadow-[0_20px_45px_-15px_rgba(15,23,42,0.35),0_8px_18px_-10px_rgba(15,23,42,0.25)] ring-1 ring-slate-900/10"
|
|
48
|
-
:class="dropdownHeight"
|
|
49
|
-
:style="dropdownPositionStyle"
|
|
50
|
-
role="listbox"
|
|
51
|
-
:aria-label="label || 'Select options'"
|
|
52
|
-
>
|
|
53
|
-
<!-- Search -->
|
|
54
|
-
<div class="sticky top-0 z-10 pt-2 pb-2 mx-2 bg-white">
|
|
55
|
-
<input
|
|
56
|
-
ref="searchInput"
|
|
57
|
-
type="text"
|
|
58
|
-
v-model="searchQuery"
|
|
59
|
-
placeholder="Αναζήτηση..."
|
|
60
|
-
class="w-full px-3 py-2 border border-gray-200 rounded-md focus:outline-none"
|
|
61
|
-
role="searchbox"
|
|
62
|
-
aria-label="Search options"
|
|
63
|
-
@keydown.esc.stop.prevent="closeDropdown"
|
|
64
|
-
/>
|
|
28
|
+
<span class="absolute text-gray-400 -translate-y-1/2 pointer-events-none right-3 top-1/2">
|
|
29
|
+
<ChevronDown class="w-4 h-4 transition-transform duration-200" :class="{ 'rotate-180': open }" />
|
|
30
|
+
</span>
|
|
65
31
|
</div>
|
|
32
|
+
</SelectTrigger>
|
|
66
33
|
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<Check v-if="option[optionValue] === selectedValue" class="w-4 h-4 shrink-0 text-primary" />
|
|
86
|
-
</div>
|
|
34
|
+
<SelectPortal>
|
|
35
|
+
<SelectContent
|
|
36
|
+
position="popper"
|
|
37
|
+
:side-offset="6"
|
|
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
|
+
>
|
|
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
|
+
/>
|
|
87
52
|
</div>
|
|
88
|
-
</div>
|
|
89
53
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
54
|
+
<SelectViewport
|
|
55
|
+
ref="viewportRef"
|
|
56
|
+
class="p-1 overflow-y-auto"
|
|
57
|
+
:style="{ maxHeight: dropdownHeight }"
|
|
58
|
+
>
|
|
59
|
+
<SelectItem
|
|
60
|
+
v-for="(option, index) in filteredOptions"
|
|
61
|
+
:key="`${option[optionValue]}-${index}`"
|
|
62
|
+
: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
|
|
64
|
+
data-[highlighted]:bg-primary data-[highlighted]:text-white
|
|
65
|
+
data-[state=checked]:text-primary
|
|
66
|
+
data-[state=checked]:data-[highlighted]:text-white"
|
|
67
|
+
>
|
|
68
|
+
<span class="absolute left-1.5 flex items-center justify-center w-4 h-4 shrink-0">
|
|
69
|
+
<SelectItemIndicator>
|
|
70
|
+
<Check class="w-3.5 h-3.5" />
|
|
71
|
+
</SelectItemIndicator>
|
|
72
|
+
</span>
|
|
73
|
+
<SelectItemText class="truncate">{{ option[optionLabel] }}</SelectItemText>
|
|
74
|
+
</SelectItem>
|
|
93
75
|
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
<div v-if="filteredOptions.length === 0" class="px-3 py-2 text-sm text-gray-400 text-center">
|
|
77
|
+
Δεν βρέθηκαν επιλογές
|
|
78
|
+
</div>
|
|
79
|
+
</SelectViewport>
|
|
80
|
+
</SelectContent>
|
|
81
|
+
</SelectPortal>
|
|
82
|
+
</SelectRoot>
|
|
83
|
+
|
|
84
|
+
<div class="mt-1 text-sm text-red-500" v-if="hasError && error !== true && error !== ''">
|
|
96
85
|
{{ error }}
|
|
97
86
|
</div>
|
|
98
87
|
|
|
99
|
-
|
|
100
|
-
<div class="mt-1 text-gray-500" v-if="info">
|
|
88
|
+
<div class="mt-1 text-sm text-gray-500" v-if="info">
|
|
101
89
|
{{ info }}
|
|
102
90
|
</div>
|
|
103
91
|
</div>
|
|
104
92
|
</template>
|
|
105
93
|
|
|
106
94
|
<script setup>
|
|
107
|
-
import {
|
|
108
|
-
|
|
95
|
+
import { computed, nextTick, ref, watch } from "vue";
|
|
96
|
+
import {
|
|
97
|
+
SelectContent,
|
|
98
|
+
SelectItem,
|
|
99
|
+
SelectItemIndicator,
|
|
100
|
+
SelectItemText,
|
|
101
|
+
SelectPortal,
|
|
102
|
+
SelectRoot,
|
|
103
|
+
SelectTrigger,
|
|
104
|
+
SelectValue,
|
|
105
|
+
SelectViewport,
|
|
106
|
+
} from "reka-ui";
|
|
107
|
+
import { X, ChevronDown, Check } from "lucide-vue-next";
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
109
|
+
const props = defineProps({
|
|
110
|
+
options: { type: Array, required: true },
|
|
111
|
+
modelValue: [String, Number],
|
|
112
|
+
optionValue: { type: String, default: "value" },
|
|
113
|
+
optionLabel: { type: String, default: "label" },
|
|
114
|
+
label: String,
|
|
115
|
+
info: String,
|
|
116
|
+
error: [String, Boolean],
|
|
117
|
+
disabled: { type: Boolean, default: false },
|
|
118
|
+
placeholder: { type: String, default: "Επιλέξτε μία τιμή" },
|
|
119
|
+
id: { type: String, default: null },
|
|
120
|
+
dropdownHeight: { type: String, default: "min(400px, 40vh)" },
|
|
121
|
+
clearable: { type: Boolean, default: true },
|
|
122
|
+
searchable: { type: Boolean, default: true },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
126
|
+
|
|
127
|
+
const open = ref(false);
|
|
128
|
+
const searchQuery = ref("");
|
|
129
|
+
const searchInput = ref(null);
|
|
130
|
+
const viewportRef = ref(null);
|
|
131
|
+
const generatedId = `select-${Math.random().toString(36).substr(2, 9)}`;
|
|
132
|
+
const defaultStyle = "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400";
|
|
133
|
+
const errorStyle = "border-red-500 focus:ring focus:ring-red-300";
|
|
134
|
+
const disabledStyle = "!bg-gray-100 !text-gray-400 !cursor-not-allowed";
|
|
135
|
+
|
|
136
|
+
const computedId = computed(() => (props.id != null && props.id !== "" ? props.id : generatedId));
|
|
137
|
+
const hasError = computed(() => props.error != null && props.error !== false);
|
|
138
|
+
const isSelectionSet = computed(() => props.modelValue !== null && props.modelValue !== undefined && props.modelValue !== "");
|
|
139
|
+
|
|
140
|
+
const internalValue = computed({
|
|
141
|
+
get() {
|
|
142
|
+
return props.modelValue === null ? undefined : props.modelValue;
|
|
137
143
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return this.id != null && this.id !== "" ? this.id : this.generatedId;
|
|
141
|
-
},
|
|
142
|
-
dropdownId() {
|
|
143
|
-
return `${this.computedId}-listbox`;
|
|
144
|
-
},
|
|
145
|
-
selectedValue() {
|
|
146
|
-
return this.modelValue;
|
|
147
|
-
},
|
|
148
|
-
isSelectionSet() {
|
|
149
|
-
return this.selectedValue !== null && this.selectedValue !== undefined && this.selectedValue !== "";
|
|
150
|
-
},
|
|
151
|
-
selectedLabel() {
|
|
152
|
-
const match = this.options.find((o) => o[this.optionValue] === this.modelValue);
|
|
153
|
-
return match ? match[this.optionLabel] : "";
|
|
154
|
-
},
|
|
155
|
-
filteredOptions() {
|
|
156
|
-
const q = this.searchQuery.trim().toLowerCase();
|
|
157
|
-
return this.options.filter((o) => String(o[this.optionLabel] ?? "").toLowerCase().includes(q));
|
|
158
|
-
},
|
|
159
|
-
hasError() {
|
|
160
|
-
return this.error != null && this.error !== false;
|
|
161
|
-
},
|
|
144
|
+
set(val) {
|
|
145
|
+
emit("update:modelValue", val ?? null);
|
|
162
146
|
},
|
|
163
|
-
|
|
164
|
-
toggleDropdown() {
|
|
165
|
-
if (this.disabled) return;
|
|
166
|
-
this.isOpen = !this.isOpen;
|
|
167
|
-
if (this.isOpen) {
|
|
168
|
-
this.$nextTick(() => {
|
|
169
|
-
this.getDropdownPosition();
|
|
170
|
-
this.$refs.searchInput?.focus();
|
|
171
|
-
});
|
|
172
|
-
} else {
|
|
173
|
-
this.searchQuery = "";
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
closeDropdown() {
|
|
177
|
-
this.isOpen = false;
|
|
178
|
-
this.searchQuery = "";
|
|
179
|
-
},
|
|
180
|
-
selectOption(option) {
|
|
181
|
-
this.$emit("update:modelValue", option[this.optionValue]);
|
|
182
|
-
this.closeDropdown();
|
|
183
|
-
},
|
|
184
|
-
clearSelection() {
|
|
185
|
-
this.$emit("update:modelValue", null);
|
|
186
|
-
this.closeDropdown();
|
|
187
|
-
},
|
|
188
|
-
handleTriggerKeydown(event) {
|
|
189
|
-
if (this.disabled) return;
|
|
190
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
191
|
-
event.preventDefault();
|
|
192
|
-
this.toggleDropdown();
|
|
193
|
-
} else if (event.key === "ArrowDown" && !this.isOpen) {
|
|
194
|
-
event.preventDefault();
|
|
195
|
-
this.toggleDropdown();
|
|
196
|
-
} else if (event.key === "Escape" && this.isOpen) {
|
|
197
|
-
event.preventDefault();
|
|
198
|
-
this.closeDropdown();
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
closeOnClickOutside(e) {
|
|
202
|
-
const clickedOutside = !this.$el.contains(e.target) && !(this.$refs.dropdown && this.$refs.dropdown.contains(e.target));
|
|
147
|
+
});
|
|
203
148
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.getDropdownPosition();
|
|
211
|
-
},
|
|
149
|
+
const filteredOptions = computed(() => {
|
|
150
|
+
if (!props.searchable) return props.options;
|
|
151
|
+
const q = searchQuery.value.trim().toLowerCase();
|
|
152
|
+
if (!q) return props.options;
|
|
153
|
+
return props.options.filter((o) => String(o?.[props.optionLabel] ?? "").toLowerCase().includes(q));
|
|
154
|
+
});
|
|
212
155
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
156
|
+
watch(open, (val) => {
|
|
157
|
+
if (val) {
|
|
158
|
+
nextTick(() => {
|
|
159
|
+
if (props.searchable) searchInput.value?.focus();
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
searchQuery.value = "";
|
|
164
|
+
});
|
|
217
165
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const viewportWidth = window.innerWidth;
|
|
223
|
-
const pad = 8;
|
|
166
|
+
function clearSelection() {
|
|
167
|
+
emit("update:modelValue", null);
|
|
168
|
+
open.value = false;
|
|
169
|
+
}
|
|
224
170
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
171
|
+
function getOptionNodes() {
|
|
172
|
+
const el = viewportRef.value?.$el ?? viewportRef.value;
|
|
173
|
+
if (!el) return [];
|
|
174
|
+
return Array.from(el.querySelectorAll('[role="option"]:not([data-disabled])'));
|
|
175
|
+
}
|
|
230
176
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
177
|
+
function handleSearchKeydown(event) {
|
|
178
|
+
// Always stop propagation to prevent reka-ui typeahead on character keys
|
|
179
|
+
event.stopPropagation();
|
|
234
180
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
181
|
+
if (event.key === "Escape") {
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
open.value = false;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
239
186
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
187
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
const nodes = getOptionNodes();
|
|
190
|
+
if (!nodes.length) return;
|
|
191
|
+
const activeIndex = nodes.findIndex((n) => n === document.activeElement);
|
|
192
|
+
if (event.key === "ArrowDown") {
|
|
193
|
+
const next = activeIndex < nodes.length - 1 ? nodes[activeIndex + 1] : nodes[0];
|
|
194
|
+
next.focus();
|
|
195
|
+
} else {
|
|
196
|
+
const prev = activeIndex > 0 ? nodes[activeIndex - 1] : nodes[nodes.length - 1];
|
|
197
|
+
prev.focus();
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
244
201
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
}
|
|
202
|
+
if (event.key === "Home") {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
const nodes = getOptionNodes();
|
|
205
|
+
nodes[0]?.focus();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (event.key === "End") {
|
|
210
|
+
event.preventDefault();
|
|
211
|
+
const nodes = getOptionNodes();
|
|
212
|
+
nodes[nodes.length - 1]?.focus();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (event.key === "Enter") {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
const nodes = getOptionNodes();
|
|
219
|
+
const focused = nodes.find((n) => n === document.activeElement);
|
|
220
|
+
if (focused) focused.click();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
266
223
|
</script>
|
|
224
|
+
|
|
225
|
+
<style scoped>
|
|
226
|
+
.select-content {
|
|
227
|
+
transform-origin: var(--reka-select-content-transform-origin);
|
|
228
|
+
width: var(--reka-popper-anchor-width);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.select-content[data-state="open"] {
|
|
232
|
+
animation: selectOpen 0.15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.select-content[data-state="closed"] {
|
|
236
|
+
animation: selectClose 0.1s ease-in;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@keyframes selectOpen {
|
|
240
|
+
from {
|
|
241
|
+
opacity: 0;
|
|
242
|
+
transform: translateY(-6px) scale(0.97);
|
|
243
|
+
}
|
|
244
|
+
to {
|
|
245
|
+
opacity: 1;
|
|
246
|
+
transform: translateY(0) scale(1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@keyframes selectClose {
|
|
251
|
+
from {
|
|
252
|
+
opacity: 1;
|
|
253
|
+
transform: translateY(0) scale(1);
|
|
254
|
+
}
|
|
255
|
+
to {
|
|
256
|
+
opacity: 0;
|
|
257
|
+
transform: translateY(-6px) scale(0.97);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.select-item {
|
|
262
|
+
transition: background-color 80ms, color 80ms;
|
|
263
|
+
}
|
|
264
|
+
</style>
|