@veritree/ui 0.27.0 → 0.28.0-1
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/.claude/settings.local.json +10 -0
- package/index.js +105 -75
- package/mixins/floating-ui-content.js +17 -4
- package/mixins/floating-ui-item.js +31 -15
- package/mixins/floating-ui.js +142 -24
- package/mixins/form-control-icon.js +3 -3
- package/mixins/form-control.js +45 -20
- package/nuxt.js +38 -26
- package/package.json +17 -6
- package/src/components/Alert/VTAlert.vue +55 -14
- package/src/components/Avatar/VTAvatarImage.vue +6 -26
- package/src/components/Badge/VTBadge.vue +60 -0
- package/src/components/Badge/VTBadgeNew.vue +60 -0
- package/src/components/Breadcrumb/VTBreadcrumbItem.vue +11 -0
- package/src/components/Breadcrumb/VTBreadcrumbLink.vue +40 -0
- package/src/components/Breadcrumb/VTBreadcrumbList.vue +11 -0
- package/src/components/Breadcrumb/VTBreadcrumbRoot.vue +11 -0
- package/src/components/Breadcrumb/VTBreadcrumbSeparator.vue +19 -0
- package/src/components/Button/VTButton.vue +104 -56
- package/src/components/Carousel/VTCarousel.vue +69 -0
- package/src/components/Carousel/VTCarouselBackward.vue +36 -0
- package/src/components/Carousel/VTCarouselForward.vue +38 -0
- package/src/components/Carousel/VTCarouselTracker.vue +80 -0
- package/src/components/Checkbox/VTCheckbox.vue +134 -0
- package/src/components/Checkbox/VTCheckboxLabel.vue +3 -0
- package/src/components/Checkbox/VTCheckboxText.vue +20 -0
- package/src/components/Chip/VTChip.vue +29 -0
- package/src/components/Dialog/VTDialog.vue +59 -25
- package/src/components/Dialog/VTDialogClose.vue +3 -2
- package/src/components/Dialog/VTDialogContent.vue +29 -7
- package/src/components/Dialog/VTDialogFooter.vue +17 -2
- package/src/components/Dialog/VTDialogHeader.vue +2 -1
- package/src/components/Dialog/VTDialogMain.vue +5 -1
- package/src/components/Dialog/VTDialogOverlay.vue +5 -1
- package/src/components/Dialog/VTDialogTitle.vue +1 -1
- package/src/components/Disclosure/VTDisclosure.vue +2 -11
- package/src/components/Disclosure/VTDisclosureContent.vue +26 -52
- package/src/components/Disclosure/VTDisclosureDetails.vue +27 -2
- package/src/components/Disclosure/VTDisclosureHeader.vue +56 -89
- package/src/components/Disclosure/VTDisclosureIcon.vue +42 -31
- package/src/components/Divider/VTDivider.vue +9 -0
- package/src/components/Drawer/VTDrawer.vue +6 -15
- package/src/components/Drawer/VTDrawerClose.vue +5 -5
- package/src/components/Drawer/VTDrawerContent.vue +10 -10
- package/src/components/Drawer/VTDrawerFooter.vue +4 -4
- package/src/components/Drawer/VTDrawerHeader.vue +4 -4
- package/src/components/Drawer/VTDrawerMain.vue +5 -5
- package/src/components/Drawer/VTDrawerOverlay.vue +6 -6
- package/src/components/Drawer/VTDrawerTitle.vue +5 -5
- package/src/components/DropdownMenu/VTDropdownMenu.vue +0 -6
- package/src/components/DropdownMenu/VTDropdownMenuContent.vue +10 -1
- package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +7 -16
- package/src/components/DropdownMenu/VTDropdownMenuItem.vue +5 -1
- package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +1 -10
- package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +2 -4
- package/src/components/Form/VTFieldset.vue +5 -0
- package/src/components/Form/VTForm.vue +11 -0
- package/src/components/Form/VTFormCol.vue +20 -0
- package/src/components/Form/VTFormFeedback.vue +7 -1
- package/src/components/Form/VTFormGroup.vue +5 -7
- package/src/components/Form/VTFormLabel.vue +22 -0
- package/src/components/Form/VTFormLabelHelper.vue +22 -0
- package/src/components/Form/VTFormRow.vue +5 -0
- package/src/components/Form/VTInput.vue +2 -5
- package/src/components/Form/VTInputDate.vue +602 -0
- package/src/components/Form/VTInputIcon.vue +3 -9
- package/src/components/Form/VTInputNumber.vue +198 -0
- package/src/components/Form/VTInputPassword.vue +14 -5
- package/src/components/Form/VTInputRange.vue +92 -0
- package/src/components/Form/VTLegend.vue +24 -0
- package/src/components/Form/VTTextarea.vue +2 -2
- package/src/components/Image/VTImage.vue +10 -10
- package/src/components/Listbox/VTListbox.vue +128 -9
- package/src/components/Listbox/VTListboxContent.vue +14 -1
- package/src/components/Listbox/VTListboxDivider.vue +21 -0
- package/src/components/Listbox/VTListboxGroup.vue +9 -0
- package/src/components/Listbox/VTListboxItem.vue +57 -15
- package/src/components/Listbox/VTListboxLabel.vue +5 -4
- package/src/components/Listbox/VTListboxList.vue +1 -6
- package/src/components/Listbox/VTListboxPlaceholder.vue +25 -0
- package/src/components/Listbox/VTListboxSearch.vue +12 -8
- package/src/components/Listbox/VTListboxTrigger.vue +87 -6
- package/src/components/Listbox/VTListboxTriggerHighlight.vue +204 -0
- package/src/components/Listbox/VTListboxViewport.vue +33 -0
- package/src/components/Popover/VTPopoverContent.vue +3 -3
- package/src/components/Popover/VTPopoverDivider.vue +1 -1
- package/src/components/Popover/VTPopoverItem.vue +6 -2
- package/src/components/ProgressBar/VTProgressBar.vue +35 -10
- package/src/components/ProgressBar/VTProgressBarIndicator.vue +53 -0
- package/src/components/ScrollShadows/VTScrollShadows.vue +76 -0
- package/src/components/Separator/VTSeparator.vue +13 -0
- package/src/components/Switch/VTSwitch.vue +61 -0
- package/src/components/Tabs/VTTab.vue +6 -5
- package/src/components/Tabs/VTTabGroup.vue +88 -9
- package/src/components/Tabs/VTTabPanel.vue +4 -5
- package/src/components/Toast/README.md +263 -0
- package/src/components/Toast/VTToast.vue +145 -0
- package/src/components/Toast/VTToastAction.vue +25 -0
- package/src/components/Toast/VTToastClose.vue +52 -0
- package/src/components/Toast/VTToastContent.vue +25 -0
- package/src/components/Toast/VTToastDescription.vue +36 -0
- package/src/components/Toast/VTToastIcon.vue +72 -0
- package/src/components/Toast/VTToastItem.vue +180 -0
- package/src/components/Toast/VTToastTitle.vue +34 -0
- package/src/components/Tooltip/VTTooltipTrigger.vue +3 -5
- package/src/components/Transitions/FadeInOut.vue +2 -2
- package/src/components/Utils/FloatingUi.vue +31 -13
- package/src/helpers/currency.js +21 -0
- package/src/utils/components.js +18 -0
- package/src/utils/images.js +31 -12
- package/src/components/Input/VTInput.vue +0 -82
- package/src/components/Input/VTInputDate.vue +0 -36
- package/src/components/Input/VTInputFile.vue +0 -60
- package/src/components/Input/VTInputUpload.vue +0 -54
- package/src/components/Modal/VTModal.vue +0 -69
- package/src/utils/genId.js +0 -13
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<transition
|
|
3
|
+
appear
|
|
4
|
+
:appear-class="enterClass"
|
|
5
|
+
appear-active-class="duration-300 ease-out"
|
|
6
|
+
appear-to-class="translate-y-0 opacity-100"
|
|
7
|
+
enter-active-class="duration-300 ease-out"
|
|
8
|
+
:enter-class="enterClass"
|
|
9
|
+
enter-to-class="translate-y-0 opacity-100"
|
|
10
|
+
leave-active-class="duration-300 ease-in"
|
|
11
|
+
:leave-to-class="leaveClass"
|
|
12
|
+
@after-leave="onAfterLeave"
|
|
13
|
+
>
|
|
14
|
+
<component
|
|
15
|
+
:is="as"
|
|
16
|
+
v-if="visible"
|
|
17
|
+
:id="id"
|
|
18
|
+
:class="[
|
|
19
|
+
headless
|
|
20
|
+
? 'toast-item'
|
|
21
|
+
: 'grid grid-cols-[auto_1fr_auto] items-center gap-3 rounded border border-solid pt-2 pb-2.5 px-3 shadow-lg bg-gray-800 ',
|
|
22
|
+
headless ? `toast-item--${variant}` : null,
|
|
23
|
+
]"
|
|
24
|
+
role="alert"
|
|
25
|
+
aria-live="polite"
|
|
26
|
+
@mouseenter="pauseTimer"
|
|
27
|
+
@mouseleave="resumeTimer"
|
|
28
|
+
>
|
|
29
|
+
<slot></slot>
|
|
30
|
+
</component>
|
|
31
|
+
</transition>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script>
|
|
35
|
+
import { genId } from '../../utils/ids';
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
name: 'VTToastItem',
|
|
39
|
+
|
|
40
|
+
inject: ['apiToast'],
|
|
41
|
+
|
|
42
|
+
provide() {
|
|
43
|
+
return {
|
|
44
|
+
apiToastItem: () => {
|
|
45
|
+
return {
|
|
46
|
+
componentId: this.componentId,
|
|
47
|
+
variant: this.variant,
|
|
48
|
+
close: this.dismiss,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
props: {
|
|
55
|
+
headless: {
|
|
56
|
+
type: Boolean,
|
|
57
|
+
default: false,
|
|
58
|
+
},
|
|
59
|
+
variant: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: 'default',
|
|
62
|
+
validator: (value) => {
|
|
63
|
+
return ['default', 'success', 'error', 'warning'].includes(value);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
duration: {
|
|
67
|
+
type: Number,
|
|
68
|
+
default: 5000,
|
|
69
|
+
},
|
|
70
|
+
as: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: 'div',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
data() {
|
|
77
|
+
return {
|
|
78
|
+
componentId: genId(),
|
|
79
|
+
visible: true,
|
|
80
|
+
timer: null,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
computed: {
|
|
85
|
+
id() {
|
|
86
|
+
return `toast-item-${this.componentId}`;
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
isSuccess() {
|
|
90
|
+
return this.variant === 'success';
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
isError() {
|
|
94
|
+
return this.variant === 'error';
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
isWarning() {
|
|
98
|
+
return this.variant === 'warning';
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
position() {
|
|
102
|
+
return this.apiToast().position;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
enterClass() {
|
|
106
|
+
// Slide from top/bottom edge based on position
|
|
107
|
+
if (this.position.includes('top')) {
|
|
108
|
+
return '-translate-y-full opacity-0';
|
|
109
|
+
} else if (this.position.includes('bottom')) {
|
|
110
|
+
return 'translate-y-full opacity-0';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return 'opacity-0';
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
leaveClass() {
|
|
117
|
+
// Slide to top/bottom edge based on position
|
|
118
|
+
if (this.position.includes('top')) {
|
|
119
|
+
return '-translate-y-full opacity-0';
|
|
120
|
+
} else if (this.position.includes('bottom')) {
|
|
121
|
+
return 'translate-y-full opacity-0';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return 'opacity-0';
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
mounted() {
|
|
129
|
+
this.apiToast().registerItem(this);
|
|
130
|
+
|
|
131
|
+
if (this.duration > 0) {
|
|
132
|
+
this.startTimer();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
beforeDestroy() {
|
|
137
|
+
this.clearTimer();
|
|
138
|
+
|
|
139
|
+
// Only unregister if not already hidden (avoid double unregister)
|
|
140
|
+
if (this.visible) {
|
|
141
|
+
this.apiToast().unregisterItem(this);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
methods: {
|
|
146
|
+
startTimer() {
|
|
147
|
+
if (this.duration > 0) {
|
|
148
|
+
this.timer = setTimeout(() => {
|
|
149
|
+
this.dismiss();
|
|
150
|
+
}, this.duration);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
clearTimer() {
|
|
155
|
+
if (this.timer) {
|
|
156
|
+
clearTimeout(this.timer);
|
|
157
|
+
this.timer = null;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
pauseTimer() {
|
|
162
|
+
this.clearTimer();
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
resumeTimer() {
|
|
166
|
+
this.startTimer();
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
dismiss() {
|
|
170
|
+
// this.visible = false;
|
|
171
|
+
this.$emit('close');
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
onAfterLeave() {
|
|
175
|
+
// Unregister from parent after transition completes
|
|
176
|
+
this.apiToast().unregisterItem(this);
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="[headless ? 'toast-title' : 'text-sm font-semibold text-white']"
|
|
6
|
+
>
|
|
7
|
+
<slot></slot>
|
|
8
|
+
</component>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
export default {
|
|
13
|
+
name: 'VTToastTitle',
|
|
14
|
+
|
|
15
|
+
inject: ['apiToastItem'],
|
|
16
|
+
|
|
17
|
+
props: {
|
|
18
|
+
headless: {
|
|
19
|
+
type: Boolean,
|
|
20
|
+
default: false,
|
|
21
|
+
},
|
|
22
|
+
as: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'div',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
computed: {
|
|
29
|
+
id() {
|
|
30
|
+
return `toast-title-${this.apiToastItem().componentId}`;
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
<div
|
|
3
3
|
:id="id"
|
|
4
4
|
:aria-describedby="ariaDescribedBy"
|
|
5
|
-
class="inline-flex"
|
|
6
5
|
@mouseenter="onMouseenter"
|
|
7
|
-
@mouseleave="
|
|
6
|
+
@mouseleave="onmouseout"
|
|
8
7
|
>
|
|
9
8
|
<slot />
|
|
10
9
|
</div>
|
|
@@ -55,7 +54,7 @@ export default {
|
|
|
55
54
|
}, this.apiTooltip().delayDuration);
|
|
56
55
|
},
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
onmouseout() {
|
|
59
58
|
clearTimeout(tooltipTriggerTimeout);
|
|
60
59
|
this.cancel();
|
|
61
60
|
},
|
|
@@ -75,8 +74,7 @@ export default {
|
|
|
75
74
|
// delay stop propagation to close other visible
|
|
76
75
|
// dropdowns and delay click event to control
|
|
77
76
|
// this dropdown visibility
|
|
78
|
-
|
|
79
|
-
setTimeout(() => this.showComponentContent(), 100);
|
|
77
|
+
this.showComponentContent();
|
|
80
78
|
},
|
|
81
79
|
|
|
82
80
|
cancel() {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<transition
|
|
3
3
|
enter-active-class="duration-300 ease-out"
|
|
4
|
-
enter-class="transform opacity-0"
|
|
4
|
+
enter-from-class="transform opacity-0"
|
|
5
5
|
enter-to-class="opacity-100"
|
|
6
6
|
leave-active-class="duration-300 ease-in"
|
|
7
|
-
leave-class="opacity-100"
|
|
7
|
+
leave-from-class="opacity-100"
|
|
8
8
|
leave-to-class="transform opacity-0"
|
|
9
9
|
@after-enter="afterEnter"
|
|
10
10
|
@after-leave="afterLeave"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Teleport to="body">
|
|
3
3
|
<transition
|
|
4
4
|
enter-active-class="duration-200 ease-out"
|
|
5
|
-
enter-class="
|
|
6
|
-
enter-to-class="
|
|
5
|
+
:enter-from-class="transitionEnterClass"
|
|
6
|
+
:enter-to-class="transitionEnterToClass"
|
|
7
7
|
leave-active-class="duration-200 ease-in"
|
|
8
|
-
leave-class="
|
|
9
|
-
leave-to-class="
|
|
8
|
+
:leave-from-class="transitionLeaveClass"
|
|
9
|
+
:leave-to-class="transitionLeaveToClass"
|
|
10
10
|
@after-leave="hidden"
|
|
11
11
|
@after-enter="shown"
|
|
12
12
|
>
|
|
@@ -15,23 +15,19 @@
|
|
|
15
15
|
:class="[
|
|
16
16
|
headless
|
|
17
17
|
? `${this.component}-content`
|
|
18
|
-
: `
|
|
18
|
+
: `absolute z-50 grid overflow-hidden rounded-md shadow-md ${this.classes} ${this.portalClass}`,
|
|
19
19
|
]"
|
|
20
20
|
v-bind="$attrs"
|
|
21
21
|
>
|
|
22
22
|
<slot />
|
|
23
23
|
</div>
|
|
24
24
|
</transition>
|
|
25
|
-
</
|
|
25
|
+
</Teleport>
|
|
26
26
|
</template>
|
|
27
27
|
|
|
28
28
|
<script>
|
|
29
|
-
import { Portal } from '@linusborg/vue-simple-portal';
|
|
30
|
-
|
|
31
29
|
export default {
|
|
32
|
-
|
|
33
|
-
Portal,
|
|
34
|
-
},
|
|
30
|
+
inheritAttrs: false,
|
|
35
31
|
|
|
36
32
|
props: {
|
|
37
33
|
component: {
|
|
@@ -46,6 +42,10 @@ export default {
|
|
|
46
42
|
type: Boolean,
|
|
47
43
|
default: false,
|
|
48
44
|
},
|
|
45
|
+
ariaActivedescendant: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: null,
|
|
48
|
+
},
|
|
49
49
|
portalClass: {
|
|
50
50
|
type: [String, Function],
|
|
51
51
|
default: null,
|
|
@@ -58,7 +58,25 @@ export default {
|
|
|
58
58
|
},
|
|
59
59
|
|
|
60
60
|
classes() {
|
|
61
|
-
return this.isTooltip
|
|
61
|
+
return this.isTooltip
|
|
62
|
+
? 'bg-gray-800 text-sm text-white py-1 px-2'
|
|
63
|
+
: 'bg-white py-2 px-3';
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
transitionEnterClass() {
|
|
67
|
+
return this.isTooltip ? 'opacity-0' : 'translate-y-[15px] opacity-0';
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
transitionEnterToClass() {
|
|
71
|
+
return this.isTooltip ? 'opacity-100' : 'translate-y-0 opacity-100';
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
transitionLeaveClass() {
|
|
75
|
+
return this.transitionEnterToClass;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
transitionLeaveToClass() {
|
|
79
|
+
return this.transitionEnterClass;
|
|
62
80
|
},
|
|
63
81
|
},
|
|
64
82
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const currencyFormatter = ({ locale = 'en-US', currency = 'USD' }) => {
|
|
2
|
+
return Intl.NumberFormat(locale, {
|
|
3
|
+
style: 'currency',
|
|
4
|
+
currency,
|
|
5
|
+
});
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getCurrencySymbol = (options) => {
|
|
9
|
+
const formatter = currencyFormatter(options);
|
|
10
|
+
const parts = formatter.formatToParts(0); // can be any number
|
|
11
|
+
const currency = parts.find((part) => part.type === 'currency');
|
|
12
|
+
const symbol = currency.value;
|
|
13
|
+
|
|
14
|
+
return removeLetters(symbol);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Remove all letters (for example, change CA$ to $)
|
|
18
|
+
const removeLetters = (value) => {
|
|
19
|
+
const lettersRegex = /[A-Za-z]/g;
|
|
20
|
+
return value.replaceAll(lettersRegex, '');
|
|
21
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {HTMLElement} el
|
|
4
|
+
* @param {HTMLElement} parent
|
|
5
|
+
*/
|
|
6
|
+
export const scrollElementIntoView = (el, parent) => {
|
|
7
|
+
// this works better than scrollIntoView
|
|
8
|
+
if (parent.scrollHeight <= parent.clientHeight) return;
|
|
9
|
+
|
|
10
|
+
const scrollBottom = parent.clientHeight + parent.scrollTop;
|
|
11
|
+
const elBottom = el.offsetTop + el.offsetHeight;
|
|
12
|
+
|
|
13
|
+
if (elBottom > scrollBottom) {
|
|
14
|
+
parent.scrollTop = elBottom - parent.clientHeight;
|
|
15
|
+
} else if (el.offsetTop < parent.scrollTop) {
|
|
16
|
+
parent.scrollTop = el.offsetTop;
|
|
17
|
+
}
|
|
18
|
+
};
|
package/src/utils/images.js
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resizes an image by adjusting its width and returns the modified URL.
|
|
3
|
+
*
|
|
4
|
+
* This method processes image URLs from CloudFront CDN. If the URL is a valid
|
|
5
|
+
* string or object, it decodes the URL, adjusts the width in the `resize` edits,
|
|
6
|
+
* and returns the updated encoded URL. If no width is provided, a default value
|
|
7
|
+
* of 450 is used. If an error occurs, the original CDN URL is returned.
|
|
8
|
+
*
|
|
9
|
+
* @param {(string|object)} url - The URL of the image, either as a string or object.
|
|
10
|
+
* @param {number} [width=450] - The width to resize the image to (default is 450 if not specified).
|
|
11
|
+
*
|
|
12
|
+
* @returns {string} The updated image URL with the resized width, or the original URL if an error occurs.
|
|
13
|
+
*/
|
|
1
14
|
export const handleImageResizing = (url, width) => {
|
|
2
15
|
// early exit if url is null for whatever reason
|
|
3
16
|
if (!url) {
|
|
4
17
|
return '';
|
|
5
18
|
}
|
|
6
|
-
|
|
19
|
+
|
|
7
20
|
let cdn;
|
|
8
|
-
|
|
21
|
+
|
|
9
22
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
// new code
|
|
24
|
+
if (typeof url === 'string' && url.includes('cloudfront.net/')) {
|
|
25
|
+
cdn = url;
|
|
26
|
+
} else {
|
|
27
|
+
// old code
|
|
28
|
+
if (typeof url === 'object') {
|
|
29
|
+
cdn = url.cdn_url;
|
|
30
|
+
}
|
|
13
31
|
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
if (typeof url === 'string') {
|
|
33
|
+
cdn = JSON.parse(url).cdn_url;
|
|
34
|
+
}
|
|
16
35
|
}
|
|
17
36
|
|
|
18
37
|
const base64String = cdn.split('net/');
|
|
@@ -23,7 +42,7 @@ export const handleImageResizing = (url, width) => {
|
|
|
23
42
|
// browsers.
|
|
24
43
|
//
|
|
25
44
|
// Details: https://developer.mozilla.org/en-US/docs/Web/API/atob
|
|
26
|
-
|
|
45
|
+
const decodedString = JSON.parse(atob(base64String[1]));
|
|
27
46
|
|
|
28
47
|
width
|
|
29
48
|
? (decodedString.edits.resize.width = width)
|
|
@@ -37,8 +56,8 @@ export const handleImageResizing = (url, width) => {
|
|
|
37
56
|
const encodedString = btoa(JSON.stringify(decodedString));
|
|
38
57
|
const encodedUrl = `${firstPart}net/${encodedString}`;
|
|
39
58
|
|
|
40
|
-
return encodedUrl
|
|
41
|
-
} catch(error) {
|
|
42
|
-
|
|
59
|
+
return encodedUrl;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return cdn;
|
|
43
62
|
}
|
|
44
|
-
};
|
|
63
|
+
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<input
|
|
3
|
-
:class="classes"
|
|
4
|
-
class="form-control"
|
|
5
|
-
:data-theme="theme"
|
|
6
|
-
:type="type"
|
|
7
|
-
:value="value"
|
|
8
|
-
v-on="listeners"
|
|
9
|
-
/>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<script>
|
|
13
|
-
export default {
|
|
14
|
-
name: 'VTInput',
|
|
15
|
-
|
|
16
|
-
props: {
|
|
17
|
-
lazy: {
|
|
18
|
-
type: Boolean,
|
|
19
|
-
default: false,
|
|
20
|
-
},
|
|
21
|
-
type: {
|
|
22
|
-
type: String,
|
|
23
|
-
default: 'text',
|
|
24
|
-
},
|
|
25
|
-
theme: {
|
|
26
|
-
type: String,
|
|
27
|
-
default: null,
|
|
28
|
-
validator(value) {
|
|
29
|
-
return ['dark'].includes(value);
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
variant: {
|
|
33
|
-
type: [String, Object],
|
|
34
|
-
default: '',
|
|
35
|
-
validator(value) {
|
|
36
|
-
if (value === '' || typeof value === 'object') {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return ['success', 'warning', 'error'].includes(value);
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
value: {
|
|
44
|
-
type: [String, Number, Object, Array],
|
|
45
|
-
default: null,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
computed: {
|
|
50
|
-
classes() {
|
|
51
|
-
const classes = {};
|
|
52
|
-
|
|
53
|
-
if (this.variant) {
|
|
54
|
-
classes[`form-control--${this.variant}`] = true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return classes;
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
listeners() {
|
|
61
|
-
// `Object.assign` merges objects together to form a new object
|
|
62
|
-
return Object.assign(
|
|
63
|
-
{},
|
|
64
|
-
// We add all the listeners from the parent
|
|
65
|
-
this.$listeners,
|
|
66
|
-
// Then we can add custom listeners or override the
|
|
67
|
-
// behavior of some listeners.
|
|
68
|
-
{
|
|
69
|
-
// This ensures that the component works with v-model
|
|
70
|
-
input: (event) => {
|
|
71
|
-
if (this.lazy) return;
|
|
72
|
-
this.$emit('input', event.target.value);
|
|
73
|
-
},
|
|
74
|
-
blur: (event) => {
|
|
75
|
-
this.$emit('blur', event);
|
|
76
|
-
},
|
|
77
|
-
}
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
</script>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<VTInput v-model="date" type="date" />
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script>
|
|
6
|
-
import VTInput from './VTInput.vue';
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
name: 'VTInputDate',
|
|
10
|
-
|
|
11
|
-
components: { VTInput },
|
|
12
|
-
|
|
13
|
-
model: {
|
|
14
|
-
prop: 'value',
|
|
15
|
-
event: 'input',
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
props: {
|
|
19
|
-
value: {
|
|
20
|
-
type: String,
|
|
21
|
-
default: '',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
computed: {
|
|
26
|
-
date: {
|
|
27
|
-
get() {
|
|
28
|
-
return this.$date.format(this.value, 'YYYY-MM-DD');
|
|
29
|
-
},
|
|
30
|
-
set(newDate) {
|
|
31
|
-
this.$emit('input', newDate);
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
</script>
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="flex items-stretch gap-2">
|
|
3
|
-
<VTInput
|
|
4
|
-
ref="input"
|
|
5
|
-
type="file"
|
|
6
|
-
:value="value"
|
|
7
|
-
:theme="theme"
|
|
8
|
-
v-bind="$attrs"
|
|
9
|
-
@change="onChange"
|
|
10
|
-
/>
|
|
11
|
-
<VTButton :theme="theme" @click.stop="onButtonClick">Browse</VTButton>
|
|
12
|
-
</div>
|
|
13
|
-
</template>
|
|
14
|
-
|
|
15
|
-
<script>
|
|
16
|
-
import VTButton from '../Button/VTButton.vue';
|
|
17
|
-
import VTInput from './VTInput.vue';
|
|
18
|
-
|
|
19
|
-
export default {
|
|
20
|
-
name: 'VTInputFile',
|
|
21
|
-
|
|
22
|
-
components: {
|
|
23
|
-
VTInput,
|
|
24
|
-
VTButton,
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
inheritAttrs: false,
|
|
28
|
-
|
|
29
|
-
props: {
|
|
30
|
-
theme: {
|
|
31
|
-
type: String,
|
|
32
|
-
default: null,
|
|
33
|
-
validator(value) {
|
|
34
|
-
return ['dark'].includes(value);
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
multiple: {
|
|
38
|
-
type: Boolean,
|
|
39
|
-
default: false,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
data() {
|
|
44
|
-
return {
|
|
45
|
-
value: null,
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
methods: {
|
|
50
|
-
onChange(event) {
|
|
51
|
-
this.value = this.$refs.input.$el.value;
|
|
52
|
-
this.$emit('change', event);
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
onButtonClick() {
|
|
56
|
-
this.$refs.input.$el.click();
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
</script>
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<label
|
|
3
|
-
class="flex h-full w-full flex-col items-center justify-center rounded border-2 border-dotted border-white p-4 text-center hover:border-fl-500 hover:bg-fd-500"
|
|
4
|
-
:class="{ 'border-fl-500 bg-fd-500': isDraggingOver }"
|
|
5
|
-
@drop.prevent="onDrop"
|
|
6
|
-
@dragover.prevent="onDragOver"
|
|
7
|
-
@dragleave.prevent="onDragLeave"
|
|
8
|
-
>
|
|
9
|
-
<IconImagePlaceholder class="mb-3" />
|
|
10
|
-
<span>Drop your images here, or click to browse</span>
|
|
11
|
-
<VTInput type="file" class="sr-only" v-bind="$attrs" @change="onChange" />
|
|
12
|
-
</label>
|
|
13
|
-
</template>
|
|
14
|
-
|
|
15
|
-
<script>
|
|
16
|
-
import { IconImagePlaceholder } from '@veritree/icons';
|
|
17
|
-
import VTInput from './VTInput.vue';
|
|
18
|
-
|
|
19
|
-
export default {
|
|
20
|
-
name: 'VTInputFile',
|
|
21
|
-
|
|
22
|
-
components: {
|
|
23
|
-
VTInput,
|
|
24
|
-
IconImagePlaceholder,
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
inheritAttrs: false,
|
|
28
|
-
|
|
29
|
-
data() {
|
|
30
|
-
return {
|
|
31
|
-
isDraggingOver: false,
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
methods: {
|
|
36
|
-
onDrop(event) {
|
|
37
|
-
this.isDraggingOver = false;
|
|
38
|
-
this.$emit('drop', event);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
onDragOver() {
|
|
42
|
-
this.isDraggingOver = true;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
onDragLeave() {
|
|
46
|
-
this.isDraggingOver = false;
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
onChange(event) {
|
|
50
|
-
this.$emit('change', event);
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
</script>
|