@saasmakers/ui 0.1.59 → 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/BaseTag.vue +179 -0
- package/app/components/bases/BaseTags.vue +242 -0
- 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/useIcons.ts +4 -0
- package/app/composables/useUtils.ts +7 -0
- package/app/types/bases.d.ts +42 -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>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { BaseTag } from '../../types/bases'
|
|
3
|
+
import { NuxtLinkLocale } from '#components'
|
|
4
|
+
import { getIcon } from '../../composables/useIcons'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<BaseTag>(), {
|
|
7
|
+
active: false,
|
|
8
|
+
avatar: '',
|
|
9
|
+
circle: '',
|
|
10
|
+
clickable: true,
|
|
11
|
+
color: 'black',
|
|
12
|
+
draggable: false,
|
|
13
|
+
editable: false,
|
|
14
|
+
icon: undefined,
|
|
15
|
+
iconSize: undefined,
|
|
16
|
+
id: undefined,
|
|
17
|
+
isCreation: false,
|
|
18
|
+
light: true,
|
|
19
|
+
removable: false,
|
|
20
|
+
rounded: false,
|
|
21
|
+
size: 'base',
|
|
22
|
+
text: '',
|
|
23
|
+
to: undefined,
|
|
24
|
+
truncate: false,
|
|
25
|
+
uppercase: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
click: [event: MouseEvent, id?: number | string]
|
|
30
|
+
close: [event: MouseEvent, id?: number | string]
|
|
31
|
+
inputBlur: [event: FocusEvent, name: string, id?: number | string]
|
|
32
|
+
inputSubmit: [event: KeyboardEvent, name: string, id?: number | string]
|
|
33
|
+
remove: [event: MouseEvent, id?: number | string]
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
const hovered = ref(false)
|
|
37
|
+
|
|
38
|
+
const form = reactive({ name: '' })
|
|
39
|
+
|
|
40
|
+
onBeforeMount(() => {
|
|
41
|
+
if (!props.isCreation) {
|
|
42
|
+
initializeFormForExistingTag()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
function initializeFormForExistingTag() {
|
|
47
|
+
form.name = String(props.text)?.substring(1)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onClick(event: MouseEvent) {
|
|
51
|
+
if (!props.removable) {
|
|
52
|
+
emit('click', event, props.id)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onInputBlur(event: FocusEvent) {
|
|
57
|
+
emit('inputBlur', event, form.name.trim(), props.id)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function onInputSubmit(event: KeyboardEvent) {
|
|
61
|
+
emit('inputSubmit', event, form.name.trim(), props.id)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onMouseEnter() {
|
|
65
|
+
hovered.value = true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onMouseLeave() {
|
|
69
|
+
hovered.value = false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onRemove(event: MouseEvent) {
|
|
73
|
+
emit('remove', event, props.id)
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<span
|
|
79
|
+
class="flex select-none"
|
|
80
|
+
@click.stop="onClick"
|
|
81
|
+
@mouseenter="onMouseEnter"
|
|
82
|
+
@mouseleave="onMouseLeave"
|
|
83
|
+
>
|
|
84
|
+
<component
|
|
85
|
+
:is="to ? NuxtLinkLocale : 'span'"
|
|
86
|
+
class="inline-flex items-center border font-medium"
|
|
87
|
+
:class="{
|
|
88
|
+
'cursor-pointer': clickable,
|
|
89
|
+
'rounded-md': !rounded,
|
|
90
|
+
'rounded-full': rounded,
|
|
91
|
+
'uppercase': uppercase,
|
|
92
|
+
|
|
93
|
+
'border-gray-200 dark:border-gray-800 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200': (color === 'black' && light && !active) || removable,
|
|
94
|
+
'border-gray-900 dark:border-gray-100 bg-gray-900 dark:bg-gray-100 text-white dark:text-black': color === 'black' && (!light || active),
|
|
95
|
+
'border-gray-700 dark:border-gray-300 bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-300': color === 'gray' && light && !active,
|
|
96
|
+
'border-gray-800 dark:border-gray-200 bg-gray-800 dark:bg-gray-200 text-white dark:text-black': color === 'gray' && (!light || active),
|
|
97
|
+
'border-green-700 dark:border-green-300 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300': color === 'green' && light && !active,
|
|
98
|
+
'border-green-800 dark:border-green-200 bg-green-800 dark:bg-green-200 text-white dark:text-black': color === 'green' && (!light || active),
|
|
99
|
+
'border-indigo-700 dark:border-indigo-300 bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300': color === 'indigo' && light && !active,
|
|
100
|
+
'border-indigo-800 dark:border-indigo-200 bg-indigo-800 dark:bg-indigo-200 text-white dark:text-black': color === 'indigo' && (!light || active),
|
|
101
|
+
'border-orange-700 dark:border-orange-300 bg-white dark:bg-gray-900 text-orange-700 dark:text-orange-300': color === 'orange' && light && !active,
|
|
102
|
+
'border-orange-800 dark:border-orange-200 bg-orange-800 dark:bg-orange-200 text-white dark:text-black': color === 'orange' && (!light || active),
|
|
103
|
+
'border-red-700 dark:border-red-300 bg-white dark:bg-gray-900 text-red-700 dark:text-red-300': color === 'red' && light && !active,
|
|
104
|
+
'border-red-800 dark:border-red-200 bg-red-800 dark:bg-red-200 text-white dark:text-black': color === 'red' && (!light || active),
|
|
105
|
+
'border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200': color === 'white' && light && !active,
|
|
106
|
+
'border-white dark:border-gray-900 text-white dark:text-black': color === 'white' && (!light || active),
|
|
107
|
+
'border-dashed border-gray-400 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200': color === 'white' && light && !active,
|
|
108
|
+
|
|
109
|
+
'py-1.5 px-2.5 text-2xs': size === 'sm',
|
|
110
|
+
'py-2 px-3 text-xs': size === 'base',
|
|
111
|
+
}"
|
|
112
|
+
:to="to"
|
|
113
|
+
>
|
|
114
|
+
<BaseIcon
|
|
115
|
+
v-if="draggable"
|
|
116
|
+
class="js-drag-handle mr-2 cursor-move"
|
|
117
|
+
clickable
|
|
118
|
+
color="gray"
|
|
119
|
+
:icon="getIcon('drag')"
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<span
|
|
123
|
+
v-if="avatar || circle || icon"
|
|
124
|
+
class="mr-1 flex items-center text-center"
|
|
125
|
+
:class="{
|
|
126
|
+
'h-2.5 w-2.5': size === 'sm' || iconSize === 2.5,
|
|
127
|
+
'h-3 w-3': size === 'base' || iconSize === 3,
|
|
128
|
+
'h-3.5 w-3.5': iconSize === 3.5,
|
|
129
|
+
'h-4 w-4': iconSize === 4,
|
|
130
|
+
}"
|
|
131
|
+
>
|
|
132
|
+
<BaseAvatar
|
|
133
|
+
v-if="avatar"
|
|
134
|
+
class="h-full w-full"
|
|
135
|
+
rounded="sm"
|
|
136
|
+
:shadow="false"
|
|
137
|
+
:src="avatar"
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<BaseIcon
|
|
141
|
+
v-else-if="icon"
|
|
142
|
+
class="h-full w-full"
|
|
143
|
+
:icon="icon"
|
|
144
|
+
/>
|
|
145
|
+
</span>
|
|
146
|
+
|
|
147
|
+
<span
|
|
148
|
+
v-if="$slots.left"
|
|
149
|
+
class="mr-1"
|
|
150
|
+
>
|
|
151
|
+
<slot name="left" />
|
|
152
|
+
</span>
|
|
153
|
+
|
|
154
|
+
<BaseText
|
|
155
|
+
v-if="!editable"
|
|
156
|
+
class="whitespace-nowrap outline-none"
|
|
157
|
+
:class="{ truncate }"
|
|
158
|
+
:text="text"
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
<FieldInput
|
|
162
|
+
v-else
|
|
163
|
+
v-model="form.name"
|
|
164
|
+
border="none"
|
|
165
|
+
class="w-16"
|
|
166
|
+
size="sm"
|
|
167
|
+
@blur="onInputBlur"
|
|
168
|
+
@submit="onInputSubmit"
|
|
169
|
+
/>
|
|
170
|
+
|
|
171
|
+
<BaseIcon
|
|
172
|
+
v-if="removable"
|
|
173
|
+
class="ml-1.5 text-red-700 dark:text-red-300 hover:text-black dark:hover:text-white"
|
|
174
|
+
:icon="getIcon('closeCircle')"
|
|
175
|
+
@click.prevent.stop="onRemove"
|
|
176
|
+
/>
|
|
177
|
+
</component>
|
|
178
|
+
</span>
|
|
179
|
+
</template>
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { BaseTags } from '../../types/bases'
|
|
3
|
+
import { getIcon } from '../../composables/useIcons'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<BaseTags>(), {
|
|
6
|
+
active: false,
|
|
7
|
+
clickable: true,
|
|
8
|
+
draggable: false,
|
|
9
|
+
editable: false,
|
|
10
|
+
hasBack: false,
|
|
11
|
+
hasCreation: false,
|
|
12
|
+
maxTags: Number.POSITIVE_INFINITY,
|
|
13
|
+
removable: false,
|
|
14
|
+
selectable: false,
|
|
15
|
+
selectableUnique: false,
|
|
16
|
+
size: 'base',
|
|
17
|
+
tags: () => [],
|
|
18
|
+
value: () => [],
|
|
19
|
+
wrap: false,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits<{
|
|
23
|
+
'attach': [event: MouseEvent, tagId?: number | string]
|
|
24
|
+
'back': [event: MouseEvent]
|
|
25
|
+
'click': [event: MouseEvent]
|
|
26
|
+
'create': [event: MouseEvent, name: string]
|
|
27
|
+
'detach': [event: MouseEvent, tagId?: number | string]
|
|
28
|
+
'remove': [event: MouseEvent, tagId?: number | string]
|
|
29
|
+
'update': [event: MouseEvent, name: string, tagId?: number | string]
|
|
30
|
+
'update:modelValue': [value: (number | string)[]]
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
const keyForTagCreation = ref(Date.now())
|
|
34
|
+
const keyForTagUpdate = ref(Date.now())
|
|
35
|
+
const root = ref<HTMLDivElement>()
|
|
36
|
+
const showingAllTags = ref(false)
|
|
37
|
+
const showingTagCreationField = ref(false)
|
|
38
|
+
|
|
39
|
+
const { t } = useI18n()
|
|
40
|
+
|
|
41
|
+
const sortedTags = computed({
|
|
42
|
+
get() {
|
|
43
|
+
return props.tags
|
|
44
|
+
.slice()
|
|
45
|
+
.filter((_tag, tagIndex) => tagIndex < props.maxTags || showingAllTags.value)
|
|
46
|
+
.sort((tagA, tagB) => (tagA.order! < tagB.order! ? -1 : 0))
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
set(tags) {
|
|
50
|
+
const suiteForTags = tags
|
|
51
|
+
.map((tag, order) => ({
|
|
52
|
+
id: tag.id,
|
|
53
|
+
order,
|
|
54
|
+
}))
|
|
55
|
+
.map(tag => ({
|
|
56
|
+
id: tag.id,
|
|
57
|
+
order: tag.order,
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
return suiteForTags
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function onCreateTag(event: MouseEvent, name: string) {
|
|
65
|
+
showingTagCreationField.value = false
|
|
66
|
+
keyForTagCreation.value = Date.now()
|
|
67
|
+
|
|
68
|
+
emit('create', event, name)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function onGoBack(event: MouseEvent) {
|
|
72
|
+
emit('back', event)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function onShowTagField() {
|
|
76
|
+
if (!showingTagCreationField.value) {
|
|
77
|
+
showingTagCreationField.value = true
|
|
78
|
+
|
|
79
|
+
// Focus contenteditable
|
|
80
|
+
nextTick(() => {
|
|
81
|
+
const element = root.value?.querySelector<HTMLInputElement>('input[type="text"]')
|
|
82
|
+
|
|
83
|
+
element?.focus()
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onShowAllTags() {
|
|
89
|
+
showingAllTags.value = true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onTagClick(event: MouseEvent, tagId?: number | string) {
|
|
93
|
+
if (props.selectable && tagId) {
|
|
94
|
+
let value = [...props.value]
|
|
95
|
+
|
|
96
|
+
if (value.includes(tagId)) {
|
|
97
|
+
value.splice(value.indexOf(tagId), 1)
|
|
98
|
+
emit('detach', event, tagId)
|
|
99
|
+
}
|
|
100
|
+
else if (props.selectableUnique) {
|
|
101
|
+
value = [tagId]
|
|
102
|
+
emit('attach', event, tagId)
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
value.push(tagId)
|
|
106
|
+
emit('attach', event, tagId)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
emit('update:modelValue', value)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onUpdateTag(event: MouseEvent, name: string, tagId?: number | string) {
|
|
114
|
+
if (name) {
|
|
115
|
+
emit('update', event, name, tagId)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
else {
|
|
119
|
+
keyForTagUpdate.value = Date.now()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function onRemoveTag(event: MouseEvent, tagId?: number | string) {
|
|
124
|
+
emit('remove', event, tagId)
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<div
|
|
130
|
+
ref="root"
|
|
131
|
+
class="flex items-center"
|
|
132
|
+
:class="{
|
|
133
|
+
'flex-wrap': wrap,
|
|
134
|
+
'w-full overflow-x-auto': !wrap,
|
|
135
|
+
|
|
136
|
+
'-mt-0.75 -ml-0.75': size === 'sm',
|
|
137
|
+
'-mt-1 -ml-1': size === 'base',
|
|
138
|
+
}"
|
|
139
|
+
>
|
|
140
|
+
<BaseTag
|
|
141
|
+
v-if="hasBack"
|
|
142
|
+
key="BaseTagToGoBack"
|
|
143
|
+
:class="{
|
|
144
|
+
'm-0.75': size === 'sm',
|
|
145
|
+
'm-1': size === 'base',
|
|
146
|
+
}"
|
|
147
|
+
color="indigo"
|
|
148
|
+
:icon="getIcon('back')"
|
|
149
|
+
:size="size"
|
|
150
|
+
:text="t('cancel')"
|
|
151
|
+
@click="onGoBack"
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<BaseTag
|
|
155
|
+
v-if="hasCreation"
|
|
156
|
+
:key="keyForTagCreation"
|
|
157
|
+
:class="{
|
|
158
|
+
'm-0.75': size === 'sm',
|
|
159
|
+
'm-1': size === 'base',
|
|
160
|
+
}"
|
|
161
|
+
color="indigo"
|
|
162
|
+
:editable="showingTagCreationField"
|
|
163
|
+
:icon="showingTagCreationField ? getIcon('tags') : getIcon('plus')"
|
|
164
|
+
is-creation
|
|
165
|
+
:light="false"
|
|
166
|
+
:size="size"
|
|
167
|
+
:text="showingTagCreationField ? '' : t('newTag')"
|
|
168
|
+
@click="onShowTagField"
|
|
169
|
+
@input:blur="onCreateTag"
|
|
170
|
+
@input:submit="onCreateTag"
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
<div class="flex items-center">
|
|
174
|
+
<span
|
|
175
|
+
v-for="(tag, tagIndex) in sortedTags"
|
|
176
|
+
:key="tag.id ? `${tag.id}_${tag.order}_${keyForTagUpdate}` : tagIndex"
|
|
177
|
+
class="flex flex-wrap items-center"
|
|
178
|
+
:class="{
|
|
179
|
+
'm-0.75': size === 'sm',
|
|
180
|
+
'm-1': size === 'base',
|
|
181
|
+
}"
|
|
182
|
+
>
|
|
183
|
+
<template v-if="tag">
|
|
184
|
+
<BaseTag
|
|
185
|
+
:id="tag.id || tagIndex"
|
|
186
|
+
:key="`${tag.id}_${tagIndex}`"
|
|
187
|
+
:active="tag.id && value.includes(tag.id) || tag.active"
|
|
188
|
+
:avatar="tag.avatar"
|
|
189
|
+
:circle="tag.circle"
|
|
190
|
+
:clickable="clickable"
|
|
191
|
+
:color="tag.color"
|
|
192
|
+
:draggable="draggable"
|
|
193
|
+
:editable="editable"
|
|
194
|
+
:icon="tag.icon"
|
|
195
|
+
:icon-size="tag.iconSize"
|
|
196
|
+
:light="tag.light"
|
|
197
|
+
:removable="removable"
|
|
198
|
+
:size="size"
|
|
199
|
+
:text="tag.text"
|
|
200
|
+
:to="tag.to"
|
|
201
|
+
@click="onTagClick"
|
|
202
|
+
@input:blur="onUpdateTag"
|
|
203
|
+
@input:submit="onUpdateTag"
|
|
204
|
+
@remove="onRemoveTag"
|
|
205
|
+
/>
|
|
206
|
+
</template>
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<BaseTag
|
|
211
|
+
v-if="tags.length > maxTags && !showingAllTags"
|
|
212
|
+
key="showMore"
|
|
213
|
+
:class="{
|
|
214
|
+
'm-0.75': size === 'sm',
|
|
215
|
+
'm-1': size === 'base',
|
|
216
|
+
}"
|
|
217
|
+
:size="size"
|
|
218
|
+
:text="t('showMore', { value: tags.length - maxTags })"
|
|
219
|
+
@click="onShowAllTags"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
</template>
|
|
223
|
+
|
|
224
|
+
<i18n lang="json">
|
|
225
|
+
{
|
|
226
|
+
"en": {
|
|
227
|
+
"cancel": "Cancel",
|
|
228
|
+
"newTag": "New tag",
|
|
229
|
+
"showMore": "& {value} other | & {value} others"
|
|
230
|
+
},
|
|
231
|
+
"fr": {
|
|
232
|
+
"cancel": "Annuler",
|
|
233
|
+
"newTag": "Nouveau tag",
|
|
234
|
+
"showMore": "& {value} autre | & {value} autres"
|
|
235
|
+
},
|
|
236
|
+
"ja": {
|
|
237
|
+
"cancel": "キャンセル",
|
|
238
|
+
"newTag": "新しいタグ",
|
|
239
|
+
"showMore": "& {value} その他 | & {value} その他"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
</i18n>
|
|
@@ -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>
|