@weni/unnnic-system 1.16.26 → 1.16.30-develop.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.16.26",
3
+ "version": "1.16.30-develop.0",
4
4
  "main": "./dist/unnnic.common.js",
5
5
  "files": [
6
6
  "dist/*",
@@ -0,0 +1,609 @@
1
+ <template>
2
+ <div @keydown="onKeyDownSelect" v-click-outside="onClickOutside" class="unnnic-select-smart">
3
+ <dropdown-skeleton type="manual" :value="active" position="bottom" ref="dropdown-skeleton">
4
+ <text-input
5
+ class="unnnic-select-smart__input"
6
+ ref="selectSmartInput"
7
+ :value="inputValue"
8
+ :placeholder="autocompletePlaceholder || selectedLabel"
9
+ :type="type"
10
+ :size="size"
11
+ :disabled="disabled"
12
+ :readonly="!isAutocompleteAllowed"
13
+ :icon-left="isAutocompleteAllowed && autocompleteIconLeft ? 'search-1' : ''"
14
+ :icon-right="active ? 'arrow-button-up-1' : 'arrow-button-down-1'"
15
+ :icon-right-clickable="!disabled"
16
+ @icon-right-click="handleClickInput"
17
+ @click="handleClickInput"
18
+ @input="searchValue = $event"
19
+ />
20
+
21
+ <template v-slot:inside="props">
22
+ <div
23
+ v-if="active"
24
+ :style="{ width: props.width }"
25
+ :class="{
26
+ 'unnnic-select-smart__options': true,
27
+ active: active,
28
+ inactive: !active,
29
+ }"
30
+ >
31
+ <div :style="{ overflow: 'auto' }">
32
+ <select-smart-multiple-header
33
+ v-if="multiple"
34
+ :selectedOptions="selectedOptions"
35
+ :withoutSelectsMessage="multipleWithoutSelectsMessage"
36
+ @clear-selected-options="clearSelectedOptions"
37
+ @unselect-option="unselectOption"
38
+ />
39
+ <div
40
+ ref="selectSmartOptionsScrollArea"
41
+ :class="[
42
+ 'unnnic-select-smart__options__scroll-area',
43
+ `size-${size}`,
44
+ {
45
+ 'with-descriptions': hasDescriptionOptions,
46
+ },
47
+ ]"
48
+ >
49
+ <select-smart-option
50
+ v-for="(option, index) in filterOptions(options)"
51
+ :key="option.value"
52
+ :label="option.label"
53
+ :description="option.description"
54
+ :tabindex="index"
55
+ :size="size"
56
+ :active="option.value === value || optionIsSelected(option)"
57
+ :focused="focusedOption && focusedOption.value === option.value"
58
+ :allowCheckbox="!!multiple"
59
+ @click="handleSelect(option)"
60
+ />
61
+ <p
62
+ v-if="filterOptions(options).length === 0"
63
+ class="unnnic-select-smart__options--no-results"
64
+ >
65
+ {{ $t('select_smart.without_results') }}
66
+ </p>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </template>
71
+ </dropdown-skeleton>
72
+ </div>
73
+ </template>
74
+
75
+ <script>
76
+ import vClickOutside from 'v-click-outside';
77
+ import SelectSmartOption from './SelectSmartOption.vue';
78
+ import SelectSmartMultipleHeader from './SelectSmartMultipleHeader.vue';
79
+ import TextInput from '../Input/TextInput.vue';
80
+ import DropdownSkeleton from '../Dropdown/DropdownSkeleton.vue';
81
+
82
+ export default {
83
+ name: 'UnnnicSelectSmart',
84
+ components: {
85
+ TextInput,
86
+ SelectSmartOption,
87
+ SelectSmartMultipleHeader,
88
+ DropdownSkeleton,
89
+ },
90
+ props: {
91
+ options: {
92
+ type: Array,
93
+ required: true,
94
+ default: () => [
95
+ {
96
+ value: '',
97
+ label: 'Initial value',
98
+ },
99
+ {
100
+ value: 'option1',
101
+ label: 'Option1',
102
+ },
103
+ ],
104
+ },
105
+ value: {
106
+ type: null,
107
+ },
108
+ size: {
109
+ type: String,
110
+ default: 'md',
111
+ },
112
+ type: {
113
+ type: String,
114
+ default: 'normal',
115
+ validator(value) {
116
+ return ['normal', 'error'].indexOf(value) !== -1;
117
+ },
118
+ },
119
+ disabled: {
120
+ type: Boolean,
121
+ default: false,
122
+ },
123
+ multiple: {
124
+ type: Boolean,
125
+ default: false,
126
+ },
127
+ multipleWithoutSelectsMessage: {
128
+ type: String,
129
+ default: '',
130
+ },
131
+ autocomplete: {
132
+ type: Boolean,
133
+ default: false,
134
+ },
135
+ autocompleteIconLeft: {
136
+ type: Boolean,
137
+ default: false,
138
+ },
139
+ autocompleteClearOnFocus: {
140
+ type: Boolean,
141
+ default: false,
142
+ },
143
+ },
144
+
145
+ data() {
146
+ return {
147
+ active: false,
148
+ status: 'not-mounted',
149
+ focusedOption: null,
150
+
151
+ searchValue: '',
152
+ isAutocompleteAllowed: false,
153
+
154
+ selectedOptions: [],
155
+ multipleSelectedsTags: 2,
156
+ };
157
+ },
158
+
159
+ mounted() {
160
+ this.status = 'mounted';
161
+
162
+ if (this.multiple || this.autocomplete) {
163
+ // The "multiple" variation only exists with autocomplete, so it can't be false
164
+ this.isAutocompleteAllowed = true;
165
+ }
166
+
167
+ if (this.options[0].value) {
168
+ this.selectOption(this.options[0]);
169
+ }
170
+ },
171
+
172
+ watch: {
173
+ active(newValue) {
174
+ this.$nextTick(() => {
175
+ this.$refs['dropdown-skeleton'].calculatePosition();
176
+
177
+ if (newValue && !this.multiple) {
178
+ const activeOptionIndex = this.getOptionIndex('active');
179
+
180
+ if (activeOptionIndex !== -1) {
181
+ this.scrollToOption(activeOptionIndex, 'center');
182
+ }
183
+ }
184
+ });
185
+ },
186
+
187
+ searchValue() {
188
+ this.$nextTick(() => {
189
+ this.$refs['dropdown-skeleton'].calculatePosition();
190
+ });
191
+
192
+ this.focusedOption = null;
193
+
194
+ if (!this.active) this.active = true;
195
+ },
196
+
197
+ selectedOptions(newSelectedOptions) {
198
+ this.$emit('onChange', newSelectedOptions);
199
+ this.$emit('input', newSelectedOptions);
200
+
201
+ this.onSelectOption(newSelectedOptions.at(-1));
202
+ },
203
+
204
+ autocomplete(newAutocomplete) {
205
+ if (!this.multiple) {
206
+ // The "multiple" variation only exists with autocomplete, so it can't be false
207
+ this.isAutocompleteAllowed = newAutocomplete;
208
+ }
209
+ },
210
+ },
211
+
212
+ computed: {
213
+ selectedLabel() {
214
+ if (this.status !== 'mounted') {
215
+ return '';
216
+ }
217
+
218
+ const selected = this.options.find((option) => {
219
+ const isValueMatch = option.value === this.value;
220
+
221
+ if (this.isAutocompleteAllowed) {
222
+ return isValueMatch && option.value !== '';
223
+ }
224
+
225
+ const isEmptyOption = option.value === '' && this.value == null;
226
+ return isEmptyOption || isValueMatch;
227
+ });
228
+
229
+ if (selected) {
230
+ return selected.label;
231
+ }
232
+
233
+ if (this.multiple) {
234
+ const labels = this.selectedOptions.map((item) => item.label);
235
+ return labels.join(', ');
236
+ }
237
+
238
+ const label = this.selectedOptions.at(-1)?.label || '';
239
+ return label;
240
+ },
241
+
242
+ hasDescriptionOptions() {
243
+ return this.options.some((item) => typeof item.description !== 'undefined');
244
+ },
245
+
246
+ autocompletePlaceholder() {
247
+ if (this.isAutocompleteAllowed) {
248
+ const selected = this.options.find((option) => option.value === '');
249
+
250
+ if (selected) {
251
+ return selected.label;
252
+ }
253
+ }
254
+ return '';
255
+ },
256
+
257
+ inputValue() {
258
+ const {
259
+ isAutocompleteAllowed, searchValue, multiple, selectedOptions,
260
+ } = this;
261
+
262
+ if (isAutocompleteAllowed || multiple) {
263
+ return searchValue;
264
+ }
265
+ if (!multiple && selectedOptions.length !== 0) {
266
+ return selectedOptions[0].label;
267
+ }
268
+
269
+ return '';
270
+ },
271
+ },
272
+
273
+ directives: {
274
+ clickOutside: vClickOutside.directive,
275
+ },
276
+
277
+ methods: {
278
+ optionIsSelected(option) {
279
+ return this.selectedOptions.some((selectedOption) => selectedOption.value === option.value);
280
+ },
281
+
282
+ clearSelectedOptions() {
283
+ this.selectedOptions = [];
284
+ },
285
+
286
+ handleSelect(option) {
287
+ if (option) {
288
+ if (this.multiple && this.optionIsSelected(option)) {
289
+ this.unselectOption(option);
290
+ return;
291
+ }
292
+
293
+ this.selectOption(option);
294
+ }
295
+ },
296
+
297
+ handleClickInput() {
298
+ if (this.isAutocompleteAllowed) {
299
+ if (this.active) {
300
+ return;
301
+ }
302
+ if (this.autocompleteClearOnFocus || this.multiple) {
303
+ this.searchValue = '';
304
+ }
305
+ }
306
+
307
+ this.active = !this.active;
308
+ },
309
+
310
+ filterOptions(options) {
311
+ const searchValue = this.searchValue.toLowerCase();
312
+ const searchTerms = searchValue
313
+ .normalize('NFD')
314
+ .replace(/[\u0300-\u036f]/g, '')
315
+ .split(' ');
316
+
317
+ const normalizeLabel = (label) => label
318
+ .toString()
319
+ .toLowerCase()
320
+ .normalize('NFD')
321
+ .replace(/[\u0300-\u036f]/g, '');
322
+
323
+ const getNumber = (str) => parseInt(str.match(/\d+/)?.[0], 10) || 0;
324
+
325
+ const filteredOptions = options.filter(({ value, label }, index, self) => {
326
+ const labelWithoutAccents = normalizeLabel(label);
327
+ const isValueUnique = self.findIndex((option) => option.value === value) === index;
328
+ const matchesSearchTerms = searchTerms.every((term) => labelWithoutAccents.includes(term));
329
+
330
+ return isValueUnique && matchesSearchTerms && value;
331
+ });
332
+
333
+ const sortedOptions = filteredOptions.sort((a, b) => {
334
+ const labelA = normalizeLabel(a.label);
335
+ const labelB = normalizeLabel(b.label);
336
+
337
+ const numberA = getNumber(labelA);
338
+ const numberB = getNumber(labelB);
339
+
340
+ return numberA - numberB || labelA.localeCompare(labelB);
341
+ });
342
+
343
+ return sortedOptions;
344
+ },
345
+
346
+ onClickOutside() {
347
+ if (this.active) {
348
+ if (this.isAutocompleteAllowed) {
349
+ this.searchValue = this.selectedLabel;
350
+ }
351
+ this.$nextTick(() => {
352
+ this.active = false;
353
+ });
354
+ }
355
+ },
356
+
357
+ getOptionIndex(type) {
358
+ const options = this.filterOptions(this.options);
359
+ let valueByType = '';
360
+ if (type === 'active') {
361
+ valueByType = this.value?.[0]?.value;
362
+ }
363
+ if (type === 'focused') {
364
+ valueByType = this.focusedOption?.value || this.selectedOptions.at(-1)?.value;
365
+ }
366
+ return options.findIndex((option) => option.value === valueByType);
367
+ },
368
+
369
+ scrollToOption(optionIndex, scrollBlock = 'nearest') {
370
+ const elementScroll = this.$refs.selectSmartOptionsScrollArea;
371
+
372
+ if (elementScroll && optionIndex >= 0 && optionIndex < elementScroll.childNodes.length) {
373
+ const optionElement = elementScroll.childNodes[optionIndex];
374
+
375
+ if (optionElement instanceof HTMLElement) {
376
+ optionElement.scrollIntoView({ block: scrollBlock });
377
+ }
378
+ }
379
+ },
380
+
381
+ selectOption(option) {
382
+ const selectedOption = option.value === null || option.value.length === 0 ? null : option;
383
+
384
+ this.selectedOptions = this.multiple
385
+ ? [...this.selectedOptions, selectedOption]
386
+ : [selectedOption];
387
+ },
388
+
389
+ unselectOption(option) {
390
+ const indexToRemove = this.selectedOptions.findIndex(
391
+ (selectedOption) => selectedOption === option,
392
+ );
393
+
394
+ if (indexToRemove !== -1) {
395
+ this.selectedOptions.splice(indexToRemove, 1);
396
+ }
397
+
398
+ if (this.multiple) {
399
+ this.searchValue = '';
400
+ }
401
+ },
402
+
403
+ onSelectOption(newOption) {
404
+ this.$nextTick(() => {
405
+ if (!this.multiple) {
406
+ this.active = false;
407
+ }
408
+ });
409
+
410
+ if (this.isAutocompleteAllowed && !this.multiple) {
411
+ this.searchValue = newOption.label;
412
+ return;
413
+ }
414
+
415
+ if (this.multiple) {
416
+ this.searchValue = '';
417
+ }
418
+ },
419
+
420
+ async onKeyDownSelect(event) {
421
+ const { key } = event;
422
+
423
+ const validKeys = ['Escape', 'Enter', 'ArrowUp', 'ArrowDown'];
424
+
425
+ if (validKeys.includes(key)) {
426
+ event.preventDefault();
427
+
428
+ const options = this.filterOptions(this.options);
429
+ const focusedOptionIndex = this.getOptionIndex('focused');
430
+ let newIndex;
431
+
432
+ // eslint-disable-next-line default-case
433
+ switch (key) {
434
+ case 'Escape':
435
+ this.active = false;
436
+ this.searchValue = '';
437
+ break;
438
+ case 'Enter':
439
+ if (this.focusedOption && this.active) {
440
+ this.handleSelect(this.focusedOption);
441
+ }
442
+ if (!this.active) {
443
+ this.active = true;
444
+ }
445
+ break;
446
+
447
+ case 'ArrowUp':
448
+ case 'ArrowDown':
449
+ if (this.multiple && !this.active) {
450
+ this.active = true;
451
+ await this.$nextTick();
452
+ }
453
+ newIndex = key === 'ArrowUp'
454
+ ? Math.max(focusedOptionIndex - 1, 0)
455
+ : Math.min(focusedOptionIndex + 1, options.length - 1);
456
+ if (!this.active) {
457
+ this.handleSelect(options[newIndex]);
458
+ }
459
+ break;
460
+ }
461
+
462
+ if (newIndex !== undefined && this.active) {
463
+ this.scrollToOption(newIndex);
464
+ }
465
+
466
+ const newOption = options[newIndex === undefined ? focusedOptionIndex : newIndex];
467
+ this.focusedOption = newOption;
468
+ }
469
+ },
470
+ },
471
+ };
472
+ </script>
473
+
474
+ <style lang="scss">
475
+ @import '../../assets/scss/unnnic.scss';
476
+ .unnnic-select-smart {
477
+ position: relative;
478
+
479
+ font-family: $unnnic-font-family-secondary;
480
+
481
+ cursor: pointer;
482
+
483
+ &__options {
484
+ left: 0;
485
+ right: 0;
486
+
487
+ margin-top: 2px;
488
+
489
+ border-radius: $unnnic-border-radius-sm;
490
+
491
+ box-shadow: $unnnic-shadow-level-near;
492
+
493
+ background-color: $unnnic-color-background-snow;
494
+
495
+ cursor: default;
496
+
497
+ &__scroll-area {
498
+ @function calc-max-height($value) {
499
+ @return ($value * $unnnic-font-size) - ($unnnic-spacing-xs * 2);
500
+ }
501
+
502
+ margin: $unnnic-spacing-xs;
503
+ margin-right: $unnnic-inline-xs;
504
+ padding-right: $unnnic-inline-xs;
505
+
506
+ max-height: calc-max-height(8.5);
507
+
508
+ overflow-y: auto;
509
+
510
+ &::-webkit-scrollbar {
511
+ width: $unnnic-spacing-inline-nano;
512
+ }
513
+
514
+ &::-webkit-scrollbar-thumb {
515
+ background: $unnnic-color-neutral-cleanest;
516
+ border-radius: $unnnic-border-radius-pill;
517
+ }
518
+
519
+ &::-webkit-scrollbar-track {
520
+ background: $unnnic-color-neutral-soft;
521
+ border-radius: $unnnic-border-radius-pill;
522
+ }
523
+
524
+ &.with-descriptions {
525
+ max-height: calc-max-height(13.5);
526
+ }
527
+
528
+ &.size-sm {
529
+ max-height: calc-max-height(8);
530
+
531
+ &.with-descriptions {
532
+ max-height: calc-max-height(12);
533
+ }
534
+ }
535
+ }
536
+
537
+ &--no-results {
538
+ margin: 0;
539
+
540
+ color: $unnnic-color-neutral-cleanest;
541
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-medium;
542
+ font-size: $unnnic-font-size-body-md;
543
+
544
+ padding: $unnnic-spacing-nano $unnnic-spacing-ant;
545
+ }
546
+
547
+ &.inactive {
548
+ display: none;
549
+ }
550
+
551
+ &.active {
552
+ display: block;
553
+ z-index: 2;
554
+ }
555
+ }
556
+
557
+ .unnnic-select-smart__input input {
558
+ // entire class name to have higher priority in styles
559
+ &:read-only {
560
+ cursor: pointer;
561
+
562
+ &::placeholder {
563
+ color: $unnnic-color-neutral-dark;
564
+ }
565
+ }
566
+
567
+ &:not(:read-only :focus) {
568
+ &::placeholder {
569
+ color: $unnnic-color-neutral-dark;
570
+ }
571
+ }
572
+
573
+ &:not(:read-only) {
574
+ &:focus {
575
+ &::placeholder {
576
+ color: $unnnic-color-neutral-cleanest;
577
+ }
578
+ }
579
+ }
580
+
581
+ &:disabled {
582
+ border: 1px solid $unnnic-color-neutral-cleanest;
583
+
584
+ cursor: not-allowed;
585
+
586
+ &::placeholder {
587
+ color: $unnnic-color-neutral-cleanest;
588
+ }
589
+
590
+ + .icon-right {
591
+ cursor: not-allowed;
592
+ }
593
+ }
594
+ }
595
+ }
596
+
597
+ ::-webkit-scrollbar {
598
+ width: 4px;
599
+ }
600
+
601
+ ::-webkit-scrollbar-track {
602
+ background: $unnnic-color-neutral-soft;
603
+ }
604
+
605
+ ::-webkit-scrollbar-thumb {
606
+ background: $unnnic-color-neutral-cleanest;
607
+ border-radius: $unnnic-border-radius-pill;
608
+ }
609
+ </style>
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <div class="unnnic-select-smart__options__multiple">
3
+ <div
4
+ v-if="selectedOptions[0]"
5
+ class="unnnic-select-smart__options__multiple__selecteds__container"
6
+ >
7
+ <div class="unnnic-select-smart__options__multiple__selecteds">
8
+ <tag
9
+ v-for="option in firstMultipleSelecteds"
10
+ class="unnnic-select-smart__options__multiple__selecteds__option"
11
+ :key="option.value"
12
+ :text="option.label"
13
+ hasCloseIcon
14
+ @close="unselectOption(option)"
15
+ />
16
+ <p
17
+ v-if="selectedOptions.length > multipleSelectedsTags"
18
+ class="unnnic-select-smart__options__multiple__selecteds__remaining"
19
+ >
20
+ +{{ selectedOptions.length - multipleSelectedsTags }}
21
+ </p>
22
+ </div>
23
+ <icon-svg
24
+ class="unnnic-select-smart__options__multiple__selecteds__clear"
25
+ icon="close-1"
26
+ size="xs"
27
+ clickable
28
+ @click="clearSelectedOptions"
29
+ />
30
+ </div>
31
+ <p v-if="!selectedOptions[0]" class="unnnic-select-smart__options__multiple--without-multiples">
32
+ {{ withoutSelectsMessage || $t('select_smart.without_multiple_selected') }}
33
+ </p>
34
+ </div>
35
+ </template>
36
+
37
+ <script>
38
+ import Tag from '../Tag/Tag.vue';
39
+ import IconSvg from '../Icon.vue';
40
+
41
+ export default {
42
+ name: 'UnnnicSelectSmartMultipleHeader',
43
+ components: {
44
+ Tag,
45
+ IconSvg,
46
+ },
47
+ props: {
48
+ selectedOptions: {
49
+ type: Array,
50
+ required: true,
51
+ },
52
+ withoutSelectsMessage: {
53
+ type: String,
54
+ default: '',
55
+ },
56
+ },
57
+ data() {
58
+ return {
59
+ multipleSelectedsTags: 2,
60
+ };
61
+ },
62
+ computed: {
63
+ firstMultipleSelecteds() {
64
+ const { selectedOptions, multipleSelectedsTags } = this;
65
+ const selectedArray = [];
66
+
67
+ for (let i = 0; i < multipleSelectedsTags; i += 1) {
68
+ selectedArray.push(selectedOptions?.[i]);
69
+ }
70
+
71
+ return selectedArray.filter((option) => option !== undefined);
72
+ },
73
+ },
74
+ methods: {
75
+ clearSelectedOptions() {
76
+ this.$emit('clear-selected-options');
77
+ },
78
+ unselectOption(option) {
79
+ this.$emit('unselect-option', option);
80
+ },
81
+ },
82
+ };
83
+ </script>
84
+
85
+ <style lang="scss" scoped>
86
+ @import '../../assets/scss/unnnic.scss';
87
+
88
+ .unnnic-select-smart__options__multiple {
89
+ border-bottom: 1px solid $unnnic-color-neutral-soft;
90
+
91
+ &__selecteds {
92
+ display: flex;
93
+
94
+ color: $unnnic-color-neutral-dark;
95
+
96
+ &__container {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ }
101
+
102
+ &__option {
103
+ margin: $unnnic-spacing-xs;
104
+ margin-right: 0;
105
+
106
+ &.unnnic-tag {
107
+ outline-color: $unnnic-color-neutral-light;
108
+ background-color: $unnnic-color-neutral-light;
109
+
110
+ color: $unnnic-color-neutral-dark;
111
+ }
112
+ }
113
+
114
+ &__remaining {
115
+ margin-left: $unnnic-spacing-xs;
116
+
117
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-small;
118
+ font-size: $unnnic-font-size-body-gt;
119
+ }
120
+
121
+ &__clear {
122
+ margin-right: $unnnic-spacing-sm;
123
+ }
124
+ }
125
+
126
+ &--without-multiples {
127
+ margin: 0;
128
+
129
+ color: $unnnic-color-neutral-cleanest;
130
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-medium;
131
+ font-size: $unnnic-font-size-body-md;
132
+
133
+ padding: $unnnic-spacing-ant;
134
+ }
135
+ }
136
+ </style>
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <div
3
+ @click="$emit('click')"
4
+ :class="{
5
+ 'unnnic-select-smart-option': true,
6
+ 'unnnic-select-smart-option--focused': focused,
7
+ 'unnnic-select-smart-option--active': active,
8
+ 'unnnic--clickable': selectable,
9
+ 'unnnic-select-smart-option--selectable': selectable,
10
+ 'unnnic-select-smart-option--with-checkbox': allowCheckbox,
11
+ }"
12
+ :title="label"
13
+ >
14
+ <unnnic-checkbox v-if="allowCheckbox" ref="checkbox" :value="active" :size="size" />
15
+ <div>
16
+ <span
17
+ :class="['unnnic-select-smart-option__label', `unnnic-select-smart-option__label--${size}`]"
18
+ >{{ label }}</span
19
+ >
20
+ <p
21
+ v-if="description"
22
+ :class="[
23
+ 'unnnic-select-smart-option__description',
24
+ `unnnic-select-smart-option__description--${size}`,
25
+ ]"
26
+ >
27
+ {{ description }}
28
+ </p>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script>
34
+ import UnnnicCheckbox from '../Checkbox/Checkbox.vue';
35
+
36
+ export default {
37
+ name: 'UnnnicSelectSmartOption',
38
+ components: { UnnnicCheckbox },
39
+ props: {
40
+ label: {
41
+ type: String,
42
+ default: 'Option',
43
+ required: true,
44
+ },
45
+ description: {
46
+ type: String,
47
+ default: '',
48
+ required: false,
49
+ },
50
+ size: {
51
+ type: String,
52
+ default: '',
53
+ },
54
+ selectable: {
55
+ type: Boolean,
56
+ default: true,
57
+ },
58
+ focused: {
59
+ type: Boolean,
60
+ default: false,
61
+ },
62
+ active: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
66
+ allowCheckbox: {
67
+ type: Boolean,
68
+ default: false,
69
+ },
70
+ },
71
+ };
72
+ </script>
73
+
74
+ <style lang="scss">
75
+ @import '../../assets/scss/unnnic.scss';
76
+ .unnnic-select-smart-option {
77
+ background-color: $unnnic-color-background-snow;
78
+ padding: $unnnic-spacing-stack-nano $unnnic-inline-xs;
79
+
80
+ max-width: 100%;
81
+
82
+ font-family: $unnnic-font-family-secondary;
83
+ color: $unnnic-color-neutral-dark;
84
+ font-weight: $unnnic-font-weight-regular;
85
+
86
+ white-space: nowrap;
87
+ text-overflow: ellipsis;
88
+ overflow: hidden;
89
+ -webkit-line-clamp: 1;
90
+
91
+ &--selectable:hover,
92
+ &--focused,
93
+ &--active {
94
+ border-radius: $unnnic-border-radius-sm;
95
+
96
+ background-color: $unnnic-color-neutral-light;
97
+ }
98
+
99
+ &--active:not(&--with-checkbox) {
100
+ color: $unnnic-color-weni-600;
101
+ font-weight: $unnnic-font-weight-bold;
102
+ }
103
+
104
+ &--with-checkbox {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: $unnnic-spacing-xs;
108
+
109
+ .unnnic-checkbox rect {
110
+ rx: $unnnic-border-radius-md;
111
+ }
112
+ }
113
+
114
+ &__label {
115
+ &--md {
116
+ font-size: $unnnic-font-size-body-gt;
117
+ line-height: $unnnic-font-size-body-gt + $unnnic-line-height-medium;
118
+ }
119
+ &--sm {
120
+ font-size: $unnnic-font-size-body-md;
121
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-small;
122
+ }
123
+ }
124
+
125
+ &__description {
126
+ margin: 0;
127
+
128
+ color: $unnnic-color-neutral-cloudy;
129
+ font-weight: $unnnic-font-weight-regular;
130
+
131
+ &--md {
132
+ font-size: $unnnic-font-size-body-md;
133
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-medium;
134
+ }
135
+ &--sm {
136
+ font-size: $unnnic-font-size-body-sm;
137
+ line-height: $unnnic-font-size-body-md + $unnnic-line-height-small;
138
+ }
139
+ }
140
+ }
141
+ </style>
@@ -11,5 +11,9 @@
11
11
  },
12
12
  "import_card": {
13
13
  "importing": "Importing"
14
+ },
15
+ "select_smart": {
16
+ "without_results": "No results found",
17
+ "without_multiple_selected": "No option selected"
14
18
  }
15
19
  }
@@ -11,5 +11,9 @@
11
11
  },
12
12
  "import_card": {
13
13
  "importing": "Importando"
14
+ },
15
+ "select_smart": {
16
+ "without_results": "Ningún resultado encontrado",
17
+ "without_multiple_selected": "Ninguna opción seleccionada"
14
18
  }
15
19
  }
@@ -11,5 +11,9 @@
11
11
  },
12
12
  "import_card": {
13
13
  "importing": "Importando"
14
+ },
15
+ "select_smart": {
16
+ "without_results": "Nenhum resultado encontrado",
17
+ "without_multiple_selected": "Nenhuma opção selecionada"
14
18
  }
15
19
  }
@@ -0,0 +1,133 @@
1
+ import unnnicSelectSmart from '../components/SelectSmart/SelectSmart.vue';
2
+
3
+ export default {
4
+ title: 'select/SelectSmart',
5
+ component: unnnicSelectSmart,
6
+ argTypes: {
7
+ size: { control: { type: 'select', options: ['md', 'sm'] } },
8
+ type: { control: { type: 'select', options: ['normal', 'error'] } },
9
+ autocomplete: { control: { type: 'boolean' } },
10
+ autocompleteIconLeft: { control: { type: 'boolean' } },
11
+ autocompleteClearOnFocus: { control: { type: 'boolean' } },
12
+ },
13
+ };
14
+
15
+ const Template = (args, { argTypes }) => ({
16
+ props: Object.keys(argTypes),
17
+ components: { unnnicSelectSmart },
18
+ data() {
19
+ return {
20
+ exampleValue: '',
21
+ };
22
+ },
23
+
24
+ methods: {
25
+ addDynamicOption() {
26
+ const value = Math.floor(Math.random() * 1e3);
27
+
28
+ this.exampleOptions.push({
29
+ value: `option${value}`,
30
+ label: `Option ${value}`,
31
+ });
32
+ },
33
+ },
34
+
35
+ template: `
36
+ <div>
37
+ <unnnic-select-smart
38
+ v-model="exampleValue"
39
+ :options="exampleOptions"
40
+ v-bind="$props"
41
+ />
42
+
43
+ <button v-if="!(disabled || autocomplete)" @click="addDynamicOption">Add dynamic option</button>
44
+
45
+ <p v-if="!disabled">v-model: {{exampleValue}}</p>
46
+ </div>
47
+ `,
48
+ });
49
+
50
+ const exampleOptionsDefault = [
51
+ { value: '', label: 'Select some option' },
52
+ { value: '1', label: 'Option 1' },
53
+ { value: '2', label: 'Option 2' },
54
+ { value: '3', label: 'Option 3' },
55
+ { value: '4', label: 'Option 4' },
56
+ { value: '5', label: 'Option 5' },
57
+ { value: '5', label: 'Option 5' },
58
+ ];
59
+
60
+ const exampleOptionsWithDescriptions = [
61
+ { value: '', label: 'Select some option' },
62
+ { value: '1', label: 'Option 1', description: 'This is the first option' },
63
+ { value: '2', label: 'Option 2', description: 'Another alternative you can consider' },
64
+ { value: '3', label: 'Option 3', description: 'A third option for your consideration' },
65
+ { value: '4', label: 'Option 4', description: 'Yet another choice among the options' },
66
+ { value: '5', label: 'Option 5', description: 'The last option available for selection' },
67
+ ];
68
+
69
+ const exampleOptionsCountries = [
70
+ { value: '', label: 'Select some option' },
71
+ { value: 'united_states', label: 'Estados Unidos' },
72
+ { value: 'brazil', label: 'Brasil' },
73
+ { value: 'china', label: 'China' },
74
+ { value: 'india', label: 'Índia' },
75
+ { value: 'russia', label: 'Rússia' },
76
+ { value: 'japan', label: 'Japão' },
77
+ { value: 'germany', label: 'Alemanha' },
78
+ { value: 'france', label: 'França' },
79
+ { value: 'canada', label: 'Canadá' },
80
+ { value: 'australia', label: 'Austrália' },
81
+ { value: 'south_korea', label: 'Coreia do Sul' },
82
+ { value: 'mexico', label: 'México' },
83
+ { value: 'egypt', label: 'Egito' },
84
+ { value: 'south_africa', label: 'África do Sul' },
85
+ { value: 'turkey', label: 'Turquia' },
86
+ { value: 'nigeria', label: 'Nigéria' },
87
+ { value: 'argentina', label: 'Argentina' },
88
+ { value: 'italy', label: 'Itália' },
89
+ { value: 'spain', label: 'Espanha' },
90
+ { value: 'saudi_arabia', label: 'Arábia Saudita' },
91
+ ];
92
+
93
+ const exampleOptionsFirstSelected = [
94
+ { value: '1', label: 'Option 1' },
95
+ { value: '2', label: 'Option 2' },
96
+ { value: '3', label: 'Option 3' },
97
+ { value: '4', label: 'Option 4' },
98
+ { value: '5', label: 'Option 5' },
99
+ { value: '5', label: 'Option 5' },
100
+ ];
101
+
102
+ export const Default = Template.bind({});
103
+ Default.args = {
104
+ exampleOptions: exampleOptionsDefault,
105
+ };
106
+
107
+ export const FirstSelected = Template.bind({});
108
+ FirstSelected.args = {
109
+ exampleOptions: exampleOptionsFirstSelected,
110
+ };
111
+
112
+ export const Disabled = Template.bind({});
113
+ Disabled.args = {
114
+ disabled: true,
115
+ };
116
+
117
+ export const WithDescriptions = Template.bind({});
118
+ WithDescriptions.args = {
119
+ exampleOptions: exampleOptionsWithDescriptions,
120
+ };
121
+
122
+ export const Autocomplete = Template.bind({});
123
+ Autocomplete.args = {
124
+ exampleOptions: exampleOptionsCountries,
125
+ autocomplete: true,
126
+ };
127
+
128
+ export const Multiple = Template.bind({});
129
+ Multiple.args = {
130
+ exampleOptions: exampleOptionsCountries,
131
+ autocomplete: true,
132
+ multiple: true,
133
+ };