@fy-/fws-vue 0.3.2 → 0.3.5
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.
|
@@ -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,42 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
136
143
|
:class="{
|
|
137
144
|
error: checkErrors,
|
|
138
145
|
}"
|
|
146
|
+
v-model="model"
|
|
139
147
|
:autocomplete="autocomplete"
|
|
148
|
+
:placeholder="placeholder"
|
|
140
149
|
:disabled="disabled"
|
|
141
150
|
:aria-describedby="help ? `${id}-help` : id"
|
|
142
|
-
class="
|
|
151
|
+
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
152
|
:required="req"
|
|
144
153
|
@focus="handleFocus"
|
|
145
154
|
@blur="handleBlur"
|
|
146
155
|
/>
|
|
156
|
+
</div>
|
|
157
|
+
<div v-if="type == 'chips' || type == 'tags'">
|
|
147
158
|
<label
|
|
148
159
|
:for="id"
|
|
149
160
|
v-if="label || placeholder"
|
|
150
|
-
class="
|
|
161
|
+
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
151
162
|
>{{ label ? label : placeholder }}
|
|
152
163
|
</label>
|
|
164
|
+
<!-- @vue-skip -->
|
|
165
|
+
<DefaultTagInput
|
|
166
|
+
v-model="model"
|
|
167
|
+
:id="id"
|
|
168
|
+
:disabled="disabled"
|
|
169
|
+
:color="color"
|
|
170
|
+
:error="checkErrors"
|
|
171
|
+
:help="help"
|
|
172
|
+
/>
|
|
153
173
|
</div>
|
|
154
174
|
<div class="group relative" v-else-if="type == 'textarea'">
|
|
155
175
|
<label
|
|
176
|
+
v-if="label"
|
|
156
177
|
:for="id"
|
|
157
178
|
class="block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white"
|
|
158
|
-
>
|
|
179
|
+
>{{ label }}</label
|
|
159
180
|
>
|
|
181
|
+
<!-- @vue-skip -->
|
|
160
182
|
<textarea
|
|
161
183
|
:id="id"
|
|
162
184
|
:name="id"
|
|
@@ -164,6 +186,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
164
186
|
:class="{
|
|
165
187
|
error: checkErrors,
|
|
166
188
|
}"
|
|
189
|
+
v-model="model"
|
|
167
190
|
:placeholder="placeholder"
|
|
168
191
|
:disabled="disabled"
|
|
169
192
|
:aria-describedby="help ? `${id}-help` : id"
|
|
@@ -184,6 +207,7 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
184
207
|
:id="id"
|
|
185
208
|
:name="id"
|
|
186
209
|
ref="inputRef"
|
|
210
|
+
v-model="model"
|
|
187
211
|
:disabled="disabled"
|
|
188
212
|
:aria-describedby="help ? `${id}-help` : id"
|
|
189
213
|
:required="req"
|
|
@@ -215,12 +239,14 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
215
239
|
<input
|
|
216
240
|
type="checkbox"
|
|
217
241
|
v-model="modelCheckbox"
|
|
242
|
+
:true-value="checkboxTrueValue"
|
|
243
|
+
:false-value="checkboxFalseValue"
|
|
218
244
|
class="sr-only peer"
|
|
219
245
|
@focus="handleFocus"
|
|
220
246
|
@blur="handleBlur"
|
|
221
247
|
/>
|
|
222
248
|
<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"
|
|
249
|
+
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
250
|
></div>
|
|
225
251
|
<span
|
|
226
252
|
class="ms-3 text-sm font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
@@ -246,6 +272,8 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
246
272
|
:type="type"
|
|
247
273
|
@focus="handleFocus"
|
|
248
274
|
@blur="handleBlur"
|
|
275
|
+
:true-value="checkboxTrueValue"
|
|
276
|
+
:false-value="checkboxFalseValue"
|
|
249
277
|
v-model="modelCheckbox"
|
|
250
278
|
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
279
|
/>
|
|
@@ -253,13 +281,13 @@ defineExpose({ focus, blur, getInputRef });
|
|
|
253
281
|
<div class="ms-2 text-sm">
|
|
254
282
|
<label
|
|
255
283
|
:for="id"
|
|
256
|
-
class="font-medium text-
|
|
284
|
+
class="font-medium text-fv-neutral-900 dark:text-fv-neutral-300"
|
|
257
285
|
>{{ label }}</label
|
|
258
286
|
>
|
|
259
287
|
<p
|
|
260
288
|
:id="`${id}-help`"
|
|
261
289
|
v-if="help"
|
|
262
|
-
class="text-xs font-normal text-
|
|
290
|
+
class="text-xs font-normal text-fv-neutral-500 dark:text-fv-neutral-400"
|
|
263
291
|
>
|
|
264
292
|
{{ help }}
|
|
265
293
|
</p>
|
|
@@ -71,12 +71,11 @@ onUnmounted(() => {
|
|
|
71
71
|
v-if="title"
|
|
72
72
|
>
|
|
73
73
|
<slot name="before"></slot>
|
|
74
|
-
<
|
|
74
|
+
<h2
|
|
75
75
|
class="text-xl font-semibold text-fv-neutral-900 dark:text-white"
|
|
76
76
|
v-if="title"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</DialogTitle>
|
|
77
|
+
v-html="title"
|
|
78
|
+
/>
|
|
80
79
|
<button
|
|
81
80
|
@click="setModal(false)"
|
|
82
81
|
class="text-fv-neutral-400 bg-transparent hover:bg-fv-neutral-200 hover:text-fv-neutral-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center dark:hover:bg-fv-neutral-600 dark:hover:text-white"
|
|
@@ -0,0 +1,177 @@
|
|
|
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 model" :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, computed, 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 textInput = ref<HTMLElement>();
|
|
63
|
+
|
|
64
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
65
|
+
const model = computed({
|
|
66
|
+
get: () => props.modelValue,
|
|
67
|
+
set: (items) => {
|
|
68
|
+
emit("update:modelValue", items);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
if (props.autofocus) {
|
|
74
|
+
focusInput();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const handleInput = (event: any) => {
|
|
79
|
+
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
80
|
+
if (separatorsRegex.test(event.data)) {
|
|
81
|
+
addTag();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const addTag = () => {
|
|
86
|
+
if (!textInput.value) return;
|
|
87
|
+
|
|
88
|
+
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
89
|
+
const newTags = textInput.value.innerText
|
|
90
|
+
.split(separatorsRegex)
|
|
91
|
+
.map((tag: string) => tag.trim())
|
|
92
|
+
.filter((tag: string) => tag.length > 0);
|
|
93
|
+
model.value.push(...newTags);
|
|
94
|
+
textInput.value.innerText = "";
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const removeTag = (index: number) => {
|
|
98
|
+
model.value.splice(index, 1);
|
|
99
|
+
focusInput();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const removeLastTag = () => {
|
|
103
|
+
if (!textInput.value) return;
|
|
104
|
+
if (textInput.value.innerText === "") {
|
|
105
|
+
model.value.pop();
|
|
106
|
+
} else {
|
|
107
|
+
const currentLength = textInput.value.innerText.length;
|
|
108
|
+
textInput.value.innerText = textInput.value.innerText.slice(0, -1);
|
|
109
|
+
|
|
110
|
+
const range = document.createRange();
|
|
111
|
+
const sel = window.getSelection();
|
|
112
|
+
range.selectNodeContents(textInput.value);
|
|
113
|
+
range.collapse(false);
|
|
114
|
+
if (!sel) return;
|
|
115
|
+
sel.removeAllRanges();
|
|
116
|
+
sel.addRange(range);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const focusInput = () => {
|
|
120
|
+
if (!textInput.value) return;
|
|
121
|
+
|
|
122
|
+
textInput.value.focus();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const handlePaste = (e: any) => {
|
|
126
|
+
if (!textInput.value) return;
|
|
127
|
+
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
const text = (e.clipboardData || window.clipboardData).getData("text");
|
|
130
|
+
const separatorsRegex = new RegExp(props.separators.join("|"), "g");
|
|
131
|
+
const pasteText = text.replace(separatorsRegex, ",");
|
|
132
|
+
textInput.value.innerText += pasteText;
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
addTag();
|
|
135
|
+
};
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style scoped>
|
|
139
|
+
.tags-input {
|
|
140
|
+
cursor: text;
|
|
141
|
+
@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;
|
|
142
|
+
&.error {
|
|
143
|
+
@apply border-red-500 dark:border-red-400 border !important;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
.tag-label {
|
|
147
|
+
@apply block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white;
|
|
148
|
+
}
|
|
149
|
+
.tag {
|
|
150
|
+
@apply inline-flex gap-1 font-medium px-2.5 py-0.5 rounded text-black dark:text-white;
|
|
151
|
+
&.blue {
|
|
152
|
+
@apply bg-blue-400 dark:bg-blue-900;
|
|
153
|
+
}
|
|
154
|
+
&.red {
|
|
155
|
+
@apply bg-red-400 dark:bg-red-900;
|
|
156
|
+
}
|
|
157
|
+
&.green {
|
|
158
|
+
@apply bg-green-400 dark:bg-green-900;
|
|
159
|
+
}
|
|
160
|
+
&.purple {
|
|
161
|
+
@apply bg-purple-400 dark:bg-purple-900;
|
|
162
|
+
}
|
|
163
|
+
&.orange {
|
|
164
|
+
@apply bg-orange-400 dark:bg-orange-900;
|
|
165
|
+
}
|
|
166
|
+
&.neutral {
|
|
167
|
+
@apply bg-fv-neutral-400 dark:bg-fv-neutral-900;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.input {
|
|
172
|
+
flex-grow: 1;
|
|
173
|
+
min-width: 100px;
|
|
174
|
+
outline: none;
|
|
175
|
+
border: none;
|
|
176
|
+
}
|
|
177
|
+
</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,
|