@fy-/fws-vue 0.3.2 → 0.3.4
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/components/ui/DefaultInput.vue +35 -8
- package/components/ui/DefaultTagInput.vue +170 -0
- package/index.ts +2 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { LinkIcon } from "@heroicons/vue/24/solid";
|
|
|
3
3
|
import { computed, ref, toRef } from "vue";
|
|
4
4
|
import type { ErrorObject } from "@vuelidate/core";
|
|
5
5
|
import { useTranslation } from "../../composables/translations";
|
|
6
|
-
|
|
6
|
+
import DefaultTagInput from "./DefaultTagInput.vue";
|
|
7
7
|
type modelValueType = string | number | string[] | number[] | undefined;
|
|
8
8
|
|
|
9
9
|
type checkboxValueType = any[] | Set<any> | undefined | boolean;
|
|
@@ -25,6 +25,7 @@ const props = withDefaults(
|
|
|
25
25
|
options?: string[][];
|
|
26
26
|
help?: string;
|
|
27
27
|
error?: string;
|
|
28
|
+
color?: string;
|
|
28
29
|
errorVuelidate?: ErrorObject[];
|
|
29
30
|
disabled?: boolean;
|
|
30
31
|
}>(),
|
|
@@ -106,7 +107,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
106
107
|
'select',
|
|
107
108
|
'phone',
|
|
108
109
|
'chips',
|
|
109
|
-
'
|
|
110
|
+
'tags',
|
|
110
111
|
'mask',
|
|
111
112
|
].includes(type)
|
|
112
113
|
"
|
|
@@ -128,6 +129,12 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
128
129
|
"
|
|
129
130
|
class="relative"
|
|
130
131
|
>
|
|
132
|
+
<label
|
|
133
|
+
:for="id"
|
|
134
|
+
v-if="label"
|
|
135
|
+
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
136
|
+
>{{ label }}
|
|
137
|
+
</label>
|
|
131
138
|
<input
|
|
132
139
|
ref="inputRef"
|
|
133
140
|
:type="type"
|
|
@@ -136,27 +143,41 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
136
143
|
:class="{
|
|
137
144
|
error: checkErrors,
|
|
138
145
|
}"
|
|
146
|
+
v-model="model"
|
|
139
147
|
:autocomplete="autocomplete"
|
|
140
148
|
:disabled="disabled"
|
|
141
149
|
:aria-describedby="help ? `${id}-help` : id"
|
|
142
|
-
class="
|
|
150
|
+
class="bg-fv-neutral-50 border border-fv-neutral-300 text-fv-neutral-900 text-sm rounded-lg focus:ring-fv-primary-500 focus:border-fv-primary-500 block w-full p-2.5 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500"
|
|
143
151
|
:required="req"
|
|
144
152
|
@focus="handleFocus"
|
|
145
153
|
@blur="handleBlur"
|
|
146
154
|
/>
|
|
155
|
+
</div>
|
|
156
|
+
<div v-if="type == 'chips' || type == 'tags'">
|
|
147
157
|
<label
|
|
148
158
|
:for="id"
|
|
149
159
|
v-if="label || placeholder"
|
|
150
|
-
class="
|
|
160
|
+
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
151
161
|
>{{ label ? label : placeholder }}
|
|
152
162
|
</label>
|
|
163
|
+
<!-- @vue-skip -->
|
|
164
|
+
<DefaultTagInput
|
|
165
|
+
v-model="model"
|
|
166
|
+
:id="id"
|
|
167
|
+
:disabled="disabled"
|
|
168
|
+
:color="color"
|
|
169
|
+
:error="checkErrors"
|
|
170
|
+
:help="help"
|
|
171
|
+
/>
|
|
153
172
|
</div>
|
|
154
173
|
<div class="group relative" v-else-if="type == 'textarea'">
|
|
155
174
|
<label
|
|
175
|
+
v-if="label"
|
|
156
176
|
:for="id"
|
|
157
177
|
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
158
|
-
>
|
|
178
|
+
>{{ label }}</label
|
|
159
179
|
>
|
|
180
|
+
<!-- @vue-skip -->
|
|
160
181
|
<textarea
|
|
161
182
|
:id="id"
|
|
162
183
|
:name="id"
|
|
@@ -164,6 +185,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
164
185
|
:class="{
|
|
165
186
|
error: checkErrors,
|
|
166
187
|
}"
|
|
188
|
+
v-model="model"
|
|
167
189
|
:placeholder="placeholder"
|
|
168
190
|
:disabled="disabled"
|
|
169
191
|
:aria-describedby="help ? `${id}-help` : id"
|
|
@@ -184,6 +206,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
184
206
|
:id="id"
|
|
185
207
|
:name="id"
|
|
186
208
|
ref="inputRef"
|
|
209
|
+
v-model="model"
|
|
187
210
|
:disabled="disabled"
|
|
188
211
|
:aria-describedby="help ? `${id}-help` : id"
|
|
189
212
|
:required="req"
|
|
@@ -215,12 +238,14 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
215
238
|
<input
|
|
216
239
|
type="checkbox"
|
|
217
240
|
v-model="modelCheckbox"
|
|
241
|
+
:true-value="checkboxTrueValue"
|
|
242
|
+
:false-value="checkboxFalseValue"
|
|
218
243
|
class="sr-only peer"
|
|
219
244
|
@focus="handleFocus"
|
|
220
245
|
@blur="handleBlur"
|
|
221
246
|
/>
|
|
222
247
|
<div
|
|
223
|
-
class="relative w-11 h-6 bg-fv-neutral-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-fv-primary-300 dark:peer-focus:ring-fv-primary-800 rounded-full peer dark:bg-fv-neutral-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-fv-neutral-300 after:border after:rounded-full after:w-5 after:h-5 after:transition-all dark:border-fv-neutral-600 peer-checked:bg-fv-primary-600"
|
|
248
|
+
class="relative flex-0 flex-shrink-0 w-11 h-6 bg-fv-neutral-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-fv-primary-300 dark:peer-focus:ring-fv-primary-800 rounded-full peer dark:bg-fv-neutral-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-fv-neutral-300 after:border after:rounded-full after:w-5 after:h-5 after:transition-all dark:border-fv-neutral-600 peer-checked:bg-fv-primary-600"
|
|
224
249
|
></div>
|
|
225
250
|
<span
|
|
226
251
|
class="ms-3 text-sm font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
@@ -246,6 +271,8 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
246
271
|
:type="type"
|
|
247
272
|
@focus="handleFocus"
|
|
248
273
|
@blur="handleBlur"
|
|
274
|
+
:true-value="checkboxTrueValue"
|
|
275
|
+
:false-value="checkboxFalseValue"
|
|
249
276
|
v-model="modelCheckbox"
|
|
250
277
|
class="w-4 h-4 text-fv-primary-600 bg-fv-neutral-100 border-fv-neutral-300 rounded focus:ring-fv-primary-500 dark:focus:ring-fv-primary-600 dark:ring-offset-fv-neutral-800 dark:focus:ring-offset-fv-neutral-800 focus:ring-2 dark:bg-fv-neutral-700 dark:border-fv-neutral-600"
|
|
251
278
|
/>
|
|
@@ -253,13 +280,13 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
253
280
|
<div class="ms-2 text-sm">
|
|
254
281
|
<label
|
|
255
282
|
:for="id"
|
|
256
|
-
class="font-medium text-
|
|
283
|
+
class="font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
257
284
|
>{{ label }}</label
|
|
258
285
|
>
|
|
259
286
|
<p
|
|
260
287
|
:id="`${id}-help`"
|
|
261
288
|
v-if="help"
|
|
262
|
-
class="text-xs font-normal text-
|
|
289
|
+
class="text-xs font-normal text-fv-neutral-500 dark:text-fv-neutral-400"
|
|
263
290
|
>
|
|
264
291
|
{{ help }}
|
|
265
292
|
</p>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="`tags-input ${$props.error ? 'error' : ''}`"
|
|
4
|
+
@click="focusInput"
|
|
5
|
+
@keydown.delete.prevent="removeLastTag"
|
|
6
|
+
@keydown.enter.prevent="addTag"
|
|
7
|
+
>
|
|
8
|
+
<span v-for="(tag, index) in tags" :key="index" :class="`tag ${color}`">
|
|
9
|
+
{{ tag }}
|
|
10
|
+
<button type="button" @click.prevent="removeTag(index)">
|
|
11
|
+
<svg
|
|
12
|
+
class="w-4 h-4"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
fill="none"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
stroke-width="1.5"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
>
|
|
19
|
+
<path
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
stroke-linejoin="round"
|
|
22
|
+
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
</button>
|
|
26
|
+
</span>
|
|
27
|
+
<div
|
|
28
|
+
contenteditable
|
|
29
|
+
class="input"
|
|
30
|
+
:id="`tags_${id}`"
|
|
31
|
+
ref="textInput"
|
|
32
|
+
@input="handleInput"
|
|
33
|
+
@paste.prevent="handlePaste"
|
|
34
|
+
placeholder="Add a tag..."
|
|
35
|
+
></div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import { ref, watch, onMounted } from "vue";
|
|
41
|
+
type colorType = "blue" | "red" | "green" | "purple" | "orange" | "neutral";
|
|
42
|
+
|
|
43
|
+
const props = withDefaults(
|
|
44
|
+
defineProps<{
|
|
45
|
+
modelValue: string[];
|
|
46
|
+
color?: colorType;
|
|
47
|
+
label?: string;
|
|
48
|
+
id: string;
|
|
49
|
+
separators?: string[];
|
|
50
|
+
autofocus?: boolean;
|
|
51
|
+
help?: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>(),
|
|
54
|
+
{
|
|
55
|
+
color: "blue",
|
|
56
|
+
label: "Tags",
|
|
57
|
+
separators: () => [","],
|
|
58
|
+
autofocus: false,
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
63
|
+
const tags = ref([...props.modelValue]);
|
|
64
|
+
const textInput = ref<HTMLElement>();
|
|
65
|
+
|
|
66
|
+
watch(
|
|
67
|
+
tags,
|
|
68
|
+
(newTags) => {
|
|
69
|
+
emit("update:modelValue", newTags);
|
|
70
|
+
},
|
|
71
|
+
{ deep: true },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
onMounted(() => {
|
|
75
|
+
if (props.autofocus) {
|
|
76
|
+
focusInput();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const handleInput = (event: any) => {
|
|
81
|
+
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
82
|
+
if (separatorsRegex.test(event.data)) {
|
|
83
|
+
addTag();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const addTag = () => {
|
|
88
|
+
if (!textInput.value) return;
|
|
89
|
+
|
|
90
|
+
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
91
|
+
const newTags = textInput.value.innerText
|
|
92
|
+
.split(separatorsRegex)
|
|
93
|
+
.map((tag: string) => tag.trim())
|
|
94
|
+
.filter((tag: string) => tag.length > 0);
|
|
95
|
+
tags.value.push(...newTags);
|
|
96
|
+
textInput.value.innerText = "";
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const removeTag = (index: number) => {
|
|
100
|
+
tags.value.splice(index, 1);
|
|
101
|
+
focusInput();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const removeLastTag = () => {
|
|
105
|
+
if (!textInput.value) return;
|
|
106
|
+
|
|
107
|
+
if (textInput.value.innerText === "") {
|
|
108
|
+
tags.value.pop();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const focusInput = () => {
|
|
113
|
+
if (!textInput.value) return;
|
|
114
|
+
|
|
115
|
+
textInput.value.focus();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handlePaste = (e: any) => {
|
|
119
|
+
if (!textInput.value) return;
|
|
120
|
+
|
|
121
|
+
// @ts-ignore
|
|
122
|
+
const text = (e.clipboardData || window.clipboardData).getData("text");
|
|
123
|
+
const separatorsRegex = new RegExp(props.separators.join("|"), "g");
|
|
124
|
+
const pasteText = text.replace(separatorsRegex, ",");
|
|
125
|
+
textInput.value.innerText += pasteText;
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
addTag();
|
|
128
|
+
};
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<style scoped>
|
|
132
|
+
.tags-input {
|
|
133
|
+
cursor: text;
|
|
134
|
+
@apply flex flex-wrap gap-2 items-center shadow-sm bg-fv-neutral-50 border border-fv-neutral-300 text-fv-neutral-900 text-sm rounded-sm focus:ring-fv-primary-500 focus:border-fv-primary-500 w-full p-2.5 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500;
|
|
135
|
+
&.error {
|
|
136
|
+
@apply border-red-500 dark:border-red-400 border !important;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
.tag-label {
|
|
140
|
+
@apply block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white;
|
|
141
|
+
}
|
|
142
|
+
.tag {
|
|
143
|
+
@apply inline-flex gap-1 font-medium px-2.5 py-0.5 rounded text-black dark:text-white;
|
|
144
|
+
&.blue {
|
|
145
|
+
@apply bg-blue-400 dark:bg-blue-900;
|
|
146
|
+
}
|
|
147
|
+
&.red {
|
|
148
|
+
@apply bg-red-400 dark:bg-red-900;
|
|
149
|
+
}
|
|
150
|
+
&.green {
|
|
151
|
+
@apply bg-green-400 dark:bg-green-900;
|
|
152
|
+
}
|
|
153
|
+
&.purple {
|
|
154
|
+
@apply bg-purple-400 dark:bg-purple-900;
|
|
155
|
+
}
|
|
156
|
+
&.orange {
|
|
157
|
+
@apply bg-orange-400 dark:bg-orange-900;
|
|
158
|
+
}
|
|
159
|
+
&.neutral {
|
|
160
|
+
@apply bg-fv-neutral-400 dark:bg-fv-neutral-900;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.input {
|
|
165
|
+
flex-grow: 1;
|
|
166
|
+
min-width: 100px;
|
|
167
|
+
outline: none;
|
|
168
|
+
border: none;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
package/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import DefaultSidebar from "./components/ui/DefaultSidebar.vue";
|
|
|
41
41
|
import DefaultGallery from "./components/ui/DefaultGallery.vue";
|
|
42
42
|
import DefaultDropdown from "./components/ui/DefaultDropdown.vue";
|
|
43
43
|
import DefaultDropdownLink from "./components/ui/DefaultDropdownLink.vue";
|
|
44
|
+
import DefaultTagInput from "./components/ui/DefaultTagInput.vue";
|
|
44
45
|
// Components/FWS
|
|
45
46
|
import UserFlow from "./components/fws/UserFlow.vue";
|
|
46
47
|
import DataTable from "./components/fws/DataTable.vue";
|
|
@@ -125,6 +126,7 @@ export {
|
|
|
125
126
|
DefaultGallery,
|
|
126
127
|
DefaultDropdown,
|
|
127
128
|
DefaultDropdownLink,
|
|
129
|
+
DefaultTagInput,
|
|
128
130
|
|
|
129
131
|
// FWS
|
|
130
132
|
UserFlow,
|