@weni/unnnic-system 1.24.21 → 1.25.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weni/unnnic-system",
3
- "version": "1.24.21",
3
+ "version": "1.25.0",
4
4
  "main": "./dist/unnnic.common.js",
5
5
  "files": [
6
6
  "dist/*",
@@ -0,0 +1,362 @@
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>
67
+ import mime from 'mime-types';
68
+
69
+ import UnnnicIcon from '../Icon.vue';
70
+
71
+ export default {
72
+
73
+ components: {
74
+ UnnnicIcon,
75
+ },
76
+
77
+ props: {
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
+ emits: [
117
+ 'update:currentFiles',
118
+ 'fileChange',
119
+ 'unsupportedFormat',
120
+ 'exceededTheMaximumFileSizeLimit',
121
+ ],
122
+
123
+ data() {
124
+ return {
125
+ isDragging: false,
126
+ hasError: false,
127
+ dragEnterCounter: 0,
128
+ };
129
+ },
130
+
131
+ computed: {
132
+ formattedSupportedFormats() {
133
+ const formats = this.supportedFormats
134
+ .split(',')
135
+ .map((format) => format.toUpperCase());
136
+
137
+ return formats.join(', ');
138
+ },
139
+ },
140
+
141
+ methods: {
142
+ isEventDefined(eventName) {
143
+ return this.$listeners[eventName];
144
+ },
145
+
146
+ dragenter() {
147
+ this.dragEnterCounter += 1;
148
+ this.isDragging = true;
149
+ },
150
+
151
+ dragover() {
152
+ this.isDragging = true;
153
+ },
154
+
155
+ dragleave() {
156
+ this.dragEnterCounter -= 1;
157
+ if (this.dragEnterCounter === 0) {
158
+ this.isDragging = false;
159
+ }
160
+ },
161
+
162
+ dragend() {
163
+ this.isDragging = false;
164
+ },
165
+
166
+ drop(event) {
167
+ this.isDragging = false;
168
+
169
+ const { files } = event.dataTransfer;
170
+
171
+ if (this.validateFiles(files)) {
172
+ this.addFiles(files);
173
+ }
174
+ },
175
+
176
+ handleFileChange(event) {
177
+ const { files } = event.target;
178
+
179
+ if (this.validateFiles(files)) {
180
+ this.addFiles(files);
181
+ }
182
+
183
+ this.$refs.file.value = '';
184
+ },
185
+
186
+ setErrorState() {
187
+ this.hasError = true;
188
+
189
+ setTimeout(() => {
190
+ this.hasError = false;
191
+ }, 5000);
192
+ },
193
+
194
+ validateFiles(files) {
195
+ if (!this.acceptMultiple && files.length > 1) {
196
+ this.setErrorState();
197
+ return false;
198
+ }
199
+
200
+ if (!this.validFormat(files)) {
201
+ if (this.isEventDefined('unsupportedFormat')) {
202
+ this.$emit('unsupportedFormat');
203
+ } else {
204
+ this.setErrorState();
205
+ }
206
+
207
+ return false;
208
+ }
209
+
210
+ if (!this.validSize(files)) {
211
+ if (this.isEventDefined('exceededTheMaximumFileSizeLimit')) {
212
+ this.$emit('exceededTheMaximumFileSizeLimit');
213
+ } else {
214
+ this.setErrorState();
215
+ }
216
+
217
+ return false;
218
+ }
219
+
220
+ return true;
221
+ },
222
+
223
+ validFormat(files) {
224
+ if (this.supportedFormats === '*') {
225
+ return true;
226
+ }
227
+
228
+ const formats = this.supportedFormats
229
+ .split(',')
230
+ .map((format) => format.trim());
231
+
232
+ const isValid = Array.from(files).find((file) => {
233
+ const fileName = file.name.toLowerCase();
234
+ const fileType = file.type.toLowerCase();
235
+ const fileExtension = `.${fileName.split('.').pop()}`;
236
+
237
+ const isValidFileExtension = formats.includes(fileExtension);
238
+ const isValidFileType = fileType === mime.lookup(fileName);
239
+
240
+ return isValidFileExtension && isValidFileType;
241
+ });
242
+
243
+ return isValid;
244
+ },
245
+
246
+ validSize(files) {
247
+ if (!this.maxFileSize) {
248
+ return true;
249
+ }
250
+
251
+ const isValid = Array.from(files).find((file) => {
252
+ const sizeInMB = (file.size / (1024 * 1024)).toFixed(2);
253
+
254
+ return sizeInMB <= this.maxFileSize;
255
+ });
256
+
257
+ return isValid;
258
+ },
259
+
260
+ addFiles(files) {
261
+ let totalLength = files.length;
262
+
263
+ if (!this.shouldReplace) {
264
+ totalLength += this.currentFiles.length;
265
+ }
266
+
267
+ if (totalLength > this.maximumUploads) {
268
+ this.setErrorState();
269
+ return;
270
+ }
271
+
272
+ const validFiles = Array.from(files).filter((file) => {
273
+ if (this.validFormat([file]) && this.validSize([file])) {
274
+ return true;
275
+ }
276
+ return false;
277
+ });
278
+
279
+ let currentFiles;
280
+
281
+ if (this.shouldReplace) {
282
+ currentFiles = validFiles;
283
+ } else {
284
+ currentFiles = this.currentFiles.concat(validFiles);
285
+ }
286
+
287
+ console.log('teste', this.$emit, currentFiles);
288
+
289
+ this.$emit('update:currentFiles', currentFiles);
290
+ this.$emit('fileChange', currentFiles);
291
+ },
292
+ },
293
+ };
294
+ </script>
295
+
296
+ <style lang="scss" scoped>
297
+ @import '../../assets/scss/unnnic.scss';
298
+
299
+ @function borderDashed($color) {
300
+ $colorString: unquote('' + $color);
301
+ $cleanColor: str-slice($colorString, 2);
302
+ @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");
303
+ }
304
+
305
+ .unnnic-upload-area__dropzone {
306
+ border-radius: $unnnic-border-radius-md;
307
+ padding: $unnnic-spacing-inset-lg;
308
+
309
+ // Dashed border with increased dashes spacing and color neutral clean
310
+ background-image: borderDashed($unnnic-color-neutral-clean);
311
+
312
+ &__has-error {
313
+ background-image: borderDashed($unnnic-color-feedback-red);
314
+ }
315
+
316
+ &__is-dragover {
317
+ background-color: $unnnic-color-background-sky;
318
+ background-image: borderDashed($unnnic-color-brand-weni);
319
+ }
320
+
321
+ &__icon {
322
+ display: block;
323
+ margin: 0 auto;
324
+
325
+ margin-bottom: $unnnic-spacing-stack-xs;
326
+ }
327
+
328
+ &__content {
329
+ display: flex;
330
+ flex-direction: column;
331
+
332
+ text-align: center;
333
+ font-family: $unnnic-font-family-secondary;
334
+
335
+ &__title {
336
+ color: $unnnic-color-neutral-darkest;
337
+ font-weight: $unnnic-font-weight-regular;
338
+ font-size: $unnnic-font-size-body-gt;
339
+ line-height: $unnnic-font-size-body-gt + $unnnic-line-height-md;
340
+
341
+ &__search {
342
+ color: $unnnic-color-brand-weni;
343
+ }
344
+
345
+ &__error {
346
+ color: $unnnic-color-feedback-red;
347
+ }
348
+ }
349
+
350
+ &__subtitle {
351
+ color: $unnnic-color-neutral-cloudy;
352
+ font-weight: $unnnic-font-weight-regular;
353
+ font-size: $unnnic-font-size-body-md;
354
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-md;
355
+
356
+ &__error {
357
+ color: $unnnic-color-feedback-red;
358
+ }
359
+ }
360
+ }
361
+ }
362
+ </style>
@@ -1,47 +1,70 @@
1
1
  <template>
2
2
  <div class="unnnic-import-card">
3
+ <UnnnicIcon
4
+ class="unnnic-import-card__file-icon"
5
+ size="md"
6
+ scheme="weni-600"
7
+ :icon="fileIcon"
8
+ />
9
+
3
10
  <div class="unnnic-import-card__data">
4
- <div v-if="isImporting" class="unnnic-import-card__data__title">
11
+ <div
12
+ v-if="isImporting"
13
+ class="unnnic-import-card__data__title"
14
+ >
5
15
  {{ $t('import_card.importing') }}...
6
16
  </div>
7
- <div v-else class="unnnic-import-card__data__title">
17
+ <div
18
+ v-else
19
+ class="unnnic-import-card__data__title"
20
+ >
8
21
  {{ title }}
9
22
  </div>
10
23
 
11
- <div v-if="subtitle && isImporting" class="unnnic-import-card__data__subtitle">
12
- <div v-if="subtitle && isImporting" class="unnnic-import-card__data__subtitle__text">
24
+ <div
25
+ v-if="subtitle && isImporting"
26
+ class="unnnic-import-card__data__subtitle"
27
+ >
28
+ <div
29
+ v-if="subtitle && isImporting"
30
+ class="unnnic-import-card__data__subtitle__text"
31
+ >
13
32
  {{ subtitle }}
14
33
  </div>
15
- <div v-if="subtitle && isImporting" class="unnnic-import-card__data__subtitle__progress">
34
+ <div
35
+ v-if="subtitle && isImporting"
36
+ class="unnnic-import-card__data__subtitle__progress"
37
+ >
16
38
  &nbsp;- {{ importProgress }}%
17
39
  </div>
18
40
  </div>
19
41
  </div>
20
42
 
21
43
  <div class="unnnic-import-card__buttons">
22
- <unnnic-button
44
+ <UnnnicButton
23
45
  v-if="canImport"
24
46
  class="unnnic-import-card__buttons__import"
25
47
  size="small"
26
- :icon-center="uploadIcon"
48
+ :iconCenter="uploadIcon"
27
49
  type="primary"
28
50
  @click="importFile"
29
51
  >
30
52
  <input
31
- type="file"
32
53
  ref="file"
54
+ type="file"
33
55
  :accept="acceptedFormats"
56
+ style="display: none"
34
57
  @change="handleFileChange"
35
- style="display: none;"
36
58
  />
37
- </unnnic-button>
59
+ </UnnnicButton>
38
60
 
39
- <unnnic-button
61
+ <UnnnicIcon
40
62
  v-if="canDelete"
41
63
  class="unnnic-import-card__buttons__delete"
42
- size="small"
43
- :icon-center="`close-1`"
44
- type="primary"
64
+ size="sm"
65
+ scheme="neutral-cloudy"
66
+ icon="delete"
67
+ clickable
45
68
  @click="emitDeletion"
46
69
  />
47
70
  </div>
@@ -49,12 +72,14 @@
49
72
  </template>
50
73
 
51
74
  <script>
52
- import unnnicButton from '../Button/Button.vue';
75
+ import UnnnicButton from '../Button/Button.vue';
76
+ import UnnnicIcon from '../Icon.vue';
53
77
 
54
78
  export default {
55
79
  name: 'ImportCard',
56
80
  components: {
57
- unnnicButton,
81
+ UnnnicButton,
82
+ UnnnicIcon,
58
83
  },
59
84
  props: {
60
85
  title: {
@@ -90,6 +115,22 @@ export default {
90
115
  default: 'upload-bottom-1',
91
116
  },
92
117
  },
118
+ computed: {
119
+ fileIcon() {
120
+ const extension = this.title.slice(this.title.lastIndexOf('.') + 1);
121
+
122
+ return (
123
+ {
124
+ pdf: 'picture_as_pdf',
125
+ txt: 'text_snippet',
126
+ xls: 'table',
127
+ xlsx: 'table',
128
+ doc: 'draft',
129
+ docx: 'draft',
130
+ }[extension] || 'draft'
131
+ );
132
+ },
133
+ },
93
134
  methods: {
94
135
  importFile() {
95
136
  this.$refs.file.click();
@@ -118,15 +159,23 @@ export default {
118
159
 
119
160
  .unnnic-import-card {
120
161
  display: flex;
162
+ column-gap: $unnnic-spacing-xs;
163
+ align-items: center;
121
164
 
122
165
  border: $unnnic-border-width-thinner solid $unnnic-color-neutral-soft;
123
- border-radius: $unnnic-border-radius-md;
166
+ border-radius: $unnnic-border-radius-sm;
124
167
 
125
- background-color: $unnnic-color-background-snow;
126
- padding: $unnnic-squish-md;
168
+ background-color: $unnnic-color-neutral-white;
169
+ padding: $unnnic-spacing-xs - $unnnic-border-width-thinner;
170
+
171
+ &__file-icon {
172
+ user-select: none;
173
+ margin: $unnnic-spacing-xs;
174
+ }
127
175
 
128
176
  &__data {
129
177
  align-self: center;
178
+ overflow: hidden;
130
179
 
131
180
  flex: 1;
132
181
  gap: $unnnic-spacing-stack-nano;
@@ -134,10 +183,14 @@ export default {
134
183
  font-family: $unnnic-font-family-secondary;
135
184
 
136
185
  &__title {
137
- color: $unnnic-color-neutral-darkest;
186
+ color: $unnnic-color-neutral-dark;
138
187
  font-size: $unnnic-font-size-body-gt;
139
188
  line-height: $unnnic-font-size-body-gt + $unnnic-line-height-md;
140
- font-weight: $unnnic-font-weight-bold;
189
+ font-weight: $unnnic-font-weight-regular;
190
+
191
+ white-space: nowrap;
192
+ text-overflow: ellipsis;
193
+ overflow: hidden;
141
194
  }
142
195
 
143
196
  &__subtitle {
@@ -166,15 +219,19 @@ export default {
166
219
  &__buttons {
167
220
  display: flex;
168
221
  gap: $unnnic-spacing-inline-xs;
222
+ align-items: center;
169
223
 
170
- margin-left: $unnnic-spacing-inline-xs;
224
+ margin-right: $unnnic-spacing-xs;
171
225
 
172
226
  align-self: center;
173
227
 
174
228
  &__import {
175
- background-color: rgba($unnnic-color-brand-weni, $unnnic-opacity-level-light);
229
+ background-color: rgba(
230
+ $unnnic-color-brand-weni,
231
+ $unnnic-opacity-level-light
232
+ );
176
233
 
177
- ::v-deep .unnnic-icon {
234
+ :deep(.unnnic-icon) {
178
235
  svg {
179
236
  & .primary {
180
237
  fill: $unnnic-color-brand-weni;
@@ -184,15 +241,7 @@ export default {
184
241
  }
185
242
 
186
243
  &__delete {
187
- background-color: rgba($unnnic-color-feedback-red, $unnnic-opacity-level-light);
188
-
189
- ::v-deep .unnnic-icon {
190
- svg {
191
- & .primary {
192
- fill: $unnnic-color-feedback-red;
193
- }
194
- }
195
- }
244
+ user-select: none;
196
245
  }
197
246
  }
198
247
  }
@@ -123,10 +123,8 @@ export default {
123
123
  window.removeEventListener('resize', this.handleResize);
124
124
  },
125
125
  mounted() {
126
- const fallbackLabelWidth = 32 + this.sliderVal.toString().length * 4.5;
127
- this.sliderWidth = this.$refs.input.clientWidth;
128
- this.labelWidth = this.$refs.tooltip.$refs.label.clientWidth || fallbackLabelWidth;
129
- this.tooltipOffset = this.getNewTooltipPosition();
126
+ this.checkInputWidth();
127
+ this.checkTooltipLabelWidth();
130
128
  },
131
129
  watch: {
132
130
  sliderVal: {
@@ -146,8 +144,13 @@ export default {
146
144
  },
147
145
  methods: {
148
146
  configureTooltip() {
149
- this.sliderWidth = this.$refs.input.clientWidth;
150
- this.labelWidth = this.$refs.tooltip.$refs.label.clientWidth;
147
+ if (this.$refs.input) {
148
+ this.sliderWidth = this.$refs.input.clientWidth;
149
+ }
150
+ const tooltipLabel = this.$refs.tooltip?.$refs.label;
151
+ if (tooltipLabel) {
152
+ this.labelWidth = tooltipLabel.clientWidth || 32;
153
+ }
151
154
  this.tooltipOffset = this.getNewTooltipPosition();
152
155
  },
153
156
  handleResize() {
@@ -162,9 +165,40 @@ export default {
162
165
 
163
166
  this.handleValueChange();
164
167
  },
168
+ checkInputWidth() {
169
+ const intervalId = setInterval(() => {
170
+ const inputElement = this.$refs.input;
171
+ if (inputElement) {
172
+ const { offsetWidth } = inputElement;
173
+ if (offsetWidth > 0) {
174
+ this.sliderWidth = offsetWidth;
175
+ this.configureTooltip();
176
+ clearInterval(intervalId);
177
+ }
178
+ }
179
+ }, 100);
180
+ },
181
+ checkTooltipLabelWidth() {
182
+ const intervalId = setInterval(() => {
183
+ const tooltipLabel = this.$refs.tooltip?.$refs.label;
184
+ if (!tooltipLabel) {
185
+ return;
186
+ }
187
+
188
+ const { clientWidth } = tooltipLabel;
189
+ if (clientWidth > 0) {
190
+ this.labelWidth = clientWidth;
191
+ this.configureTooltip();
192
+ clearInterval(intervalId);
193
+ }
194
+ }, 100);
195
+ },
165
196
  getNewTooltipPosition() {
166
- const haldThumbWidth = 12 / 2;
167
- const halfLabelWidth = (this.labelWidth === 0 ? 32 : this.labelWidth) / 2;
197
+ if (this.sliderWidth === 0 || this.labelWidth === 0) {
198
+ return 0;
199
+ }
200
+ const halfThumbWidth = 12 / 2;
201
+ const halfLabelWidth = this.labelWidth / 2 || 16;
168
202
  const centerPosition = this.sliderWidth / 2;
169
203
 
170
204
  let percentOfRange = (this.sliderVal - this.minValue) / (this.maxValue - this.minValue);
@@ -175,7 +209,7 @@ export default {
175
209
  const valuePXPosition = percentOfRange * this.sliderWidth;
176
210
  const distFromCenter = valuePXPosition - centerPosition;
177
211
  const percentDistFromCenter = distFromCenter / centerPosition;
178
- const offset = percentDistFromCenter * haldThumbWidth;
212
+ const offset = percentDistFromCenter * halfThumbWidth;
179
213
 
180
214
  const finalLabelPosition = valuePXPosition - halfLabelWidth - offset;
181
215
  return finalLabelPosition;