@fy-/fws-vue 0.0.916 → 0.0.918
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/DefaultGallery.vue +348 -0
- package/components/ui/DefaultTagInput.vue +164 -0
- package/index.ts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Dialog, DialogPanel, TransitionRoot } from "@headlessui/vue";
|
|
3
|
+
import { ref, onMounted, reactive, onUnmounted, h, computed } from "vue";
|
|
4
|
+
import type { APIPaging } from "../../composables/rest";
|
|
5
|
+
import { useEventBus } from "../../composables/event-bus";
|
|
6
|
+
import {
|
|
7
|
+
XCircleIcon,
|
|
8
|
+
ChevronDoubleRightIcon,
|
|
9
|
+
ArrowLeftCircleIcon,
|
|
10
|
+
ArrowRightCircleIcon,
|
|
11
|
+
ChevronDoubleLeftIcon,
|
|
12
|
+
} from "@heroicons/vue/24/solid";
|
|
13
|
+
import DefaultPaging from "./DefaultPaging.vue";
|
|
14
|
+
import { Component } from "vue";
|
|
15
|
+
const isOpen = ref<boolean>(false);
|
|
16
|
+
const eventBus = useEventBus();
|
|
17
|
+
const sidePanel = ref<boolean>(true);
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
id: string;
|
|
21
|
+
images: Array<any>;
|
|
22
|
+
title?: string;
|
|
23
|
+
getImageUrl?: Function;
|
|
24
|
+
getThumbnailUrl?: Function;
|
|
25
|
+
onOpen?: Function;
|
|
26
|
+
onClose?: Function;
|
|
27
|
+
closeIcon?: Object;
|
|
28
|
+
gridHeight?: number;
|
|
29
|
+
mode: "mason" | "grid" | "button" | "hidden";
|
|
30
|
+
paging?: APIPaging | undefined;
|
|
31
|
+
buttonText?: string;
|
|
32
|
+
buttonType?: string;
|
|
33
|
+
modelValue: number;
|
|
34
|
+
borderColor?: Function;
|
|
35
|
+
imageLoader: string;
|
|
36
|
+
videoComponent?: Component;
|
|
37
|
+
isVideo?: Function;
|
|
38
|
+
}>(),
|
|
39
|
+
{
|
|
40
|
+
modelValue: 0,
|
|
41
|
+
mode: "grid",
|
|
42
|
+
gridHeight: 4,
|
|
43
|
+
closeIcon: () => h(XCircleIcon),
|
|
44
|
+
images: () => [],
|
|
45
|
+
isVideo: (image: any) => false,
|
|
46
|
+
getImageUrl: (image: any) => image.image_url,
|
|
47
|
+
getThumbnailUrl: (image: any) => `${image.image_url}?s=250x250&m=autocrop`,
|
|
48
|
+
paging: undefined,
|
|
49
|
+
borderColor: undefined,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
53
|
+
const modelValue = computed({
|
|
54
|
+
get: () => props.modelValue,
|
|
55
|
+
set: (i) => {
|
|
56
|
+
emit("update:modelValue", i);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const setModal = (value: boolean) => {
|
|
60
|
+
if (value === true) {
|
|
61
|
+
if (props.onOpen) props.onOpen();
|
|
62
|
+
} else {
|
|
63
|
+
if (props.onClose) props.onClose();
|
|
64
|
+
}
|
|
65
|
+
isOpen.value = value;
|
|
66
|
+
};
|
|
67
|
+
const openGalleryImage = (index: number | undefined) => {
|
|
68
|
+
if (index === undefined) modelValue.value = 0;
|
|
69
|
+
else {
|
|
70
|
+
modelValue.value = index;
|
|
71
|
+
}
|
|
72
|
+
setModal(true);
|
|
73
|
+
};
|
|
74
|
+
const goNextImage = () => {
|
|
75
|
+
if (modelValue.value < props.images.length - 1) {
|
|
76
|
+
modelValue.value++;
|
|
77
|
+
} else {
|
|
78
|
+
modelValue.value = 0;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const goPrevImage = () => {
|
|
82
|
+
if (modelValue.value > 0) {
|
|
83
|
+
modelValue.value--;
|
|
84
|
+
} else {
|
|
85
|
+
modelValue.value = props.images.length - 1;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const modelValueSrc = computed(() => {
|
|
89
|
+
if (props.images.length == 0) return false;
|
|
90
|
+
if (props.images[modelValue.value] == undefined) return false;
|
|
91
|
+
return props.getImageUrl(props.images[modelValue.value]);
|
|
92
|
+
});
|
|
93
|
+
const start = reactive({ x: 0, y: 0 });
|
|
94
|
+
|
|
95
|
+
const touchStart = (event: TouchEvent) => {
|
|
96
|
+
const touch = event.touches[0];
|
|
97
|
+
start.x = touch.screenX;
|
|
98
|
+
start.y = touch.screenY;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const touchEnd = (event: TouchEvent) => {
|
|
102
|
+
const touch = event.changedTouches[0];
|
|
103
|
+
const end = { x: touch.screenX, y: touch.screenY };
|
|
104
|
+
|
|
105
|
+
const diffX = start.x - end.x;
|
|
106
|
+
const diffY = start.y - end.y;
|
|
107
|
+
|
|
108
|
+
if (Math.abs(diffX) > Math.abs(diffY)) {
|
|
109
|
+
if (diffX > 0) {
|
|
110
|
+
goNextImage();
|
|
111
|
+
} else {
|
|
112
|
+
goPrevImage();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const getBorderColor = (i: any) => {
|
|
117
|
+
if (props.borderColor !== undefined) {
|
|
118
|
+
return props.borderColor(i);
|
|
119
|
+
}
|
|
120
|
+
return "";
|
|
121
|
+
};
|
|
122
|
+
const isKeyPressed = ref<boolean>(false);
|
|
123
|
+
const handleKeyboardInput = (event: KeyboardEvent) => {
|
|
124
|
+
if (isKeyPressed.value) return;
|
|
125
|
+
switch (event.key) {
|
|
126
|
+
case "ArrowRight":
|
|
127
|
+
isKeyPressed.value = true;
|
|
128
|
+
goNextImage();
|
|
129
|
+
break;
|
|
130
|
+
case "ArrowLeft":
|
|
131
|
+
isKeyPressed.value = true;
|
|
132
|
+
goPrevImage();
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const handleKeyboardRelease = (event: KeyboardEvent) => {
|
|
139
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
140
|
+
isKeyPressed.value = false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
onMounted(() => {
|
|
144
|
+
eventBus.on(`${props.id}GalleryImage`, openGalleryImage);
|
|
145
|
+
eventBus.on(`${props.id}Gallery`, openGalleryImage);
|
|
146
|
+
if (window !== undefined && !import.meta.env.SSR) {
|
|
147
|
+
window.addEventListener("keydown", handleKeyboardInput);
|
|
148
|
+
window.addEventListener("keyup", handleKeyboardRelease);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
onUnmounted(() => {
|
|
152
|
+
eventBus.off(`${props.id}Gallery`, openGalleryImage);
|
|
153
|
+
eventBus.off(`${props.id}GalleryImage`, openGalleryImage);
|
|
154
|
+
if (window !== undefined && !import.meta.env.SSR) {
|
|
155
|
+
window.removeEventListener("keydown", handleKeyboardInput);
|
|
156
|
+
window.removeEventListener("keyup", handleKeyboardRelease);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
<template>
|
|
161
|
+
<div>
|
|
162
|
+
<TransitionRoot
|
|
163
|
+
:show="isOpen"
|
|
164
|
+
as="template"
|
|
165
|
+
enter="duration-300 ease-out"
|
|
166
|
+
enter-from="opacity-0"
|
|
167
|
+
enter-to="opacity-100"
|
|
168
|
+
leave="duration-200 ease-in"
|
|
169
|
+
leave-from="opacity-100"
|
|
170
|
+
leave-to="opacity-0"
|
|
171
|
+
>
|
|
172
|
+
<Dialog
|
|
173
|
+
:open="isOpen"
|
|
174
|
+
@close="setModal"
|
|
175
|
+
class="fixed bg-fv-neutral-900 text-white inset-0 max-w-[100vw] overflow-y-auto overflow-x-hidden"
|
|
176
|
+
style="z-index: 37"
|
|
177
|
+
>
|
|
178
|
+
<DialogPanel
|
|
179
|
+
class="relative w-full max-w-full flex flex-col justify-center items-center"
|
|
180
|
+
style="z-index: 38"
|
|
181
|
+
>
|
|
182
|
+
<div class="flex flex-grow gap-4 w-full max-w-full">
|
|
183
|
+
<div class="flex-grow h-[100vh] flex items-center relative">
|
|
184
|
+
<button
|
|
185
|
+
class="btn w-9 h-9 rounded-full absolute top-4 left-2"
|
|
186
|
+
@click="setModal(false)"
|
|
187
|
+
style="z-index: 39"
|
|
188
|
+
>
|
|
189
|
+
<component :is="closeIcon" class="w-8 h-8" />
|
|
190
|
+
</button>
|
|
191
|
+
|
|
192
|
+
<div
|
|
193
|
+
class="flex h-[100vh] relative flex-grow items-center justify-center gap-2"
|
|
194
|
+
>
|
|
195
|
+
<div
|
|
196
|
+
class="hidden lg:relative lg:flex w-10 flex-shrink-0 items-center justify-center"
|
|
197
|
+
>
|
|
198
|
+
<button
|
|
199
|
+
class="btn p-1 rounded-full"
|
|
200
|
+
v-if="images.length > 1"
|
|
201
|
+
@click="goPrevImage()"
|
|
202
|
+
>
|
|
203
|
+
<ArrowLeftCircleIcon class="w-8 h-8" />
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
<div
|
|
207
|
+
class="flex-1 flex flex-col items-center justify-center max-w-full lg:max-w-[calc(100vw - 256px)]"
|
|
208
|
+
>
|
|
209
|
+
<div
|
|
210
|
+
class="flex-1 w-full max-w-full flex items-center justify-center"
|
|
211
|
+
>
|
|
212
|
+
<template
|
|
213
|
+
v-if="videoComponent && isVideo(images[modelValue])"
|
|
214
|
+
>
|
|
215
|
+
<ClientOnly>
|
|
216
|
+
<component
|
|
217
|
+
:is="videoComponent"
|
|
218
|
+
:src="isVideo(images[modelValue])"
|
|
219
|
+
class="shadow max-w-full h-auto object-contain max-h-[85vh]"
|
|
220
|
+
@touchstart="touchStart"
|
|
221
|
+
@touchend="touchEnd"
|
|
222
|
+
/>
|
|
223
|
+
</ClientOnly>
|
|
224
|
+
</template>
|
|
225
|
+
<template v-else>
|
|
226
|
+
<img
|
|
227
|
+
class="shadow max-w-full h-auto object-contain max-h-[85vh]"
|
|
228
|
+
:src="modelValueSrc"
|
|
229
|
+
v-if="modelValueSrc"
|
|
230
|
+
@touchstart="touchStart"
|
|
231
|
+
@touchend="touchEnd"
|
|
232
|
+
/>
|
|
233
|
+
</template>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="flex-0 py-2 flex items-center justify-center">
|
|
236
|
+
<slot></slot>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div
|
|
240
|
+
class="hidden lg:flex w-10 flex-shrink-0 items-center justify-center"
|
|
241
|
+
>
|
|
242
|
+
<button
|
|
243
|
+
class="btn w-9 h-9 rounded-full hidden lg:block absolute top-4"
|
|
244
|
+
:class="{
|
|
245
|
+
'-right-4': sidePanel,
|
|
246
|
+
'right-2': !sidePanel,
|
|
247
|
+
}"
|
|
248
|
+
style="z-index: 39"
|
|
249
|
+
@click="() => (sidePanel = !sidePanel)"
|
|
250
|
+
>
|
|
251
|
+
<ChevronDoubleRightIcon class="w-7 h-7" v-if="sidePanel" />
|
|
252
|
+
<ChevronDoubleLeftIcon class="w-7 h-7" v-else />
|
|
253
|
+
</button>
|
|
254
|
+
<button
|
|
255
|
+
class="btn p-1 rounded-full"
|
|
256
|
+
@click="goNextImage()"
|
|
257
|
+
v-if="images.length > 1"
|
|
258
|
+
>
|
|
259
|
+
<ArrowRightCircleIcon class="w-8 h-8" />
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<TransitionRoot
|
|
266
|
+
:show="sidePanel"
|
|
267
|
+
as="div"
|
|
268
|
+
enter="transform transition ease-in-out duration-300"
|
|
269
|
+
enter-from="translate-x-full"
|
|
270
|
+
enter-to="translate-x-0"
|
|
271
|
+
leave="transform transition ease-in-out duration-300"
|
|
272
|
+
leave-from="translate-x-0"
|
|
273
|
+
leave-to="translate-x-full"
|
|
274
|
+
class="hidden lg:block flex-shrink-0 w-64 bg-fv-neutral-800 h-[100vh] max-h-[100vh] overflow-y-auto"
|
|
275
|
+
>
|
|
276
|
+
<div v-if="paging" class="flex items-center justify-center">
|
|
277
|
+
<DefaultPaging :items="paging" :id="id" />
|
|
278
|
+
</div>
|
|
279
|
+
<div class="grid grid-cols-2 gap-2 p-2">
|
|
280
|
+
<div
|
|
281
|
+
v-for="i in images.length"
|
|
282
|
+
:key="`bg_${id}_${i}`"
|
|
283
|
+
class="hover:!brightness-100"
|
|
284
|
+
:style="`${
|
|
285
|
+
i - 1 == modelValue
|
|
286
|
+
? 'filter: brightness(1)'
|
|
287
|
+
: 'filter: brightness(0.5)'
|
|
288
|
+
}`"
|
|
289
|
+
>
|
|
290
|
+
<img
|
|
291
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
|
|
292
|
+
:class="`h-auto max-w-full rounded-lg cursor-pointer shadow ${getBorderColor(
|
|
293
|
+
images[i - 1],
|
|
294
|
+
)}`"
|
|
295
|
+
:src="getThumbnailUrl(images[i - 1])"
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</TransitionRoot>
|
|
300
|
+
</div>
|
|
301
|
+
</DialogPanel>
|
|
302
|
+
</Dialog>
|
|
303
|
+
</TransitionRoot>
|
|
304
|
+
<template v-if="mode == 'grid' || mode == 'mason'">
|
|
305
|
+
<div
|
|
306
|
+
class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-4"
|
|
307
|
+
:class="{
|
|
308
|
+
'items-start': mode == 'mason',
|
|
309
|
+
'items-center': mode == 'grid',
|
|
310
|
+
}"
|
|
311
|
+
>
|
|
312
|
+
<template v-for="i in images.length" :key="`g_${id}_${i}`">
|
|
313
|
+
<template v-if="mode == 'mason'">
|
|
314
|
+
<div
|
|
315
|
+
class="grid gap-4 items-start"
|
|
316
|
+
v-if="i + (1 % gridHeight) == 0"
|
|
317
|
+
>
|
|
318
|
+
<template v-for="j in gridHeight" :key="`gi_${id}_${i + j}`">
|
|
319
|
+
<div>
|
|
320
|
+
<img
|
|
321
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
|
|
322
|
+
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
323
|
+
v-if="i + j - 2 < images.length"
|
|
324
|
+
:src="getThumbnailUrl(images[i + j - 2])"
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
</template>
|
|
328
|
+
</div>
|
|
329
|
+
</template>
|
|
330
|
+
<div v-else>
|
|
331
|
+
<img
|
|
332
|
+
@click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
|
|
333
|
+
class="h-auto max-w-full rounded-lg cursor-pointer"
|
|
334
|
+
:src="getThumbnailUrl(images[i - 1])"
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
</template>
|
|
338
|
+
</div>
|
|
339
|
+
</template>
|
|
340
|
+
<button
|
|
341
|
+
v-if="mode == 'button'"
|
|
342
|
+
:class="`btn ${buttonType ? buttonType : 'primary'} defaults`"
|
|
343
|
+
@click="openGalleryImage(0)"
|
|
344
|
+
>
|
|
345
|
+
{{ buttonText ? buttonText : $t("open_gallery_cta") }}
|
|
346
|
+
</button>
|
|
347
|
+
</div>
|
|
348
|
+
</template>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<label class="tag-label" :for="`tags_${id}`">{{ label }}</label>
|
|
4
|
+
<div
|
|
5
|
+
class="tags-input"
|
|
6
|
+
@click="focusInput"
|
|
7
|
+
@keydown.enter.prevent="addTag"
|
|
8
|
+
@keydown.delete="removeLastTag"
|
|
9
|
+
>
|
|
10
|
+
<span v-for="(tag, index) in tags" :key="index" :class="`tag ${color}`">
|
|
11
|
+
{{ tag }}
|
|
12
|
+
<button @click.stop="removeTag(index)">
|
|
13
|
+
<svg
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
fill="none"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
stroke-width="1.5"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
class="w-3 h-3 text-red-600"
|
|
20
|
+
>
|
|
21
|
+
<path
|
|
22
|
+
stroke-linecap="round"
|
|
23
|
+
stroke-linejoin="round"
|
|
24
|
+
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
</button>
|
|
28
|
+
</span>
|
|
29
|
+
<div
|
|
30
|
+
contenteditable
|
|
31
|
+
class="input"
|
|
32
|
+
:id="`tags_${id}`"
|
|
33
|
+
ref="textInput"
|
|
34
|
+
@input="updateInput"
|
|
35
|
+
@paste.prevent="handlePaste"
|
|
36
|
+
placeholder="Add a tag..."
|
|
37
|
+
></div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup>
|
|
43
|
+
import { ref, watch, onMounted } from "vue";
|
|
44
|
+
|
|
45
|
+
const props = defineProps({
|
|
46
|
+
modelValue: {
|
|
47
|
+
type: Array,
|
|
48
|
+
default: () => [],
|
|
49
|
+
},
|
|
50
|
+
color: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: "blue",
|
|
53
|
+
},
|
|
54
|
+
label: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: "Tags",
|
|
57
|
+
},
|
|
58
|
+
id: {
|
|
59
|
+
type: String,
|
|
60
|
+
required: true,
|
|
61
|
+
},
|
|
62
|
+
autofocus: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
69
|
+
const tags = ref([...props.modelValue]);
|
|
70
|
+
const textInput = ref(null);
|
|
71
|
+
|
|
72
|
+
watch(
|
|
73
|
+
tags,
|
|
74
|
+
(newTags) => {
|
|
75
|
+
emit("update:modelValue", newTags);
|
|
76
|
+
},
|
|
77
|
+
{ deep: true },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
if (props.autofocus) {
|
|
82
|
+
focusInput();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const updateInput = (event) => {
|
|
87
|
+
const text = event.target.innerText;
|
|
88
|
+
if (text.includes(",")) {
|
|
89
|
+
addTag();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const addTag = () => {
|
|
94
|
+
const newTags = textInput.value.innerText
|
|
95
|
+
.split(",")
|
|
96
|
+
.map((tag) => tag.trim())
|
|
97
|
+
.filter((tag) => tag.length > 0);
|
|
98
|
+
tags.value.push(...newTags);
|
|
99
|
+
textInput.value.innerText = "";
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const removeTag = (index) => {
|
|
103
|
+
tags.value.splice(index, 1);
|
|
104
|
+
focusInput();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const removeLastTag = () => {
|
|
108
|
+
if (textInput.value.innerText === "") {
|
|
109
|
+
tags.value.pop();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const focusInput = () => {
|
|
114
|
+
textInput.value.focus();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handlePaste = (e) => {
|
|
118
|
+
const text = (e.clipboardData || window.clipboardData).getData("text");
|
|
119
|
+
textInput.value.innerText += text;
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
};
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<style scoped>
|
|
125
|
+
.tags-input {
|
|
126
|
+
cursor: text;
|
|
127
|
+
@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;
|
|
128
|
+
}
|
|
129
|
+
.tag-label {
|
|
130
|
+
@apply block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white;
|
|
131
|
+
|
|
132
|
+
&.error {
|
|
133
|
+
@apply text-red-700 dark:text-red-500;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
.tag {
|
|
137
|
+
@apply inline-flex gap-1 font-medium px-2.5 py-0.5 rounded;
|
|
138
|
+
&.blue {
|
|
139
|
+
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300;
|
|
140
|
+
}
|
|
141
|
+
&.purple {
|
|
142
|
+
@apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300;
|
|
143
|
+
}
|
|
144
|
+
&.red {
|
|
145
|
+
@apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300;
|
|
146
|
+
}
|
|
147
|
+
&.orange {
|
|
148
|
+
@apply bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300;
|
|
149
|
+
}
|
|
150
|
+
&.neutral {
|
|
151
|
+
@apply bg-fv-neutral-100 text-fv-neutral-800 dark:bg-fv-neutral-900 dark:text-fv-neutral-300;
|
|
152
|
+
}
|
|
153
|
+
&.green {
|
|
154
|
+
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.input {
|
|
159
|
+
flex-grow: 1;
|
|
160
|
+
min-width: 100px;
|
|
161
|
+
outline: none;
|
|
162
|
+
border: none;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
package/index.ts
CHANGED
|
@@ -38,6 +38,8 @@ import DefaultPaging from "./components/ui/DefaultPaging.vue";
|
|
|
38
38
|
import DefaultBreadcrumb from "./components/ui/DefaultBreadcrumb.vue";
|
|
39
39
|
import DefaultLoader from "./components/ui/DefaultLoader.vue";
|
|
40
40
|
import DefaultSidebar from "./components/ui/DefaultSidebar.vue";
|
|
41
|
+
import DefaultTagInput from "./components/ui/DefaultTagInput.vue";
|
|
42
|
+
import DefaultGallery from "./components/ui/DefaultGallery.vue";
|
|
41
43
|
|
|
42
44
|
// Components/FWS
|
|
43
45
|
import UserFlow from "./components/fws/UserFlow.vue";
|
|
@@ -119,6 +121,8 @@ export {
|
|
|
119
121
|
DefaultBreadcrumb,
|
|
120
122
|
DefaultLoader,
|
|
121
123
|
DefaultSidebar,
|
|
124
|
+
DefaultTagInput,
|
|
125
|
+
DefaultGallery,
|
|
122
126
|
|
|
123
127
|
// FWS
|
|
124
128
|
UserFlow,
|