@weni/unnnic-system 2.6.1-alpha.2 → 2.6.2

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.
@@ -1,349 +0,0 @@
1
- <template>
2
- <section
3
- ref="dropzone"
4
- :class="{
5
- 'unnnic-upload-area__dropzone': true,
6
- 'unnnic-upload-area__dropzone__is-dragover': isDragging,
7
- 'unnnic-upload-area__dropzone__has-error': hasError,
8
- }"
9
- @dragenter.stop.prevent="dragenter"
10
- @dragover.stop.prevent="dragover"
11
- @dragleave.stop.prevent="dragleave"
12
- @dragend.stop.prevent="dragend"
13
- @drop.stop.prevent="drop"
14
- @click="() => $refs.file.click()"
15
- >
16
- <UnnnicIcon
17
- class="unnnic-upload-area__dropzone__icon"
18
- icon="upload"
19
- :scheme="hasError ? 'feedback-red' : 'weni-500'"
20
- size="lg"
21
- />
22
-
23
- <div class="unnnic-upload-area__dropzone__content">
24
- <span class="unnnic-upload-area__dropzone__content__title">
25
- <slot name="title">
26
- {{ $t('upload_area.title.text') }}
27
- <span
28
- :class="`unnnic-upload-area__dropzone__content__title__${
29
- hasError ? 'error' : 'search'
30
- }`"
31
- >
32
- {{ $t('upload_area.title.highlight') }}
33
- </span>
34
- </slot>
35
- </span>
36
- <span
37
- :class="[
38
- 'unnnic-upload-area__dropzone__content__subtitle',
39
- {
40
- 'unnnic-upload-area__dropzone__content__subtitle__error': hasError,
41
- },
42
- ]"
43
- :title="formattedSupportedFormats"
44
- >
45
- <slot name="subtitle">
46
- {{
47
- subtitle ||
48
- `${$t(
49
- `upload_area${hasError ? '.invalid' : ''}.subtitle`,
50
- )} ${formattedSupportedFormats}`
51
- }}
52
- </slot>
53
- </span>
54
- </div>
55
- <input
56
- v-show="false"
57
- ref="file"
58
- type="file"
59
- :accept="supportedFormats"
60
- :multiple="acceptMultiple"
61
- @input="handleFileChange"
62
- />
63
- </section>
64
- </template>
65
-
66
- <script setup>
67
- import { ref, computed, getCurrentInstance } from 'vue';
68
- import mime from 'mime';
69
-
70
- import UnnnicIcon from '../Icon.vue';
71
-
72
- const isDragging = ref(false);
73
- const hasError = ref(false);
74
- const dragEnterCounter = ref(0);
75
- const file = ref();
76
-
77
- const props = defineProps({
78
- acceptMultiple: {
79
- type: Boolean,
80
- default: true,
81
- },
82
-
83
- supportedFormats: {
84
- type: String,
85
- default: '*',
86
- },
87
-
88
- maxFileSize: {
89
- type: Number,
90
- default: undefined,
91
- },
92
-
93
- shouldReplace: {
94
- type: Boolean,
95
- default: false,
96
- },
97
-
98
- currentFiles: {
99
- type: Array,
100
- default() {
101
- return [];
102
- },
103
- },
104
-
105
- maximumUploads: {
106
- type: Number,
107
- default: 1,
108
- },
109
-
110
- subtitle: {
111
- type: String,
112
- default: '',
113
- },
114
- });
115
-
116
- const emit = defineEmits([
117
- 'update:currentFiles',
118
- 'fileChange',
119
- 'unsupportedFormat',
120
- 'exceededTheMaximumFileSizeLimit',
121
- ]);
122
-
123
- const instance = getCurrentInstance();
124
-
125
- function isEventDefined(eventName) {
126
- return !!instance?.vnode?.props?.[eventName];
127
- }
128
-
129
- const formattedSupportedFormats = computed(() => {
130
- const formats = props.supportedFormats
131
- .split(',')
132
- .map((format) => format.toUpperCase());
133
-
134
- return formats.join(', ');
135
- });
136
-
137
- function dragenter() {
138
- dragEnterCounter.value += 1;
139
- isDragging.value = true;
140
- }
141
-
142
- function dragover() {
143
- isDragging.value = true;
144
- }
145
-
146
- function dragleave() {
147
- dragEnterCounter.value -= 1;
148
- if (dragEnterCounter.value === 0) {
149
- isDragging.value = false;
150
- }
151
- }
152
-
153
- function dragend() {
154
- isDragging.value = false;
155
- }
156
-
157
- function drop(event) {
158
- isDragging.value = false;
159
-
160
- const { files } = event.dataTransfer;
161
-
162
- if (validateFiles(files)) {
163
- addFiles(files);
164
- }
165
- }
166
-
167
- function handleFileChange(event) {
168
- const { files } = event.target;
169
-
170
- if (validateFiles(files)) {
171
- addFiles(files);
172
- }
173
-
174
- file.value.value = '';
175
- }
176
-
177
- function setErrorState() {
178
- hasError.value = true;
179
-
180
- setTimeout(() => {
181
- hasError.value = false;
182
- }, 5000);
183
- }
184
-
185
- function validateFiles(files) {
186
- if (!props.acceptMultiple && files.length > 1) {
187
- setErrorState();
188
- return false;
189
- }
190
-
191
- if (!validFormat(files)) {
192
- if (isEventDefined('onUnsupportedFormat')) {
193
- emit('unsupportedFormat');
194
- } else {
195
- setErrorState();
196
- }
197
-
198
- return false;
199
- }
200
-
201
- if (!validSize(files)) {
202
- if (isEventDefined('onExceededTheMaximumFileSizeLimit')) {
203
- emit('exceededTheMaximumFileSizeLimit');
204
- } else {
205
- setErrorState();
206
- }
207
-
208
- return false;
209
- }
210
-
211
- return true;
212
- }
213
-
214
- function validFormat(files) {
215
- if (props.supportedFormats === '*') {
216
- return true;
217
- }
218
-
219
- const formats = props.supportedFormats
220
- .split(',')
221
- .map((format) => format.trim());
222
-
223
- const isValid = Array.from(files).find((file) => {
224
- const fileName = file.name.toLowerCase();
225
- const fileType = file.type.toLowerCase();
226
- const fileExtension = `.${fileName.split('.').pop()}`;
227
-
228
- const isValidFileExtension = formats.includes(fileExtension);
229
- const isValidFileType = fileType === mime.getType(fileName);
230
-
231
- return isValidFileExtension && isValidFileType;
232
- });
233
-
234
- return isValid;
235
- }
236
-
237
- function validSize(files) {
238
- if (!props.maxFileSize) {
239
- return true;
240
- }
241
-
242
- const isValid = Array.from(files).find((file) => {
243
- const sizeInMB = (file.size / (1024 * 1024)).toFixed(2);
244
-
245
- return sizeInMB <= props.maxFileSize;
246
- });
247
-
248
- return isValid;
249
- }
250
-
251
- function addFiles(files) {
252
- let totalLength = files.length;
253
-
254
- if (!props.shouldReplace) {
255
- totalLength += props.currentFiles.length;
256
- }
257
-
258
- if (totalLength > props.maximumUploads) {
259
- setErrorState();
260
- return;
261
- }
262
-
263
- const validFiles = Array.from(files).filter((file) => {
264
- if (validFormat([file]) && validSize([file])) {
265
- return true;
266
- }
267
- return false;
268
- });
269
-
270
- let currentFiles;
271
-
272
- if (props.shouldReplace) {
273
- currentFiles = validFiles;
274
- } else {
275
- currentFiles = props.currentFiles.concat(validFiles);
276
- }
277
-
278
- emit('update:currentFiles', currentFiles);
279
- emit('fileChange', currentFiles);
280
- }
281
- </script>
282
-
283
- <style lang="scss" scoped>
284
- @import '../../assets/scss/unnnic.scss';
285
-
286
- @function borderDashed($color) {
287
- $colorString: unquote('' + $color);
288
- $cleanColor: str-slice($colorString, 2);
289
- @return url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%23#{$cleanColor}' stroke-width='1' stroke-dasharray='4%2c 4' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
290
- }
291
-
292
- .unnnic-upload-area__dropzone {
293
- border-radius: $unnnic-border-radius-md;
294
- padding: $unnnic-spacing-inset-lg;
295
-
296
- // Dashed border with increased dashes spacing and color neutral clean
297
- background-image: borderDashed($unnnic-color-neutral-clean);
298
-
299
- &__has-error {
300
- background-image: borderDashed($unnnic-color-feedback-red);
301
- }
302
-
303
- &__is-dragover {
304
- background-color: $unnnic-color-background-sky;
305
- background-image: borderDashed($unnnic-color-brand-weni);
306
- }
307
-
308
- &__icon {
309
- display: block;
310
- margin: 0 auto;
311
-
312
- margin-bottom: $unnnic-spacing-stack-xs;
313
- }
314
-
315
- &__content {
316
- display: flex;
317
- flex-direction: column;
318
-
319
- text-align: center;
320
- font-family: $unnnic-font-family-secondary;
321
-
322
- &__title {
323
- color: $unnnic-color-neutral-darkest;
324
- font-weight: $unnnic-font-weight-regular;
325
- font-size: $unnnic-font-size-body-gt;
326
- line-height: $unnnic-font-size-body-gt + $unnnic-line-height-md;
327
-
328
- &__search {
329
- color: $unnnic-color-brand-weni;
330
- }
331
-
332
- &__error {
333
- color: $unnnic-color-feedback-red;
334
- }
335
- }
336
-
337
- &__subtitle {
338
- color: $unnnic-color-neutral-cloudy;
339
- font-weight: $unnnic-font-weight-regular;
340
- font-size: $unnnic-font-size-body-md;
341
- line-height: $unnnic-font-size-body-md + $unnnic-line-height-md;
342
-
343
- &__error {
344
- color: $unnnic-color-feedback-red;
345
- }
346
- }
347
- }
348
- }
349
- </style>
@@ -1,126 +0,0 @@
1
- <template>
2
- <TourMask
3
- v-if="isTourActive"
4
- :maskRect="maskRect"
5
- />
6
-
7
- <TourPopover
8
- v-if="isTourActive"
9
- :step="currentStepOptions"
10
- :stepsLength="steps.length"
11
- :currentStep="currentStep"
12
- :attachedElement="currentStepOptions.attachedElement"
13
- @end="end"
14
- @next-step="nextStep"
15
- />
16
- </template>
17
-
18
- <script>
19
- import TourMask from './TourMask.vue';
20
- import TourPopover from './TourPopover.vue';
21
-
22
- export default {
23
- name: 'UnnnicTour',
24
-
25
- components: {
26
- TourMask,
27
- TourPopover,
28
- },
29
-
30
- props: {
31
- steps: {
32
- required: true,
33
- type: Array,
34
- },
35
- },
36
-
37
- data() {
38
- return {
39
- isTourActive: false,
40
- currentStep: 1,
41
- maskRect: {
42
- x: 0,
43
- y: 0,
44
- width: 0,
45
- height: 0,
46
- },
47
- };
48
- },
49
-
50
- computed: {
51
- currentStepOptions() {
52
- return this.steps[this.currentStep - 1];
53
- },
54
- stepStyle() {
55
- const attachedElement = this.currentStepOptions.attachedElement;
56
-
57
- if (!this.isTourActive || !attachedElement) {
58
- return {};
59
- }
60
-
61
- const { top, left, width, height } =
62
- attachedElement.getBoundingClientRect();
63
- return {
64
- top: `${top}px`,
65
- left: `${left}px`,
66
- width: `${width}px`,
67
- height: `${height}px`,
68
- };
69
- },
70
- },
71
-
72
- watch: {
73
- currentStep() {
74
- this.updateMaskRect();
75
- },
76
- isTourActive(isTourActive) {
77
- if (isTourActive) {
78
- this.updateMaskRect();
79
- }
80
- },
81
- },
82
-
83
- methods: {
84
- start() {
85
- this.isTourActive = true;
86
- this.currentStep = 1;
87
- },
88
- end() {
89
- this.isTourActive = false;
90
- this.currentStep = 1;
91
- },
92
- handleStep(step) {
93
- if (this.currentStep < this.steps.length) {
94
- this.currentStep = step;
95
- }
96
- },
97
- nextStep() {
98
- const { handleStep, steps, currentStep, end } = this;
99
-
100
- if (currentStep === steps.length) {
101
- end();
102
- return;
103
- }
104
-
105
- handleStep(currentStep + 1);
106
- },
107
- updateMaskRect() {
108
- const attachedElement = this.currentStepOptions?.attachedElement;
109
- const { padding } = this.currentStepOptions;
110
-
111
- if (!attachedElement) {
112
- return;
113
- }
114
-
115
- const { top, left, width, height } =
116
- attachedElement.getBoundingClientRect();
117
- this.maskRect = {
118
- x: left - (padding?.horizontal || 0) / 2,
119
- y: top - (padding?.vertical || 0) / 2,
120
- width: width + (padding?.horizontal || 0),
121
- height: height + (padding?.vertical || 0),
122
- };
123
- },
124
- },
125
- };
126
- </script>
@@ -1,135 +0,0 @@
1
- <template>
2
- <section class="unnnic-tour__mask">
3
- <svg
4
- class="mask__svg"
5
- width="100%"
6
- height="100%"
7
- >
8
- <defs>
9
- <mask
10
- id="svgTourMask"
11
- x="0"
12
- y="0"
13
- width="100%"
14
- height="100%"
15
- >
16
- <rect
17
- x="0"
18
- y="0"
19
- width="100vw"
20
- height="100vh"
21
- fill="white"
22
- />
23
- <rect
24
- :x="maskRect.x"
25
- :y="maskRect.y"
26
- :width="maskRect.width"
27
- :height="maskRect.height"
28
- rx="4"
29
- fill="black"
30
- class="unnnic-tour__step"
31
- />
32
- </mask>
33
- </defs>
34
- <rect
35
- x="0"
36
- y="0"
37
- width="100%"
38
- height="100%"
39
- mask="url(#svgTourMask)"
40
- class="unnnic-tour__overlay"
41
- />
42
- <rect
43
- v-for="(rect, index) of clickBlockerRects"
44
- :key="index"
45
- :x="rect.x"
46
- :y="rect.y"
47
- :width="rect.width"
48
- :height="rect.height"
49
- :style="rect.style"
50
- fill="transparent"
51
- pointer-events="auto"
52
- />
53
- </svg>
54
- </section>
55
- </template>
56
-
57
- <script>
58
- export default {
59
- name: 'TourMask',
60
-
61
- props: {
62
- maskRect: {
63
- type: Object,
64
- required: true,
65
- },
66
- },
67
-
68
- computed: {
69
- clickBlockerRects() {
70
- const { maskRect } = this;
71
- return [
72
- {
73
- x: '0',
74
- y: '0',
75
- width: '100%',
76
- height: maskRect.y,
77
- },
78
- {
79
- x: '0',
80
- y: '0',
81
- width: maskRect.x,
82
- height: '100%',
83
- },
84
- {
85
- x: '0',
86
- y: maskRect.y + maskRect.height,
87
- style: { height: `calc(100vh - ${maskRect.y}px)` },
88
- width: '100%',
89
- },
90
- {
91
- x: maskRect.x + maskRect.width,
92
- y: '0',
93
- style: { width: `calc(100vw - ${maskRect.x}px)` },
94
- height: '100%',
95
- },
96
- ];
97
- },
98
- },
99
- };
100
- </script>
101
-
102
- <style lang="scss" scoped>
103
- @import '../../assets/scss/unnnic.scss';
104
-
105
- .unnnic-tour {
106
- &__mask {
107
- position: fixed;
108
- z-index: 1000;
109
-
110
- inset: 0;
111
-
112
- pointer-events: none;
113
-
114
- width: 100%;
115
- height: 100%;
116
-
117
- &__svg {
118
- position: absolute;
119
- top: 0;
120
- left: 0;
121
-
122
- width: 100%;
123
- height: 100%;
124
- }
125
- }
126
-
127
- &__step {
128
- transition: all 0.2s ease;
129
- }
130
-
131
- &__overlay {
132
- fill: rgba($unnnic-color-neutral-black, $unnnic-opacity-level-overlay);
133
- }
134
- }
135
- </style>