@saasmakers/ui 0.1.60 → 0.1.61
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/app/components/bases/BaseAvatar.vue +5 -5
- package/app/components/bases/BaseButton.vue +15 -1
- package/app/components/bases/BaseIcon.vue +15 -1
- package/app/components/bases/BaseQuote.vue +15 -1
- package/app/components/bases/BaseTags.vue +5 -1
- package/app/components/fields/FieldCheckbox.vue +98 -0
- package/app/components/fields/FieldDays.vue +109 -0
- package/app/components/fields/FieldEmojis.vue +104 -0
- package/app/components/fields/FieldInput.vue +209 -0
- package/app/components/fields/FieldLabel.vue +64 -0
- package/app/components/fields/FieldMessage.vue +209 -0
- package/app/components/fields/FieldSelect.vue +291 -0
- package/app/components/fields/FieldTabs.vue +100 -0
- package/app/components/fields/FieldTextarea.vue +123 -0
- package/app/components/fields/FieldTime.vue +73 -0
- package/app/composables/useDevice.ts +11 -0
- package/app/composables/useUtils.ts +7 -0
- package/app/types/fields.d.ts +179 -0
- package/app/types/global.d.ts +10 -0
- package/nuxt.config.ts +8 -4
- package/package.json +7 -1
|
@@ -74,7 +74,7 @@ function onLoad() {
|
|
|
74
74
|
@mouseleave="onMouseLeave"
|
|
75
75
|
>
|
|
76
76
|
<div
|
|
77
|
-
:aria-label="editable ? t('
|
|
77
|
+
:aria-label="editable ? t('edit') : undefined"
|
|
78
78
|
class="relative h-full w-full overflow-hidden"
|
|
79
79
|
:class="{
|
|
80
80
|
'rounded-full': circular,
|
|
@@ -103,7 +103,7 @@ function onLoad() {
|
|
|
103
103
|
v-if="editable && hovered"
|
|
104
104
|
class="absolute bottom-0 left-0 right-0 w-full bg-gray-900 text-center text-white dark:bg-gray-900 dark:text-white"
|
|
105
105
|
size="3xs"
|
|
106
|
-
:text="t('
|
|
106
|
+
:text="t('edit')"
|
|
107
107
|
/>
|
|
108
108
|
|
|
109
109
|
<input
|
|
@@ -126,13 +126,13 @@ function onLoad() {
|
|
|
126
126
|
<i18n lang="json">
|
|
127
127
|
{
|
|
128
128
|
"en": {
|
|
129
|
-
"
|
|
129
|
+
"edit": "Edit"
|
|
130
130
|
},
|
|
131
131
|
"fr": {
|
|
132
|
-
"
|
|
132
|
+
"edit": "Modifier"
|
|
133
133
|
},
|
|
134
134
|
"ja": {
|
|
135
|
-
"
|
|
135
|
+
"edit": "編集"
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
</i18n>
|
|
@@ -126,7 +126,7 @@ function onClick(event: MouseEvent) {
|
|
|
126
126
|
:light="light"
|
|
127
127
|
:reverse="reverse"
|
|
128
128
|
:size="size"
|
|
129
|
-
:text="confirming ? t('
|
|
129
|
+
:text="confirming ? t('confirm') : text"
|
|
130
130
|
/>
|
|
131
131
|
</span>
|
|
132
132
|
|
|
@@ -138,3 +138,17 @@ function onClick(event: MouseEvent) {
|
|
|
138
138
|
/>
|
|
139
139
|
</component>
|
|
140
140
|
</template>
|
|
141
|
+
|
|
142
|
+
<i18n lang="json">
|
|
143
|
+
{
|
|
144
|
+
"en": {
|
|
145
|
+
"confirm": "Confirm"
|
|
146
|
+
},
|
|
147
|
+
"fr": {
|
|
148
|
+
"confirm": "Confirmer"
|
|
149
|
+
},
|
|
150
|
+
"ja": {
|
|
151
|
+
"confirm": "確認"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
</i18n>
|
|
@@ -132,10 +132,24 @@ function onClick(event: MouseEvent) {
|
|
|
132
132
|
no-wrap
|
|
133
133
|
:reverse="reverse"
|
|
134
134
|
:size="size"
|
|
135
|
-
:text="confirming ? t('
|
|
135
|
+
:text="confirming ? t('confirm') : text"
|
|
136
136
|
:truncate="truncate"
|
|
137
137
|
:underline="underline"
|
|
138
138
|
:uppercase="uppercase"
|
|
139
139
|
/>
|
|
140
140
|
</div>
|
|
141
141
|
</template>
|
|
142
|
+
|
|
143
|
+
<i18n lang="json">
|
|
144
|
+
{
|
|
145
|
+
"en": {
|
|
146
|
+
"confirm": "Confirm"
|
|
147
|
+
},
|
|
148
|
+
"fr": {
|
|
149
|
+
"confirm": "Confirmer"
|
|
150
|
+
},
|
|
151
|
+
"ja": {
|
|
152
|
+
"confirm": "確認"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
</i18n>
|
|
@@ -136,7 +136,7 @@ function onClose(event: MouseEvent) {
|
|
|
136
136
|
|
|
137
137
|
<BaseIcon
|
|
138
138
|
v-if="hasClose"
|
|
139
|
-
v-tooltip="t('
|
|
139
|
+
v-tooltip="t('close')"
|
|
140
140
|
class="mt-1 self-start text-gray-600 flex-initial dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400"
|
|
141
141
|
:class="{
|
|
142
142
|
'ml-2': size === 'xs',
|
|
@@ -150,3 +150,17 @@ function onClose(event: MouseEvent) {
|
|
|
150
150
|
</div>
|
|
151
151
|
</div>
|
|
152
152
|
</template>
|
|
153
|
+
|
|
154
|
+
<i18n lang="json">
|
|
155
|
+
{
|
|
156
|
+
"en": {
|
|
157
|
+
"close": "Close"
|
|
158
|
+
},
|
|
159
|
+
"fr": {
|
|
160
|
+
"close": "Fermer"
|
|
161
|
+
},
|
|
162
|
+
"ja": {
|
|
163
|
+
"close": "閉じる"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
</i18n>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { BaseTags } from '../../types/bases'
|
|
3
|
+
import { getIcon } from '../../composables/useIcons'
|
|
3
4
|
|
|
4
5
|
const props = withDefaults(defineProps<BaseTags>(), {
|
|
5
6
|
active: false,
|
|
@@ -146,7 +147,7 @@ function onRemoveTag(event: MouseEvent, tagId?: number | string) {
|
|
|
146
147
|
color="indigo"
|
|
147
148
|
:icon="getIcon('back')"
|
|
148
149
|
:size="size"
|
|
149
|
-
:text="t('
|
|
150
|
+
:text="t('cancel')"
|
|
150
151
|
@click="onGoBack"
|
|
151
152
|
/>
|
|
152
153
|
|
|
@@ -223,14 +224,17 @@ function onRemoveTag(event: MouseEvent, tagId?: number | string) {
|
|
|
223
224
|
<i18n lang="json">
|
|
224
225
|
{
|
|
225
226
|
"en": {
|
|
227
|
+
"cancel": "Cancel",
|
|
226
228
|
"newTag": "New tag",
|
|
227
229
|
"showMore": "& {value} other | & {value} others"
|
|
228
230
|
},
|
|
229
231
|
"fr": {
|
|
232
|
+
"cancel": "Annuler",
|
|
230
233
|
"newTag": "Nouveau tag",
|
|
231
234
|
"showMore": "& {value} autre | & {value} autres"
|
|
232
235
|
},
|
|
233
236
|
"ja": {
|
|
237
|
+
"cancel": "キャンセル",
|
|
234
238
|
"newTag": "新しいタグ",
|
|
235
239
|
"showMore": "& {value} その他 | & {value} その他"
|
|
236
240
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldCheckbox } from '../../types/fields'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<FieldCheckbox>(), {
|
|
5
|
+
description: '',
|
|
6
|
+
disabled: false,
|
|
7
|
+
fullWidth: false,
|
|
8
|
+
hideError: false,
|
|
9
|
+
label: '',
|
|
10
|
+
labelIcon: undefined,
|
|
11
|
+
lineThrough: false,
|
|
12
|
+
loading: false,
|
|
13
|
+
modelValue: false,
|
|
14
|
+
required: false,
|
|
15
|
+
size: 'base',
|
|
16
|
+
truncate: false,
|
|
17
|
+
uppercase: false,
|
|
18
|
+
validation: undefined,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
'update:modelValue': [value: boolean ]
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const input = ref<HTMLInputElement>()
|
|
26
|
+
const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
|
|
27
|
+
|
|
28
|
+
const value = computed({
|
|
29
|
+
get() {
|
|
30
|
+
return props.modelValue
|
|
31
|
+
},
|
|
32
|
+
set(value) {
|
|
33
|
+
emit('update:modelValue', value || false)
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
function onFieldChange(event: Event) {
|
|
38
|
+
const value = (event.target as HTMLInputElement).checked
|
|
39
|
+
|
|
40
|
+
emit('update:modelValue', value)
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div
|
|
46
|
+
class="relative flex flex-col"
|
|
47
|
+
:class="{ 'w-full': fullWidth }"
|
|
48
|
+
@click.stop
|
|
49
|
+
>
|
|
50
|
+
<div class="flex flex-1 items-center">
|
|
51
|
+
<input
|
|
52
|
+
v-if="!loading"
|
|
53
|
+
:id="uuid"
|
|
54
|
+
ref="input"
|
|
55
|
+
v-model="value"
|
|
56
|
+
class="cursor-pointer border border-gray-300 rounded-lg accent-indigo-700 transition-shadow flex-initial dark:border-gray-700 focus:border-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-indigo-500/40 dark:focus:border-gray-500 dark:hover:border-gray-600"
|
|
57
|
+
:class="{
|
|
58
|
+
'disabled:opacity-50 disabled:cursor-not-allowed': disabled,
|
|
59
|
+
|
|
60
|
+
'h-3.5 w-3.5': size === 'xs',
|
|
61
|
+
'h-4 w-4': size === 'sm',
|
|
62
|
+
'h-4.5 w-4.5': size === 'base',
|
|
63
|
+
'h-5 w-5': size === 'lg',
|
|
64
|
+
}"
|
|
65
|
+
:disabled="disabled"
|
|
66
|
+
type="checkbox"
|
|
67
|
+
@change="onFieldChange"
|
|
68
|
+
>
|
|
69
|
+
|
|
70
|
+
<BaseSpinner
|
|
71
|
+
v-else
|
|
72
|
+
class="flex-initial"
|
|
73
|
+
:size="size"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<FieldLabel
|
|
77
|
+
v-if="label"
|
|
78
|
+
class="flex-1"
|
|
79
|
+
:disabled="disabled"
|
|
80
|
+
:for-field="uuid"
|
|
81
|
+
has-margin-left
|
|
82
|
+
:icon="labelIcon"
|
|
83
|
+
:label="label"
|
|
84
|
+
:line-through="lineThrough && modelValue"
|
|
85
|
+
:required="required"
|
|
86
|
+
:size="size"
|
|
87
|
+
:truncate="truncate"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<FieldMessage
|
|
92
|
+
:description="description"
|
|
93
|
+
:hide-error="hideError"
|
|
94
|
+
:size="size"
|
|
95
|
+
:validation="validation"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldDays } from '../../types/fields'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<FieldDays>(), { modelValue: () => [] })
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits<{
|
|
7
|
+
'change': [event: MouseEvent, value: number[]]
|
|
8
|
+
'update:modelValue': [value: number[]]
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
|
|
13
|
+
const daysToDisplay = computed(() => {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
label: t('days.monday'),
|
|
17
|
+
value: 1,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: t('days.tuesday'),
|
|
21
|
+
value: 2,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: t('days.wednesday'),
|
|
25
|
+
value: 3,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: t('days.thursday'),
|
|
29
|
+
value: 4,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: t('days.friday'),
|
|
33
|
+
value: 5,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: t('days.saturday'),
|
|
37
|
+
value: 6,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: t('days.sunday'),
|
|
41
|
+
value: 0,
|
|
42
|
+
},
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
function onUpdateDays(event: MouseEvent, day: number) {
|
|
47
|
+
const days = [...props.modelValue]
|
|
48
|
+
|
|
49
|
+
days.includes(day) ? days.splice(days.indexOf(day), 1) : days.push(day)
|
|
50
|
+
|
|
51
|
+
emit('change', event, days)
|
|
52
|
+
emit('update:modelValue', days)
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div class="flex items-center">
|
|
58
|
+
<BaseText
|
|
59
|
+
v-for="day in daysToDisplay"
|
|
60
|
+
:key="day.value"
|
|
61
|
+
class="mr-2 h-7 w-7 flex cursor-pointer items-center justify-center border rounded-full shadow-inner last:mr-0"
|
|
62
|
+
:class="{
|
|
63
|
+
'border-indigo-700 dark:border-indigo-300 bg-gray-100 dark:bg-gray-900 text-indigo-700 dark:text-indigo-300': modelValue.includes(day.value),
|
|
64
|
+
'border-red-700 dark:border-red-300 bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300': !modelValue.includes(day.value),
|
|
65
|
+
}"
|
|
66
|
+
size="xs"
|
|
67
|
+
:text="day.label.charAt(0)"
|
|
68
|
+
@click="onUpdateDays($event, day.value)"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<i18n lang="json">
|
|
74
|
+
{
|
|
75
|
+
"en": {
|
|
76
|
+
"days": {
|
|
77
|
+
"monday": "Monday",
|
|
78
|
+
"tuesday": "Tuesday",
|
|
79
|
+
"wednesday": "Wednesday",
|
|
80
|
+
"thursday": "Thursday",
|
|
81
|
+
"friday": "Friday",
|
|
82
|
+
"saturday": "Saturday",
|
|
83
|
+
"sunday": "Sunday"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"fr": {
|
|
87
|
+
"days": {
|
|
88
|
+
"monday": "Lundi",
|
|
89
|
+
"tuesday": "Mardi",
|
|
90
|
+
"wednesday": "Mercredi",
|
|
91
|
+
"thursday": "Jeudi",
|
|
92
|
+
"friday": "Vendredi",
|
|
93
|
+
"saturday": "Samedi",
|
|
94
|
+
"sunday": "Dimanche"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"ja": {
|
|
98
|
+
"days": {
|
|
99
|
+
"monday": "月曜日",
|
|
100
|
+
"tuesday": "火曜日",
|
|
101
|
+
"wednesday": "水曜日",
|
|
102
|
+
"thursday": "木曜日",
|
|
103
|
+
"friday": "金曜日",
|
|
104
|
+
"saturday": "土曜日",
|
|
105
|
+
"sunday": "日曜日"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</i18n>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldEmojis } from '../../types/fields'
|
|
3
|
+
import { emojis } from '@saasmakers/shared'
|
|
4
|
+
import { refDebounced } from '@vueuse/core'
|
|
5
|
+
|
|
6
|
+
withDefaults(defineProps<FieldEmojis>(), { modelValue: undefined })
|
|
7
|
+
|
|
8
|
+
const emit = defineEmits<{
|
|
9
|
+
'click': [event: MouseEvent, emoji: string]
|
|
10
|
+
'update:modelValue': [value: string]
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const { locale, t } = useI18n()
|
|
14
|
+
|
|
15
|
+
const searchRaw = ref('')
|
|
16
|
+
const searchQuery = refDebounced(searchRaw, 250)
|
|
17
|
+
|
|
18
|
+
const emojisFiltered = computed(() => {
|
|
19
|
+
const searchQueryCleaned = normalizeText(searchQuery.value.trim())
|
|
20
|
+
|
|
21
|
+
if (!searchQueryCleaned) {
|
|
22
|
+
return emojis
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return emojis
|
|
26
|
+
.map((category) => {
|
|
27
|
+
const filtered = category.emojis.filter((emoji) => {
|
|
28
|
+
const localizedKeywords = (emoji.keywords[locale.value] ?? [])
|
|
29
|
+
const englishKeywords = (emoji.keywords.en ?? [])
|
|
30
|
+
const allKeywords = [...localizedKeywords, ...englishKeywords]
|
|
31
|
+
|
|
32
|
+
return allKeywords.some(keyword => normalizeText(keyword).includes(searchQueryCleaned))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...category,
|
|
37
|
+
emojis: filtered,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.filter(category => category.emojis.length > 0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function onEmojiClick(event: MouseEvent, emoji?: string) {
|
|
44
|
+
emit('click', event, emoji || '')
|
|
45
|
+
emit('update:modelValue', emoji || '')
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<div class="border border-gray-200 rounded-lg p-4 shadow-sm dark:border-gray-800">
|
|
51
|
+
<FieldInput
|
|
52
|
+
v-model="searchRaw"
|
|
53
|
+
autofocus
|
|
54
|
+
:placeholder="t('searchEmoji')"
|
|
55
|
+
size="sm"
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<div class="h-48 overflow-y-auto">
|
|
59
|
+
<template
|
|
60
|
+
v-for="category in emojisFiltered"
|
|
61
|
+
:key="category.category"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
v-if="category.emojis.length"
|
|
65
|
+
class="mt-4"
|
|
66
|
+
>
|
|
67
|
+
<div class="mb-4">
|
|
68
|
+
<BaseText
|
|
69
|
+
class="mb-2 text-xs text-gray-700 dark:text-gray-300"
|
|
70
|
+
size="xs"
|
|
71
|
+
:text="category.category"
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<div class="flex flex-wrap">
|
|
75
|
+
<BaseEmoji
|
|
76
|
+
v-for="emoji in category.emojis"
|
|
77
|
+
:key="emoji.id"
|
|
78
|
+
class="rounded-lg p-1.5 hover:bg-gray-200 dark:hover:bg-gray-800"
|
|
79
|
+
clickable
|
|
80
|
+
:emoji="emoji.id"
|
|
81
|
+
size="xl"
|
|
82
|
+
@click="onEmojiClick"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<i18n lang="json">
|
|
93
|
+
{
|
|
94
|
+
"en": {
|
|
95
|
+
"searchEmoji": "Search an emoji"
|
|
96
|
+
},
|
|
97
|
+
"fr": {
|
|
98
|
+
"searchEmoji": "Rechercher un emoji (en anglais)"
|
|
99
|
+
},
|
|
100
|
+
"ja": {
|
|
101
|
+
"searchEmoji": "絵文字を検索 (英語)"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
</i18n>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { FieldInput } from '../../types/fields'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<FieldInput>(), {
|
|
5
|
+
alignment: 'left',
|
|
6
|
+
autocomplete: true,
|
|
7
|
+
autofocus: false,
|
|
8
|
+
background: 'gray',
|
|
9
|
+
border: 'full',
|
|
10
|
+
description: '',
|
|
11
|
+
disabled: false,
|
|
12
|
+
fullWidth: false,
|
|
13
|
+
hideError: false,
|
|
14
|
+
label: '',
|
|
15
|
+
labelIcon: undefined,
|
|
16
|
+
lineThrough: false,
|
|
17
|
+
loading: false,
|
|
18
|
+
lowercaseOnly: false,
|
|
19
|
+
max: undefined,
|
|
20
|
+
min: undefined,
|
|
21
|
+
modelValue: '',
|
|
22
|
+
placeholder: '',
|
|
23
|
+
required: false,
|
|
24
|
+
size: 'base',
|
|
25
|
+
type: 'text',
|
|
26
|
+
uppercase: false,
|
|
27
|
+
validation: undefined,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits<{
|
|
31
|
+
'blur': [event: FocusEvent]
|
|
32
|
+
'focus': [event: FocusEvent]
|
|
33
|
+
'keyup': [event: KeyboardEvent, value: number | string ]
|
|
34
|
+
'submit': [event: KeyboardEvent, value: number | string ]
|
|
35
|
+
'update:modelValue': [value: number | string ]
|
|
36
|
+
}>()
|
|
37
|
+
|
|
38
|
+
const input = ref<HTMLInputElement>()
|
|
39
|
+
const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
|
|
40
|
+
|
|
41
|
+
const { isDesktopBrowser } = useDevice()
|
|
42
|
+
|
|
43
|
+
onMounted(() => {
|
|
44
|
+
if (props.autofocus && input.value && isDesktopBrowser.value) {
|
|
45
|
+
input.value.focus()
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const value = computed({
|
|
50
|
+
get() {
|
|
51
|
+
return props.modelValue
|
|
52
|
+
},
|
|
53
|
+
set(value) {
|
|
54
|
+
emit('update:modelValue', value || '')
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
function blur() {
|
|
59
|
+
if (input.value) {
|
|
60
|
+
input.value.blur()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function focus() {
|
|
65
|
+
if (input.value) {
|
|
66
|
+
input.value.focus()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function onFieldBlur(event: FocusEvent) {
|
|
71
|
+
emit('blur', event)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function onFieldFocus(event: FocusEvent) {
|
|
75
|
+
emit('focus', event)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function onFieldInput(event: Event) {
|
|
79
|
+
let value: number | string = (event.target as HTMLInputElement).value
|
|
80
|
+
|
|
81
|
+
if (props.type === 'number' && props.max && Number.parseInt(value) > props.max) {
|
|
82
|
+
value = props.max
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (props.lowercaseOnly) {
|
|
86
|
+
value = value.toString().toLowerCase()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
emit('update:modelValue', value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onFieldKeyDown(event: KeyboardEvent) {
|
|
93
|
+
const value = (event.target as HTMLInputElement).value
|
|
94
|
+
|
|
95
|
+
if (['Control', 'Escape'].includes(event.key)) {
|
|
96
|
+
blur()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
else if (event.key === 'Enter' && props.modelValue) {
|
|
100
|
+
emit('submit', event, value)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
else if (props.type === 'number' && [
|
|
104
|
+
'+',
|
|
105
|
+
'-',
|
|
106
|
+
':',
|
|
107
|
+
'^',
|
|
108
|
+
'e',
|
|
109
|
+
'E',
|
|
110
|
+
].includes(event.key)) {
|
|
111
|
+
return event.preventDefault()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
emit('keyup', event, value)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Expose
|
|
118
|
+
defineExpose({ focus })
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<template>
|
|
122
|
+
<div
|
|
123
|
+
class="relative flex flex-col"
|
|
124
|
+
:class="{ 'w-full': fullWidth }"
|
|
125
|
+
>
|
|
126
|
+
<div
|
|
127
|
+
v-if="label"
|
|
128
|
+
class="flex justify-between"
|
|
129
|
+
>
|
|
130
|
+
<FieldLabel
|
|
131
|
+
:disabled="disabled"
|
|
132
|
+
:for-field="uuid"
|
|
133
|
+
has-margin-bottom
|
|
134
|
+
:icon="labelIcon"
|
|
135
|
+
:label="label"
|
|
136
|
+
:required="required"
|
|
137
|
+
:size="size"
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<slot name="label" />
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div
|
|
144
|
+
class="flex items-center"
|
|
145
|
+
:class="{
|
|
146
|
+
'h-8': size === 'xs',
|
|
147
|
+
'h-10': size === 'sm',
|
|
148
|
+
'h-12': size === 'base',
|
|
149
|
+
'h-14': size === 'lg',
|
|
150
|
+
}"
|
|
151
|
+
>
|
|
152
|
+
<slot name="inputLeft" />
|
|
153
|
+
|
|
154
|
+
<input
|
|
155
|
+
:id="uuid"
|
|
156
|
+
ref="input"
|
|
157
|
+
v-model="value"
|
|
158
|
+
:autocomplete="autocomplete ? 'on' : 'off'"
|
|
159
|
+
class="h-full w-full flex-1 appearance-none items-center rounded-lg outline-none placeholder-gray-600 dark:placeholder-gray-400 focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100"
|
|
160
|
+
:class="{
|
|
161
|
+
'text-gray-900 dark:text-gray-100': !lineThrough,
|
|
162
|
+
'text-gray-500 dark:text-gray-500 line-through': lineThrough,
|
|
163
|
+
'font-medium tracking-tight uppercase': uppercase,
|
|
164
|
+
'font-normal': !uppercase,
|
|
165
|
+
|
|
166
|
+
'text-center': alignment === 'center',
|
|
167
|
+
'text-left': alignment === 'left',
|
|
168
|
+
'text-right': alignment === 'right',
|
|
169
|
+
|
|
170
|
+
'bg-gray-100 dark:bg-gray-900': background === 'gray',
|
|
171
|
+
'bg-white dark:bg-gray-900': background === 'white',
|
|
172
|
+
|
|
173
|
+
'border shadow-sm border-gray-200 dark:border-gray-800 px-4 hover:border-gray-300 focus:border-gray-400 dark:hover:border-gray-700 dark:focus:border-gray-600': border === 'full',
|
|
174
|
+
'border-b border-gray-200 dark:border-gray-800 px-10': border === 'bottom',
|
|
175
|
+
'border-0 p-0': border === 'none',
|
|
176
|
+
|
|
177
|
+
'text-xs': size === 'xs',
|
|
178
|
+
'text-sm': size === 'sm',
|
|
179
|
+
'text-base': size === 'base',
|
|
180
|
+
'text-lg': size === 'lg',
|
|
181
|
+
}"
|
|
182
|
+
:max="max"
|
|
183
|
+
:min="min"
|
|
184
|
+
:placeholder="placeholder"
|
|
185
|
+
spellcheck="false"
|
|
186
|
+
:type="type"
|
|
187
|
+
@blur="onFieldBlur"
|
|
188
|
+
@focus="onFieldFocus"
|
|
189
|
+
@input="onFieldInput"
|
|
190
|
+
@keydown="onFieldKeyDown"
|
|
191
|
+
>
|
|
192
|
+
|
|
193
|
+
<BaseSpinner
|
|
194
|
+
v-if="loading"
|
|
195
|
+
class="ml-4 flex-initial"
|
|
196
|
+
size="sm"
|
|
197
|
+
/>
|
|
198
|
+
|
|
199
|
+
<slot name="inputRight" />
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<FieldMessage
|
|
203
|
+
:description="description"
|
|
204
|
+
:hide-error="hideError"
|
|
205
|
+
:size="size"
|
|
206
|
+
:validation="validation"
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
</template>
|