@fy-/fws-vue 0.3.1 → 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 +37 -52
- 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
|
"
|
|
@@ -122,10 +123,18 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
122
123
|
'search',
|
|
123
124
|
'url',
|
|
124
125
|
'mask',
|
|
126
|
+
'date',
|
|
127
|
+
'datetime',
|
|
125
128
|
].includes(type)
|
|
126
129
|
"
|
|
127
130
|
class="relative"
|
|
128
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>
|
|
129
138
|
<input
|
|
130
139
|
ref="inputRef"
|
|
131
140
|
:type="type"
|
|
@@ -134,27 +143,41 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
134
143
|
:class="{
|
|
135
144
|
error: checkErrors,
|
|
136
145
|
}"
|
|
146
|
+
v-model="model"
|
|
137
147
|
:autocomplete="autocomplete"
|
|
138
148
|
:disabled="disabled"
|
|
139
149
|
:aria-describedby="help ? `${id}-help` : id"
|
|
140
|
-
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"
|
|
141
151
|
:required="req"
|
|
142
152
|
@focus="handleFocus"
|
|
143
153
|
@blur="handleBlur"
|
|
144
154
|
/>
|
|
155
|
+
</div>
|
|
156
|
+
<div v-if="type == 'chips' || type == 'tags'">
|
|
145
157
|
<label
|
|
146
158
|
:for="id"
|
|
147
159
|
v-if="label || placeholder"
|
|
148
|
-
class="
|
|
160
|
+
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
149
161
|
>{{ label ? label : placeholder }}
|
|
150
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
|
+
/>
|
|
151
172
|
</div>
|
|
152
173
|
<div class="group relative" v-else-if="type == 'textarea'">
|
|
153
174
|
<label
|
|
175
|
+
v-if="label"
|
|
154
176
|
:for="id"
|
|
155
177
|
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
156
|
-
>
|
|
178
|
+
>{{ label }}</label
|
|
157
179
|
>
|
|
180
|
+
<!-- @vue-skip -->
|
|
158
181
|
<textarea
|
|
159
182
|
:id="id"
|
|
160
183
|
:name="id"
|
|
@@ -162,6 +185,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
162
185
|
:class="{
|
|
163
186
|
error: checkErrors,
|
|
164
187
|
}"
|
|
188
|
+
v-model="model"
|
|
165
189
|
:placeholder="placeholder"
|
|
166
190
|
:disabled="disabled"
|
|
167
191
|
:aria-describedby="help ? `${id}-help` : id"
|
|
@@ -171,50 +195,6 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
171
195
|
class="block p-2.5 w-full text-sm text-fv-neutral-900 bg-fv-neutral-50 rounded-lg border border-fv-neutral-300 focus:ring-fv-primary-500 focus:border-fv-primary-500 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"
|
|
172
196
|
></textarea>
|
|
173
197
|
</div>
|
|
174
|
-
<div
|
|
175
|
-
class="relative max-w-sm"
|
|
176
|
-
v-else-if="type == 'datetime' || type == 'date'"
|
|
177
|
-
>
|
|
178
|
-
<div
|
|
179
|
-
class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none"
|
|
180
|
-
>
|
|
181
|
-
<svg
|
|
182
|
-
class="w-4 h-4 text-fv-neutral-500 dark:text-fv-neutral-400"
|
|
183
|
-
aria-hidden="true"
|
|
184
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
185
|
-
fill="currentColor"
|
|
186
|
-
viewBox="0 0 20 20"
|
|
187
|
-
>
|
|
188
|
-
<path
|
|
189
|
-
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
|
|
190
|
-
/>
|
|
191
|
-
</svg>
|
|
192
|
-
</div>
|
|
193
|
-
<label
|
|
194
|
-
:for="id"
|
|
195
|
-
v-if="label"
|
|
196
|
-
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
197
|
-
>{{ label }}</label
|
|
198
|
-
>
|
|
199
|
-
|
|
200
|
-
<input
|
|
201
|
-
datepicker
|
|
202
|
-
ref="inputRef"
|
|
203
|
-
type="text"
|
|
204
|
-
:class="{
|
|
205
|
-
error: checkErrors,
|
|
206
|
-
}"
|
|
207
|
-
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 ps-10 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"
|
|
208
|
-
:id="id"
|
|
209
|
-
:name="id"
|
|
210
|
-
:disabled="disabled"
|
|
211
|
-
:aria-describedby="help ? `${id}-help` : id"
|
|
212
|
-
:required="req"
|
|
213
|
-
@focus="handleFocus"
|
|
214
|
-
@blur="handleBlur"
|
|
215
|
-
:placeholder="placeholder"
|
|
216
|
-
/>
|
|
217
|
-
</div>
|
|
218
198
|
<div class="relative" v-else-if="type == 'select'">
|
|
219
199
|
<label
|
|
220
200
|
:for="id"
|
|
@@ -226,6 +206,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
226
206
|
:id="id"
|
|
227
207
|
:name="id"
|
|
228
208
|
ref="inputRef"
|
|
209
|
+
v-model="model"
|
|
229
210
|
:disabled="disabled"
|
|
230
211
|
:aria-describedby="help ? `${id}-help` : id"
|
|
231
212
|
:required="req"
|
|
@@ -257,12 +238,14 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
257
238
|
<input
|
|
258
239
|
type="checkbox"
|
|
259
240
|
v-model="modelCheckbox"
|
|
241
|
+
:true-value="checkboxTrueValue"
|
|
242
|
+
:false-value="checkboxFalseValue"
|
|
260
243
|
class="sr-only peer"
|
|
261
244
|
@focus="handleFocus"
|
|
262
245
|
@blur="handleBlur"
|
|
263
246
|
/>
|
|
264
247
|
<div
|
|
265
|
-
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"
|
|
266
249
|
></div>
|
|
267
250
|
<span
|
|
268
251
|
class="ms-3 text-sm font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
@@ -288,6 +271,8 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
288
271
|
:type="type"
|
|
289
272
|
@focus="handleFocus"
|
|
290
273
|
@blur="handleBlur"
|
|
274
|
+
:true-value="checkboxTrueValue"
|
|
275
|
+
:false-value="checkboxFalseValue"
|
|
291
276
|
v-model="modelCheckbox"
|
|
292
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"
|
|
293
278
|
/>
|
|
@@ -295,13 +280,13 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
295
280
|
<div class="ms-2 text-sm">
|
|
296
281
|
<label
|
|
297
282
|
:for="id"
|
|
298
|
-
class="font-medium text-
|
|
283
|
+
class="font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
299
284
|
>{{ label }}</label
|
|
300
285
|
>
|
|
301
286
|
<p
|
|
302
287
|
:id="`${id}-help`"
|
|
303
288
|
v-if="help"
|
|
304
|
-
class="text-xs font-normal text-
|
|
289
|
+
class="text-xs font-normal text-fv-neutral-500 dark:text-fv-neutral-400"
|
|
305
290
|
>
|
|
306
291
|
{{ help }}
|
|
307
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,
|