plugin-ui-for-kzt 0.0.28 → 0.0.29
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/dist/components/BaseBadge/BaseBadge.vue.d.ts +2 -2
- package/dist/components/BaseButton/BaseButton.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +2 -2
- package/dist/components/BaseDropdown/BaseDropdown.vue.d.ts +1 -1
- package/dist/components/BaseInput/BaseInput.vue.d.ts +4 -4
- package/dist/components/BaseInputCalendar/BaseInputCalendar.vue.d.ts +3 -3
- package/dist/components/BaseInputCurrency/BaseInputCurrency.vue.d.ts +4 -4
- package/dist/components/BaseInputEmail/BaseInputEmail.vue.d.ts +3 -3
- package/dist/components/BaseInputPhone/BaseInputPhone.vue.d.ts +3 -3
- package/dist/components/BasePagination/BasePagination.vue.d.ts +1 -1
- package/dist/components/BaseRadio/BaseRadio.vue.d.ts +2 -2
- package/dist/components/BaseSegmentedButtons/BaseSegmentedButtons.vue.d.ts +1 -1
- package/dist/components/BaseSelect/BaseSelect.vue.d.ts +14 -4
- package/dist/components/BaseTable/BaseTable.vue.d.ts +14 -1
- package/dist/components/BaseTag/BaseTag.vue.d.ts +1 -1
- package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +3 -3
- package/dist/components/BaseToast/BaseToast.vue.d.ts +1 -1
- package/dist/components/BaseToggle/BaseToggle.vue.d.ts +2 -2
- package/dist/components/BaseTooltip/BaseTooltip.vue.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sprite.svg +1 -1
- package/example/App.vue +270 -6
- package/package.json +1 -1
- package/src/assets/icons/search.svg +4 -0
- package/src/components/BaseButton/BaseButton.vue +20 -20
- package/src/components/BaseSelect/BaseSelect.vue +155 -10
- package/src/components/BaseTable/BaseTable.vue +278 -26
- package/src/components/BaseTable/README.md +96 -1
- package/src/components/BaseTooltip/BaseTooltip.vue +9 -5
- package/src/components/BaseUpload/BaseUpload.vue +97 -25
- package/src/types/input.d.ts +1 -0
- package/src/types/table.d.ts +6 -0
- package/src/vue-virtual-scroller.d.ts +4 -0
package/example/App.vue
CHANGED
|
@@ -1,19 +1,283 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="demo-page">
|
|
3
3
|
<h1>Plugin UI KZT - Компоненты</h1>
|
|
4
|
+
|
|
5
|
+
<section>
|
|
6
|
+
<h2>BaseUpload с drag & drop</h2>
|
|
7
|
+
<div style="display: flex; gap: 20px;">
|
|
8
|
+
<div style="width: 400px;">
|
|
9
|
+
<h3>Множественная загрузка (multiple: true)</h3>
|
|
10
|
+
<BaseUpload
|
|
11
|
+
:accepted-formats="['.pdf', '.docx', '.jpeg', '.jpg', '.png', '.svg']"
|
|
12
|
+
:multiple="true"
|
|
13
|
+
>
|
|
14
|
+
<template #title>
|
|
15
|
+
Перетащите файлы сюда или кликните для выбора
|
|
16
|
+
</template>
|
|
17
|
+
<template #description>
|
|
18
|
+
Поддерживаемые форматы: PDF, DOCX, JPEG, JPG, PNG, SVG до 25 MB
|
|
19
|
+
</template>
|
|
20
|
+
</BaseUpload>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div style="width: 400px;">
|
|
24
|
+
<h3>Одиночная загрузка (multiple: false)</h3>
|
|
25
|
+
<BaseUpload
|
|
26
|
+
:accepted-formats="['.pdf', '.docx', '.jpeg', '.jpg', '.png', '.svg']"
|
|
27
|
+
:multiple="false"
|
|
28
|
+
>
|
|
29
|
+
<template #title>
|
|
30
|
+
Перетащите файл сюда или кликните для выбора
|
|
31
|
+
</template>
|
|
32
|
+
<template #description>
|
|
33
|
+
Только один файл: PDF, DOCX, JPEG, JPG, PNG, SVG до 25 MB
|
|
34
|
+
</template>
|
|
35
|
+
</BaseUpload>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
|
|
40
|
+
<section>
|
|
41
|
+
<h2>Сравнение режимов</h2>
|
|
42
|
+
<div style="display: flex; gap: 20px;">
|
|
43
|
+
<div style="width: 300px;">
|
|
44
|
+
<h3>Дефолтный режим</h3>
|
|
45
|
+
<BaseSelect
|
|
46
|
+
id="select4"
|
|
47
|
+
:options="options"
|
|
48
|
+
:placeholder="'Выберите опцию'"
|
|
49
|
+
:label="'Опция'"
|
|
50
|
+
:searchable="false"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
<div style="width: 300px;">
|
|
54
|
+
<h3>Поисковый режим (новое поведение)</h3>
|
|
55
|
+
<BaseSelect
|
|
56
|
+
id="select5"
|
|
57
|
+
:options="searchableOptions"
|
|
58
|
+
:placeholder="'Выберите страну'"
|
|
59
|
+
:label="'Страна'"
|
|
60
|
+
:searchable="true"
|
|
61
|
+
:error="errorMessage"
|
|
62
|
+
@error="handleSelectError"
|
|
63
|
+
>
|
|
64
|
+
<template #empty>
|
|
65
|
+
<div>
|
|
66
|
+
Нет стран
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
</BaseSelect>
|
|
70
|
+
<p style="font-size: 12px; color: #666; margin-top: 8px;">
|
|
71
|
+
• Клик по стрелке открывает dropdown<br/>
|
|
72
|
+
• При точном совпадении элемент выбирается автоматически<br/>
|
|
73
|
+
• При выборе элемента иконка меняется на крестик<br/>
|
|
74
|
+
• Клик по крестику очищает выбор<br/>
|
|
75
|
+
• Клик по инпуту не очищает выбор<br/>
|
|
76
|
+
• При отсутствии совпадений эмитится ошибка
|
|
77
|
+
</p>
|
|
78
|
+
<div v-if="errorMessage" style="color: red; font-size: 12px; margin-top: 4px;">
|
|
79
|
+
Элемент не найден
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
|
|
85
|
+
<section>
|
|
86
|
+
<h2>Тест тултипов (несколько экземпляров)</h2>
|
|
87
|
+
<div style="display: flex; gap: 20px; align-items: center;">
|
|
88
|
+
<BaseTooltip
|
|
89
|
+
id="tooltip1"
|
|
90
|
+
title="Первый тултип"
|
|
91
|
+
content="Это первый тултип на странице"
|
|
92
|
+
trigger="click"
|
|
93
|
+
>
|
|
94
|
+
<template #trigger>
|
|
95
|
+
<button style="padding: 8px 16px; border: 1px solid #ccc; background: white; cursor: pointer;">
|
|
96
|
+
Тултип 1 (click)
|
|
97
|
+
</button>
|
|
98
|
+
</template>
|
|
99
|
+
</BaseTooltip>
|
|
100
|
+
|
|
101
|
+
<BaseTooltip
|
|
102
|
+
id="tooltip2"
|
|
103
|
+
title="Второй тултип"
|
|
104
|
+
content="Это второй тултип на странице"
|
|
105
|
+
trigger="click"
|
|
106
|
+
>
|
|
107
|
+
<template #trigger>
|
|
108
|
+
<button style="padding: 8px 16px; border: 1px solid #ccc; background: white; cursor: pointer;">
|
|
109
|
+
Тултип 2 (click)
|
|
110
|
+
</button>
|
|
111
|
+
</template>
|
|
112
|
+
</BaseTooltip>
|
|
113
|
+
|
|
114
|
+
<BaseTooltip
|
|
115
|
+
id="tooltip3"
|
|
116
|
+
title="Третий тултип"
|
|
117
|
+
content="Это третий тултип на странице"
|
|
118
|
+
trigger="hover"
|
|
119
|
+
>
|
|
120
|
+
<template #trigger>
|
|
121
|
+
<button style="padding: 8px 16px; border: 1px solid #ccc; background: white; cursor: pointer;">
|
|
122
|
+
Тултип 3 (hover)
|
|
123
|
+
</button>
|
|
124
|
+
</template>
|
|
125
|
+
</BaseTooltip>
|
|
126
|
+
</div>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
<section>
|
|
130
|
+
<h2>BaseTable - Обычная таблица</h2>
|
|
131
|
+
<BaseTable
|
|
132
|
+
:columns="tableColumns"
|
|
133
|
+
:data="tableData"
|
|
134
|
+
:show-row-selection="true"
|
|
135
|
+
:show-actions="true"
|
|
136
|
+
:actions="tableActions"
|
|
137
|
+
/>
|
|
138
|
+
</section>
|
|
139
|
+
|
|
140
|
+
<section>
|
|
141
|
+
<h2>BaseTable - С пагинацией</h2>
|
|
142
|
+
<BaseTable
|
|
143
|
+
:columns="tableColumns"
|
|
144
|
+
:data="tableData"
|
|
145
|
+
:pagination="{ perPage: 5, currentPage: 1 }"
|
|
146
|
+
:show-row-selection="true"
|
|
147
|
+
:show-actions="true"
|
|
148
|
+
:actions="tableActions"
|
|
149
|
+
@page-change="handlePageChange"
|
|
150
|
+
/>
|
|
151
|
+
</section>
|
|
152
|
+
|
|
4
153
|
<section>
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
154
|
+
<h2>BaseTable - С виртуальным скроллом (высота: 400px)</h2>
|
|
155
|
+
<p style="font-size: 14px; color: #666; margin-bottom: 12px;">
|
|
156
|
+
• Колонки ID, Имя, Email, Статус теперь имеют заданные ширины: 80px, 200px, 1fr, 120px<br/>
|
|
157
|
+
• Ширины колонок в thead и виртуальном скролле полностью синхронизированы<br/>
|
|
158
|
+
• Можно использовать px, fr, auto или числовые значения для ширины<br/>
|
|
159
|
+
• Автоматическая компенсация ширины скроллбара - колонки не разъезжаются<br/>
|
|
160
|
+
• Настраиваемое выравнивание: ID - center, Имя - left, Email - left, Статус - right
|
|
161
|
+
</p>
|
|
162
|
+
<BaseTable
|
|
163
|
+
:columns="tableColumns"
|
|
164
|
+
:data="largeTableData"
|
|
165
|
+
:virtual-scroll="true"
|
|
166
|
+
:virtual-scroll-threshold="50"
|
|
167
|
+
:virtual-scroll-height="400"
|
|
168
|
+
:show-row-selection="true"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@scroll="handleScroll"
|
|
172
|
+
@scroll-end="handleScrollEnd"
|
|
173
|
+
/>
|
|
11
174
|
</section>
|
|
12
175
|
</div>
|
|
13
176
|
</template>
|
|
14
177
|
<script setup lang="ts">
|
|
15
178
|
import { ref, watch, computed } from 'vue'
|
|
179
|
+
import BaseSelect from '../src/components/BaseSelect/BaseSelect.vue'
|
|
180
|
+
import BaseTooltip from '../src/components/BaseTooltip/BaseTooltip.vue'
|
|
181
|
+
import BaseUpload from '../src/components/BaseUpload/BaseUpload.vue'
|
|
182
|
+
import BaseTable from '../src/components/BaseTable/BaseTable.vue'
|
|
183
|
+
|
|
184
|
+
const options = ref([
|
|
185
|
+
{ id: 1, name: ' Option 1 Option 1 Option 1Option 1 Option 1Option 1' },
|
|
186
|
+
{ id: 2, name: 'Option 2 Option 2 Option 2Option 2 Option 2Option 2' },
|
|
187
|
+
{ id: 3, name: 'Option 3 Option 3 Option 3Option 3 Option 3Option 3' },
|
|
188
|
+
])
|
|
189
|
+
|
|
190
|
+
const searchableOptions = ref([
|
|
191
|
+
{ id: 'kz', name: 'Казахстан Казахстан Казахстан Казахстан Казахстан Казахстан Казахстан Казахстан Казахстан' },
|
|
192
|
+
{ id: 'ru', name: 'Россия' },
|
|
193
|
+
{ id: 'kg', name: 'Кыргызстан' },
|
|
194
|
+
{ id: 'uz', name: 'Узбекистан' },
|
|
195
|
+
{ id: 'tj', name: 'Таджикистан' },
|
|
196
|
+
{ id: 'tm', name: 'Туркменистан' },
|
|
197
|
+
{ id: 'af', name: 'Афганистан' },
|
|
198
|
+
{ id: 'mn', name: 'Монголия' },
|
|
199
|
+
{ id: 'cn', name: 'Китай' },
|
|
200
|
+
{ id: 'ir', name: 'Иран' },
|
|
201
|
+
{ id: 'tr', name: 'Турция' },
|
|
202
|
+
{ id: 'ge', name: 'Грузия' },
|
|
203
|
+
{ id: 'am', name: 'Армения' },
|
|
204
|
+
{ id: 'az', name: 'Азербайджан' },
|
|
205
|
+
{ id: 'by', name: 'Беларусь' },
|
|
206
|
+
{ id: 'ua', name: 'Украина' },
|
|
207
|
+
{ id: 'md', name: 'Молдова' },
|
|
208
|
+
{ id: 'lt', name: 'Литва' },
|
|
209
|
+
{ id: 'lv', name: 'Латвия' },
|
|
210
|
+
{ id: 'ee', name: 'Эстония' },
|
|
211
|
+
])
|
|
212
|
+
|
|
213
|
+
const errorMessage = ref(false)
|
|
214
|
+
|
|
215
|
+
function handleSelectError() {
|
|
216
|
+
errorMessage.value = true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Данные для таблицы
|
|
220
|
+
const tableColumns = ref([
|
|
221
|
+
{ key: 'id', label: 'ID', sortable: true, width: '80px', align: 'center' as const },
|
|
222
|
+
{ key: 'name', label: 'Имя', sortable: true, width: '200px', align: 'left' as const },
|
|
223
|
+
{ key: 'email', label: 'Email', sortable: true, width: '1fr', align: 'left' as const },
|
|
224
|
+
{ key: 'status', label: 'Статус', sortable: true, width: '120px', align: 'right' as const }
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
const tableData = ref([
|
|
228
|
+
{ id: 1, name: 'Иван Иванов', email: 'ivan@example.com', status: 'Активен' },
|
|
229
|
+
{ id: 2, name: 'Петр Петров', email: 'petr@example.com', status: 'Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен Неактивен' },
|
|
230
|
+
{ id: 3, name: 'Мария Сидорова', email: 'maria@example.com', status: 'Активен' },
|
|
231
|
+
{ id: 4, name: 'Анна Козлова', email: 'anna@example.com', status: 'Активен' },
|
|
232
|
+
{ id: 5, name: 'Сергей Волков', email: 'sergey@example.com', status: 'Неактивен' },
|
|
233
|
+
{ id: 6, name: 'Елена Морозова', email: 'elena@example.com', status: 'Активен' },
|
|
234
|
+
{ id: 7, name: 'Дмитрий Лебедев', email: 'dmitry@example.com', status: 'Активен' },
|
|
235
|
+
{ id: 8, name: 'Ольга Новикова', email: 'olga@example.com', status: 'Неактивен' }
|
|
236
|
+
])
|
|
16
237
|
|
|
238
|
+
// Большой набор данных для виртуального скролла
|
|
239
|
+
const largeTableData = computed(() => {
|
|
240
|
+
const data: any[] = []
|
|
241
|
+
for (let i = 1; i <= 100; i++) {
|
|
242
|
+
data.push({
|
|
243
|
+
id: i,
|
|
244
|
+
name: `Пользователь ${i}`,
|
|
245
|
+
email: `user${i}@example.com`,
|
|
246
|
+
status: i % 3 === 0 ? 'Неактивен Неактивен Неактивен Неактивен' : 'Активен'
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
console.log("🚀 ~ data:", data)
|
|
250
|
+
return data
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const tableActions = ref([
|
|
254
|
+
{
|
|
255
|
+
key: 'edit',
|
|
256
|
+
icon: 'edit-table',
|
|
257
|
+
handler: (row: any) => {
|
|
258
|
+
console.log('Редактировать:', row)
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: 'delete',
|
|
263
|
+
icon: 'trash-table',
|
|
264
|
+
handler: (row: any) => {
|
|
265
|
+
console.log('Удалить:', row)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
const handlePageChange = (page: number) => {
|
|
271
|
+
console.log('Смена страницы:', page)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const handleScroll = (payload: any) => {
|
|
275
|
+
console.log('Скролл:', payload)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const handleScrollEnd = () => {
|
|
279
|
+
console.log('Достигнут конец списка - можно подгрузить еще данные')
|
|
280
|
+
}
|
|
17
281
|
</script>
|
|
18
282
|
|
|
19
283
|
<style lang="scss" scoped>
|
package/package.json
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M15.3333 28.0001C22.3289 28.0001 28 22.329 28 15.3334C28 8.33781 22.3289 2.66675 15.3333 2.66675C8.33773 2.66675 2.66667 8.33781 2.66667 15.3334C2.66667 22.329 8.33773 28.0001 15.3333 28.0001Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M29.3333 29.3334L26.6667 26.6667" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|
|
@@ -114,17 +114,17 @@ const classList = computed(() => [
|
|
|
114
114
|
font: var(--typography-text-s-medium);
|
|
115
115
|
border-radius: var(--corner-radius-xs);
|
|
116
116
|
|
|
117
|
-
#{$button}--only-icon {
|
|
118
|
-
padding: 0;
|
|
119
|
-
width: 32px;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
117
|
.loader__circle-icon {
|
|
123
118
|
width: 20px;
|
|
124
119
|
height: 20px;
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
122
|
|
|
123
|
+
&.--extra-small-size#{&}--only-icon {
|
|
124
|
+
padding: 0;
|
|
125
|
+
width: 32px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
128
|
&.--small-size {
|
|
129
129
|
column-gap: var(--spacing-xs);
|
|
130
130
|
height: 40px;
|
|
@@ -132,17 +132,17 @@ const classList = computed(() => [
|
|
|
132
132
|
font: var(--typography-text-m-medium);
|
|
133
133
|
border-radius: var(--corner-radius-s);
|
|
134
134
|
|
|
135
|
-
#{$button}--only-icon {
|
|
136
|
-
padding: 0;
|
|
137
|
-
width: 40px;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
135
|
.loader__circle-icon {
|
|
141
136
|
width: 20px;
|
|
142
137
|
height: 20px;
|
|
143
138
|
}
|
|
144
139
|
}
|
|
145
140
|
|
|
141
|
+
&.--small-size#{&}--only-icon {
|
|
142
|
+
padding: 0;
|
|
143
|
+
width: 40px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
146
|
&.--medium-size {
|
|
147
147
|
column-gap: var(--spacing-xs);
|
|
148
148
|
height: 48px;
|
|
@@ -150,17 +150,17 @@ const classList = computed(() => [
|
|
|
150
150
|
font: var(--typography-text-l-medium);
|
|
151
151
|
border-radius: var(--corner-radius-m);
|
|
152
152
|
|
|
153
|
-
#{$button}--only-icon {
|
|
154
|
-
padding: 0;
|
|
155
|
-
width: 48px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
153
|
.loader__circle-icon {
|
|
159
154
|
width: 24px;
|
|
160
155
|
height: 24px;
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
|
|
159
|
+
&.--medium-size#{&}--only-icon {
|
|
160
|
+
padding: 0;
|
|
161
|
+
width: 48px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
164
|
&.--large-size {
|
|
165
165
|
column-gap: var(--spacing-xs);
|
|
166
166
|
height: 56px;
|
|
@@ -168,17 +168,17 @@ const classList = computed(() => [
|
|
|
168
168
|
font: var(--typography-text-l-medium);
|
|
169
169
|
border-radius: var(--corner-radius-m);
|
|
170
170
|
|
|
171
|
-
#{$button}--only-icon {
|
|
172
|
-
padding: 0;
|
|
173
|
-
width: 56px;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
171
|
.loader__circle-icon {
|
|
177
172
|
width: 32px;
|
|
178
173
|
height: 32px;
|
|
179
174
|
}
|
|
180
175
|
}
|
|
181
176
|
|
|
177
|
+
&.--large-size#{&}--only-icon {
|
|
178
|
+
padding: 0;
|
|
179
|
+
width: 56px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
182
|
&.--is-interactive {
|
|
183
183
|
height: 22px;
|
|
184
184
|
padding: 0;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
:disabled="actualDisabled"
|
|
9
9
|
>
|
|
10
10
|
<template #top>
|
|
11
|
+
<!-- Старый header для дефолтного поведения -->
|
|
11
12
|
<div
|
|
13
|
+
v-if="!searchable"
|
|
12
14
|
:data-error="Boolean(error)"
|
|
13
15
|
:data-disabled="disabled"
|
|
14
16
|
class="base-select__header"
|
|
@@ -21,12 +23,9 @@
|
|
|
21
23
|
<slot name="headerIcon" />
|
|
22
24
|
</div>
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
{{ actualOption.name }}
|
|
29
|
-
</div>
|
|
26
|
+
<div v-if="actualOption" class="base-select__header_value">
|
|
27
|
+
{{ actualOption.name }}
|
|
28
|
+
</div>
|
|
30
29
|
|
|
31
30
|
<div
|
|
32
31
|
v-else
|
|
@@ -48,15 +47,45 @@
|
|
|
48
47
|
</div>
|
|
49
48
|
</div>
|
|
50
49
|
</div>
|
|
50
|
+
|
|
51
|
+
<BaseInput
|
|
52
|
+
v-else
|
|
53
|
+
:id="id"
|
|
54
|
+
:model-value="displayValue || ''"
|
|
55
|
+
:placeholder="placeholder"
|
|
56
|
+
:disabled="disabled"
|
|
57
|
+
:readonly="readonly"
|
|
58
|
+
:size="size"
|
|
59
|
+
:error="Boolean(error)"
|
|
60
|
+
class="base-select__input"
|
|
61
|
+
@input="handleInputChange"
|
|
62
|
+
>
|
|
63
|
+
<template #left-icon>
|
|
64
|
+
<base-icon
|
|
65
|
+
name="search"
|
|
66
|
+
:size="size"
|
|
67
|
+
/>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<template #right-icon>
|
|
71
|
+
<base-icon
|
|
72
|
+
v-if="!readonly"
|
|
73
|
+
:name="actualOption ? 'close' : 'arrow-down'"
|
|
74
|
+
:size="size"
|
|
75
|
+
class="base-select__toggle-icon"
|
|
76
|
+
@click="handleIconClick"
|
|
77
|
+
/>
|
|
78
|
+
</template>
|
|
79
|
+
</BaseInput>
|
|
51
80
|
</template>
|
|
52
81
|
|
|
53
82
|
<template #dropdown>
|
|
54
83
|
<div
|
|
55
|
-
v-if="(
|
|
84
|
+
v-if="(filteredOptions ?? []).length"
|
|
56
85
|
class="base-select__dropdown"
|
|
57
|
-
>
|
|
86
|
+
>
|
|
58
87
|
<dynamic-scroller
|
|
59
|
-
:items="
|
|
88
|
+
:items="filteredOptions as ICoreSelectBaseProps[]"
|
|
60
89
|
:min-item-size="36"
|
|
61
90
|
key-field="id"
|
|
62
91
|
class="base-select__list"
|
|
@@ -86,6 +115,9 @@
|
|
|
86
115
|
</template>
|
|
87
116
|
</dynamic-scroller>
|
|
88
117
|
</div>
|
|
118
|
+
<div class="base-select__empty">
|
|
119
|
+
<slot name="empty" />
|
|
120
|
+
</div>
|
|
89
121
|
</template>
|
|
90
122
|
</base-dropdown>
|
|
91
123
|
</div>
|
|
@@ -99,6 +131,7 @@ import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
|
|
|
99
131
|
import type { ICoreSelectProps, TSelectValue, ICoreSelectBaseProps, ICoreSelectOption, ISelectSlotProps } from '../../types/input';
|
|
100
132
|
import BaseDropdown from '../BaseDropdown/BaseDropdown.vue';
|
|
101
133
|
import BaseIcon from '../BaseIcon/BaseIcon.vue';
|
|
134
|
+
import BaseInput from '../BaseInput/BaseInput.vue';
|
|
102
135
|
import BaseOpenedListItem from '../BaseOpenedListItem/BaseOpenedListItem.vue';
|
|
103
136
|
import { useKitSize } from '../../composables/kit/size';
|
|
104
137
|
import { useKitState } from '../../composables/kit/state';
|
|
@@ -109,18 +142,28 @@ const props = withDefaults(defineProps<ICoreSelectProps & {
|
|
|
109
142
|
}>(), {
|
|
110
143
|
options: () => [],
|
|
111
144
|
size: 'medium',
|
|
145
|
+
searchable: false,
|
|
112
146
|
});
|
|
113
147
|
|
|
114
148
|
const emit = defineEmits<{
|
|
115
149
|
(e: 'update:modelValue', value: TSelectValue): void
|
|
116
150
|
(e: 'change', value: TSelectValue): void
|
|
151
|
+
(e: 'error'): void
|
|
117
152
|
}>();
|
|
118
153
|
|
|
119
154
|
const actualValue = ref<TSelectValue>(props.modelValue ?? '');
|
|
155
|
+
const searchQuery = ref<string>('');
|
|
120
156
|
const actualOption = computed(() =>
|
|
121
157
|
props.options?.find((item: ICoreSelectOption) => item?.id === actualValue.value) || null
|
|
122
158
|
);
|
|
123
159
|
|
|
160
|
+
const displayValue = computed(() => {
|
|
161
|
+
if (actualOption.value) {
|
|
162
|
+
return actualOption.value.name;
|
|
163
|
+
}
|
|
164
|
+
return searchQuery.value;
|
|
165
|
+
});
|
|
166
|
+
|
|
124
167
|
watch(() => props.modelValue, (val) => {
|
|
125
168
|
actualValue.value = val ?? '';
|
|
126
169
|
}, { immediate: true });
|
|
@@ -129,10 +172,66 @@ function handleInput(value: TSelectValue) {
|
|
|
129
172
|
actualValue.value = value;
|
|
130
173
|
emit('update:modelValue', value);
|
|
131
174
|
emit('change', value);
|
|
175
|
+
dropdownVisible.value = false;
|
|
176
|
+
searchQuery.value = '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleInputChange(event: Event) {
|
|
180
|
+
const target = event.target as HTMLInputElement;
|
|
181
|
+
const inputValue = target.value;
|
|
182
|
+
searchQuery.value = inputValue;
|
|
183
|
+
|
|
184
|
+
if (inputValue) {
|
|
185
|
+
const exactMatch = props.options?.find((option: ICoreSelectOption) =>
|
|
186
|
+
option.name.toLowerCase() === inputValue.toLowerCase()
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (exactMatch) {
|
|
190
|
+
handleInput(exactMatch.id);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!dropdownVisible.value) {
|
|
195
|
+
dropdownVisible.value = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleIconClick(event: Event) {
|
|
201
|
+
event.stopPropagation();
|
|
202
|
+
if (actualOption.value) {
|
|
203
|
+
handleInput('');
|
|
204
|
+
} else {
|
|
205
|
+
dropdownVisible.value = !dropdownVisible.value;
|
|
206
|
+
}
|
|
132
207
|
}
|
|
133
208
|
|
|
134
209
|
const actualDisabled = computed(() => props.disabled || !props.options?.length);
|
|
135
210
|
const dropdownVisible = ref(false);
|
|
211
|
+
|
|
212
|
+
const filteredOptions = computed(() => {
|
|
213
|
+
if (!props.searchable || !searchQuery.value.trim()) {
|
|
214
|
+
return props.options || [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const query = searchQuery.value.toLowerCase().trim();
|
|
218
|
+
return (props.options || []).filter((option: ICoreSelectOption) =>
|
|
219
|
+
option.name.toLowerCase().includes(query)
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
watch(() => props.error, (error) => {
|
|
224
|
+
console.log('ERROR', error);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
watch(dropdownVisible, (isVisible) => {
|
|
228
|
+
if (!isVisible) {
|
|
229
|
+
if (searchQuery.value && !actualOption.value) {
|
|
230
|
+
emit('error');
|
|
231
|
+
}
|
|
232
|
+
searchQuery.value = '';
|
|
233
|
+
}
|
|
234
|
+
});
|
|
136
235
|
const { sizeClassList } = useKitSize(props);
|
|
137
236
|
const { stateClassList } = useKitState(props);
|
|
138
237
|
const { styleClassList } = useKitStyle(props);
|
|
@@ -162,6 +261,7 @@ defineSlots<{
|
|
|
162
261
|
iconItem(props: { item: ICoreSelectBaseProps }): any;
|
|
163
262
|
header(props: { value: ICoreSelectProps['options'] }): any;
|
|
164
263
|
headerIcon(): any;
|
|
264
|
+
empty(): any;
|
|
165
265
|
}>();
|
|
166
266
|
</script>
|
|
167
267
|
|
|
@@ -185,6 +285,10 @@ defineSlots<{
|
|
|
185
285
|
border: 1px solid var(--primary-black-300);
|
|
186
286
|
outline: 4px solid var(--effects-primary-focus);
|
|
187
287
|
}
|
|
288
|
+
|
|
289
|
+
&__toggle-icon {
|
|
290
|
+
transform: rotate(180deg);
|
|
291
|
+
}
|
|
188
292
|
}
|
|
189
293
|
|
|
190
294
|
.dropdown__dropdown {
|
|
@@ -202,6 +306,10 @@ defineSlots<{
|
|
|
202
306
|
#{$select}__header {
|
|
203
307
|
border: 1px solid var(--error-red-light-01);
|
|
204
308
|
}
|
|
309
|
+
|
|
310
|
+
#{$select}__toggle-icon {
|
|
311
|
+
color: var(--error-red);
|
|
312
|
+
}
|
|
205
313
|
}
|
|
206
314
|
|
|
207
315
|
&__wrapper {
|
|
@@ -225,6 +333,8 @@ defineSlots<{
|
|
|
225
333
|
|
|
226
334
|
&__header_value {
|
|
227
335
|
color: var(--primary-text-primary);
|
|
336
|
+
|
|
337
|
+
@include text-clamp(1);
|
|
228
338
|
}
|
|
229
339
|
|
|
230
340
|
&__placeholder {
|
|
@@ -247,6 +357,25 @@ defineSlots<{
|
|
|
247
357
|
transform: translate3d(0, -50%, 0);
|
|
248
358
|
}
|
|
249
359
|
|
|
360
|
+
&__input {
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
|
|
363
|
+
@include text-clamp(1);
|
|
364
|
+
|
|
365
|
+
&.--is-readonly {
|
|
366
|
+
cursor: default;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
&__toggle-icon {
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
transition: transform var(--transition);
|
|
373
|
+
|
|
374
|
+
&:hover {
|
|
375
|
+
opacity: 0.7;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
250
379
|
&__dropdown {
|
|
251
380
|
width: 100%;
|
|
252
381
|
height: 100%;
|
|
@@ -266,7 +395,7 @@ defineSlots<{
|
|
|
266
395
|
font: var(--typography-text-m-regular);
|
|
267
396
|
}
|
|
268
397
|
|
|
269
|
-
&__dropdown {
|
|
398
|
+
&__dropdown, &__input {
|
|
270
399
|
border-radius: var(--corner-radius-s);
|
|
271
400
|
}
|
|
272
401
|
|
|
@@ -289,6 +418,10 @@ defineSlots<{
|
|
|
289
418
|
font: var(--typography-text-m-regular);
|
|
290
419
|
}
|
|
291
420
|
|
|
421
|
+
&__input {
|
|
422
|
+
border-radius: var(--corner-radius-m);
|
|
423
|
+
}
|
|
424
|
+
|
|
292
425
|
&__header {
|
|
293
426
|
height: 48px;
|
|
294
427
|
padding: var(--spacing-m) var(--spacing-2l);
|
|
@@ -307,6 +440,10 @@ defineSlots<{
|
|
|
307
440
|
font: var(--typography-text-l-regular);
|
|
308
441
|
}
|
|
309
442
|
|
|
443
|
+
&__input {
|
|
444
|
+
border-radius: var(--corner-radius-l);
|
|
445
|
+
}
|
|
446
|
+
|
|
310
447
|
&__header {
|
|
311
448
|
height: 56px;
|
|
312
449
|
padding: var(--spacing-m) var(--spacing-l);
|
|
@@ -328,6 +465,10 @@ defineSlots<{
|
|
|
328
465
|
&__arrow {
|
|
329
466
|
color: var(--ui-colors-input-icon-disabled);
|
|
330
467
|
}
|
|
468
|
+
|
|
469
|
+
&__toggle-icon {
|
|
470
|
+
color: var(--ui-colors-input-icon-disabled);
|
|
471
|
+
}
|
|
331
472
|
}
|
|
332
473
|
}
|
|
333
474
|
|
|
@@ -336,6 +477,10 @@ defineSlots<{
|
|
|
336
477
|
&__header {
|
|
337
478
|
pointer-events: none;
|
|
338
479
|
}
|
|
480
|
+
|
|
481
|
+
&__input {
|
|
482
|
+
pointer-events: none;
|
|
483
|
+
}
|
|
339
484
|
}
|
|
340
485
|
}
|
|
341
486
|
}
|