@saasmakers/ui 1.4.29 → 1.4.31
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.
|
@@ -15,9 +15,9 @@ export default {
|
|
|
15
15
|
},
|
|
16
16
|
hideNext: { control: 'boolean' },
|
|
17
17
|
hidePrevious: { control: 'boolean' },
|
|
18
|
+
loading: { control: 'boolean' },
|
|
18
19
|
margin: { control: 'number' },
|
|
19
20
|
navigable: { control: 'boolean' },
|
|
20
|
-
loading: { control: 'boolean' },
|
|
21
21
|
size: {
|
|
22
22
|
control: 'select',
|
|
23
23
|
options: ['sm', 'base'] satisfies BaseDividerSize[],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
import type { FieldInput } from '#components'
|
|
2
3
|
import { vOnClickOutside } from '@vueuse/components'
|
|
3
4
|
import type { FieldSelect, FieldSelectOption } from '../../types/fields'
|
|
4
5
|
|
|
@@ -18,6 +19,8 @@ const props = withDefaults(defineProps<Omit<FieldSelect, 'modelValue'>>(), {
|
|
|
18
19
|
padding: true,
|
|
19
20
|
placeholder: '',
|
|
20
21
|
required: false,
|
|
22
|
+
searchable: false,
|
|
23
|
+
searchPlaceholder: '',
|
|
21
24
|
size: 'base',
|
|
22
25
|
validation: undefined,
|
|
23
26
|
})
|
|
@@ -30,11 +33,24 @@ const emit = defineEmits<{
|
|
|
30
33
|
|
|
31
34
|
const { getIcon } = useLayerIcons()
|
|
32
35
|
const { fadeIn } = useMotion()
|
|
36
|
+
const { normalizeText } = useLayerUtils()
|
|
37
|
+
const { t } = useI18n()
|
|
33
38
|
const id = useId()
|
|
34
39
|
|
|
35
40
|
const modelValue = defineModel<FieldSelect['modelValue']>({ default: '' })
|
|
36
41
|
|
|
37
42
|
const opened = ref(false)
|
|
43
|
+
const searchRaw = ref('')
|
|
44
|
+
|
|
45
|
+
const searchInput = ref<InstanceType<typeof FieldInput>>()
|
|
46
|
+
|
|
47
|
+
const computedColumns = computed(() => {
|
|
48
|
+
if (props.columns.length > 0) {
|
|
49
|
+
return props.columns
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return [{ options: computedOptions.value }]
|
|
53
|
+
})
|
|
38
54
|
|
|
39
55
|
const computedOptions = computed(() => {
|
|
40
56
|
const options: FieldSelectOption[] = []
|
|
@@ -56,12 +72,28 @@ const computedOptions = computed(() => {
|
|
|
56
72
|
return options
|
|
57
73
|
})
|
|
58
74
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const searchQueryCleaned = computed(() => {
|
|
76
|
+
return normalizeText(searchRaw.value.trim())
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const filteredColumns = computed(() => {
|
|
80
|
+
if (!props.searchable || !searchQueryCleaned.value) {
|
|
81
|
+
return computedColumns.value
|
|
62
82
|
}
|
|
63
83
|
|
|
64
|
-
return
|
|
84
|
+
return computedColumns.value
|
|
85
|
+
.map(column => ({
|
|
86
|
+
...column,
|
|
87
|
+
options: column.options.filter((option) => {
|
|
88
|
+
return normalizeText(option.text).includes(searchQueryCleaned.value)
|
|
89
|
+
|| normalizeText(option.value).includes(searchQueryCleaned.value)
|
|
90
|
+
}),
|
|
91
|
+
}))
|
|
92
|
+
.filter(column => column.options.length > 0)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const hasFilteredResults = computed(() => {
|
|
96
|
+
return filteredColumns.value.some(column => column.options.length > 0)
|
|
65
97
|
})
|
|
66
98
|
|
|
67
99
|
const selectedOption = computed(() => {
|
|
@@ -70,6 +102,18 @@ const selectedOption = computed(() => {
|
|
|
70
102
|
})
|
|
71
103
|
})
|
|
72
104
|
|
|
105
|
+
const showNoResults = computed(() => {
|
|
106
|
+
return props.searchable && !!searchQueryCleaned.value && !hasFilteredResults.value
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
watch(opened, (isOpened) => {
|
|
110
|
+
if (isOpened && props.searchable) {
|
|
111
|
+
nextTick(() => {
|
|
112
|
+
searchInput.value?.focus()
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
73
117
|
function onClose() {
|
|
74
118
|
reset()
|
|
75
119
|
}
|
|
@@ -128,8 +172,15 @@ function onOptionKeyDown(event: KeyboardEvent) {
|
|
|
128
172
|
}
|
|
129
173
|
}
|
|
130
174
|
|
|
175
|
+
function onSearchKeyup(event: KeyboardEvent) {
|
|
176
|
+
if (event.key === 'Escape') {
|
|
177
|
+
reset()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
131
181
|
function reset() {
|
|
132
182
|
opened.value = false
|
|
183
|
+
searchRaw.value = ''
|
|
133
184
|
}
|
|
134
185
|
|
|
135
186
|
function selectOption(event: MouseEvent, value: string) {
|
|
@@ -248,7 +299,33 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
248
299
|
}"
|
|
249
300
|
>
|
|
250
301
|
<div
|
|
251
|
-
v-
|
|
302
|
+
v-if="searchable"
|
|
303
|
+
class="sticky top-0 z-10 border-b border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900"
|
|
304
|
+
@click.stop
|
|
305
|
+
>
|
|
306
|
+
<FieldInput
|
|
307
|
+
ref="searchInput"
|
|
308
|
+
v-model="searchRaw"
|
|
309
|
+
:autocomplete="false"
|
|
310
|
+
background="white"
|
|
311
|
+
border="none"
|
|
312
|
+
class="px-3"
|
|
313
|
+
:placeholder="searchPlaceholder || t('search')"
|
|
314
|
+
:size="size"
|
|
315
|
+
type="search"
|
|
316
|
+
@keyup="onSearchKeyup"
|
|
317
|
+
/>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div
|
|
321
|
+
v-if="showNoResults"
|
|
322
|
+
class="px-3 py-2 text-center text-gray-600 dark:text-gray-400"
|
|
323
|
+
>
|
|
324
|
+
{{ t('noResults') }}
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<div
|
|
328
|
+
v-for="(column, columnIndex) in filteredColumns"
|
|
252
329
|
:key="columnIndex"
|
|
253
330
|
>
|
|
254
331
|
<BaseIcon
|
|
@@ -298,3 +375,60 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
298
375
|
/>
|
|
299
376
|
</div>
|
|
300
377
|
</template>
|
|
378
|
+
|
|
379
|
+
<i18n lang="json">
|
|
380
|
+
{
|
|
381
|
+
"de": {
|
|
382
|
+
"noResults": "Keine Ergebnisse",
|
|
383
|
+
"search": "Suchen"
|
|
384
|
+
},
|
|
385
|
+
"en": {
|
|
386
|
+
"noResults": "No results",
|
|
387
|
+
"search": "Search"
|
|
388
|
+
},
|
|
389
|
+
"es": {
|
|
390
|
+
"noResults": "Sin resultados",
|
|
391
|
+
"search": "Buscar"
|
|
392
|
+
},
|
|
393
|
+
"fr": {
|
|
394
|
+
"noResults": "Aucun résultat",
|
|
395
|
+
"search": "Rechercher"
|
|
396
|
+
},
|
|
397
|
+
"it": {
|
|
398
|
+
"noResults": "Nessun risultato",
|
|
399
|
+
"search": "Cerca"
|
|
400
|
+
},
|
|
401
|
+
"ja": {
|
|
402
|
+
"noResults": "結果がありません",
|
|
403
|
+
"search": "検索"
|
|
404
|
+
},
|
|
405
|
+
"ko": {
|
|
406
|
+
"noResults": "결과 없음",
|
|
407
|
+
"search": "검색"
|
|
408
|
+
},
|
|
409
|
+
"nl": {
|
|
410
|
+
"noResults": "Geen resultaten",
|
|
411
|
+
"search": "Zoeken"
|
|
412
|
+
},
|
|
413
|
+
"pl": {
|
|
414
|
+
"noResults": "Brak wyników",
|
|
415
|
+
"search": "Szukaj"
|
|
416
|
+
},
|
|
417
|
+
"pt": {
|
|
418
|
+
"noResults": "Sem resultados",
|
|
419
|
+
"search": "Pesquisar"
|
|
420
|
+
},
|
|
421
|
+
"pt-BR": {
|
|
422
|
+
"noResults": "Nenhum resultado",
|
|
423
|
+
"search": "Pesquisar"
|
|
424
|
+
},
|
|
425
|
+
"id": {
|
|
426
|
+
"noResults": "Tidak ada hasil",
|
|
427
|
+
"search": "Cari"
|
|
428
|
+
},
|
|
429
|
+
"vi": {
|
|
430
|
+
"noResults": "Không có kết quả",
|
|
431
|
+
"search": "Tìm kiếm"
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
</i18n>
|
package/app/types/bases.d.ts
CHANGED
|
@@ -109,9 +109,9 @@ export interface BaseDivider {
|
|
|
109
109
|
borderStyle?: BaseDividerBorderStyle
|
|
110
110
|
hideNext?: boolean
|
|
111
111
|
hidePrevious?: boolean
|
|
112
|
+
loading?: boolean
|
|
112
113
|
margin?: number
|
|
113
114
|
navigable?: boolean
|
|
114
|
-
loading?: boolean
|
|
115
115
|
size?: BaseDividerSize
|
|
116
116
|
title?: string
|
|
117
117
|
}
|
package/app/types/fields.d.ts
CHANGED