@mozaic-ds/vue 0.23.0 → 0.24.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.
@@ -0,0 +1,316 @@
1
+ <template>
2
+ <div
3
+ ref="dropdown"
4
+ v-click-outside="onClickOutside"
5
+ class="mc-dropdown"
6
+ :class="{ 'mc-dropdown--multi': multiple }"
7
+ :style="setStyles"
8
+ >
9
+ <MTag
10
+ v-if="multiple && listboxValue.length > 0"
11
+ :id="tagId ? tagId : `autoCompleteTag-${uuid}`"
12
+ ref="tag"
13
+ :label="setTagLabel"
14
+ :disabled="disabled"
15
+ type="removable"
16
+ class="mc-dropdown__tag"
17
+ size="s"
18
+ @remove-tag="clearAutocomplete()"
19
+ />
20
+ <button
21
+ type="button"
22
+ class="mc-select mc-dropdown__trigger"
23
+ :class="{ 'is-open': openState }"
24
+ @click="openState = !openState"
25
+ >
26
+ {{ buttonValue }}
27
+ </button>
28
+ <MListBox
29
+ v-model="listboxValue"
30
+ :open="openState"
31
+ :items="localItems"
32
+ :multiple="multiple"
33
+ :empty-search-label="emptySearchLabel"
34
+ :data-key-expr="dataKeyExpr"
35
+ :data-text-expr="dataTextExpr"
36
+ :data-value-expr="dataValueExpr"
37
+ @change="onChange"
38
+ >
39
+ <template #item="{ item }">
40
+ <slot name="item" :item="item"></slot>
41
+ </template>
42
+ </MListBox>
43
+ </div>
44
+ </template>
45
+
46
+ <script>
47
+ import MTag from '../tags/MTag.vue';
48
+ import MListBox from '../listbox/MListBox.vue';
49
+
50
+ export default {
51
+ name: 'MDropdown',
52
+
53
+ components: {
54
+ MTag,
55
+ MListBox,
56
+ },
57
+
58
+ directives: {
59
+ 'click-outside': {
60
+ bind(el, binding, vnode) {
61
+ el.clickOutsideEvent = (event) => {
62
+ if (!(el === event.target || el.contains(event.target))) {
63
+ vnode.context[binding.expression](event);
64
+ }
65
+ };
66
+ document.body.addEventListener('click', el.clickOutsideEvent);
67
+ },
68
+ unbind(el) {
69
+ document.body.removeEventListener('click', el.clickOutsideEvent);
70
+ },
71
+ },
72
+ },
73
+
74
+ model: {
75
+ event: 'change',
76
+ },
77
+
78
+ props: {
79
+ // Tag Element
80
+ tagId: {
81
+ type: String,
82
+ default: null,
83
+ },
84
+ tagLabel: {
85
+ type: String,
86
+ default: '',
87
+ },
88
+ // Input Element
89
+ placeholder: {
90
+ type: String,
91
+ default: '-- Placeholder --',
92
+ },
93
+ filter: {
94
+ type: Function,
95
+ default: null,
96
+ },
97
+ disabled: {
98
+ type: Boolean,
99
+ default: false,
100
+ },
101
+ // Listbox Element
102
+ items: {
103
+ type: Array,
104
+ required: true,
105
+ },
106
+ value: {
107
+ type: [Array, String, Number],
108
+ default: undefined,
109
+ },
110
+ open: {
111
+ type: Boolean,
112
+ default: false,
113
+ },
114
+ multiple: {
115
+ type: Boolean,
116
+ default: false,
117
+ },
118
+ emptySearchLabel: {
119
+ type: String,
120
+ default: 'No results found',
121
+ },
122
+ dataKeyExpr: {
123
+ type: String,
124
+ default: 'id',
125
+ },
126
+ dataTextExpr: {
127
+ type: String,
128
+ default: 'label',
129
+ },
130
+ dataValueExpr: {
131
+ type: String,
132
+ default: 'value',
133
+ },
134
+ sort: {
135
+ type: Boolean,
136
+ default: true,
137
+ },
138
+ // Global
139
+ maxWidth: {
140
+ type: String,
141
+ default: '17.875rem',
142
+ },
143
+ },
144
+
145
+ data() {
146
+ return {
147
+ uuid: Math.random(),
148
+ openState: this.open,
149
+ tagWidth: '0px',
150
+ tagValue: null,
151
+ buttonValue: this.placeholder,
152
+ localItems: null,
153
+ sortedListItems: null,
154
+ listboxValue: null,
155
+ };
156
+ },
157
+
158
+ computed: {
159
+ setTagLabel() {
160
+ return this.listboxValue.length.toString() + ' ' + this.tagLabel;
161
+ },
162
+ setStyles() {
163
+ return {
164
+ '--tag-width': this.tagWidth,
165
+ '--max-width': this.maxWidth,
166
+ };
167
+ },
168
+ },
169
+
170
+ watch: {
171
+ value: {
172
+ handler: function (val) {
173
+ if (!val && this.multiple) {
174
+ this.listboxValue = [];
175
+ } else {
176
+ this.listboxValue = val;
177
+ }
178
+ },
179
+ immediate: true,
180
+ },
181
+
182
+ items: {
183
+ handler: function (val) {
184
+ this.localItems = val;
185
+ // this.clearAutocomplete();
186
+ },
187
+ immediate: true,
188
+ },
189
+
190
+ listboxValue: function (val) {
191
+ const value = Array.isArray(val) ? val : val.toString();
192
+ const selectedItems = this.getSelectedItems(value);
193
+
194
+ const seletedLabels = selectedItems.map(
195
+ (item) => item[this.dataTextExpr]
196
+ );
197
+
198
+ this.buttonValue = seletedLabels.join(', ');
199
+
200
+ if (val.length === 0) {
201
+ this.buttonValue = this.placeholder;
202
+ }
203
+
204
+ if (this.multiple) {
205
+ this.tagValue = val;
206
+ }
207
+ },
208
+
209
+ tagValue: function () {
210
+ this.setTagWidth();
211
+ },
212
+
213
+ openState: function (val) {
214
+ const eventName = val ? 'open' : 'close';
215
+ this.$emit(eventName);
216
+ this.$emit('update:open', val);
217
+ },
218
+ },
219
+
220
+ methods: {
221
+ setTagWidth() {
222
+ this.$nextTick(() => {
223
+ if (this.$refs.tag && this.$refs.tag.$el) {
224
+ this.tagWidth = this.$refs.tag.$el.clientWidth + 8 + 'px';
225
+ } else {
226
+ this.tagWidth = '0px';
227
+ }
228
+ });
229
+ },
230
+
231
+ clearAutocomplete() {
232
+ this.listboxValue = this.multiple ? [] : undefined;
233
+ this.onChange();
234
+ this.$emit('clear');
235
+ },
236
+
237
+ onClickOutside() {
238
+ this.openState = false;
239
+
240
+ if (this.multiple && this.sort) {
241
+ this.sortItems();
242
+ } else {
243
+ this.localItems = this.items;
244
+ }
245
+ },
246
+
247
+ onChange() {
248
+ this.$emit('change', this.listboxValue);
249
+
250
+ if (!this.multiple) {
251
+ this.onClickOutside();
252
+ }
253
+ },
254
+
255
+ getSelectedItems(val) {
256
+ const value = val ? val : this.listboxValue;
257
+
258
+ const selectedItems = this.items.filter((item) =>
259
+ value.includes(item[this.dataValueExpr])
260
+ );
261
+
262
+ return selectedItems;
263
+ },
264
+
265
+ sortItems() {
266
+ this.sortedListItems = this.items;
267
+ const selectedItems = this.getSelectedItems();
268
+
269
+ this.sortedListItems.sort((a, b) => {
270
+ const hasItemA = selectedItems.includes(a);
271
+ const hasItemB = selectedItems.includes(b);
272
+
273
+ if (hasItemA === hasItemB) {
274
+ return a[this.dataValueExpr] - b[this.dataValueExpr];
275
+ } else if (hasItemA < hasItemB) {
276
+ return 1;
277
+ } else {
278
+ return -1;
279
+ }
280
+ });
281
+ },
282
+ },
283
+ };
284
+ </script>
285
+
286
+ <style lang="scss">
287
+ @import 'settings-tools/all-settings';
288
+ @import 'components/c.checkbox';
289
+ @import 'components/_c.dropdown';
290
+
291
+ .mc-dropdown {
292
+ max-width: var(--max-width);
293
+
294
+ &__tag {
295
+ position: absolute;
296
+ top: 0;
297
+ transform: translateY(50%);
298
+ }
299
+
300
+ &__trigger {
301
+ display: block;
302
+ text-align: left;
303
+
304
+ &.is-open {
305
+ background-image: url(inline-icons('arrow-top-16', black));
306
+ }
307
+ }
308
+ }
309
+
310
+ .mc-dropdown--multi .mc-dropdown__trigger {
311
+ overflow: hidden;
312
+ padding-left: calc(0.75rem + var(--tag-width));
313
+ text-overflow: ellipsis;
314
+ white-space: nowrap;
315
+ }
316
+ </style>
@@ -0,0 +1,7 @@
1
+ import MDropdown from './MDropdown.vue';
2
+
3
+ MDropdown.install = function (Vue) {
4
+ Vue.component(MDropdown.name, MDropdown);
5
+ };
6
+
7
+ export { MDropdown };
@@ -12,6 +12,7 @@ export { MCard } from './card';
12
12
  export { MCheckbox, MCheckboxGroup } from './checkbox';
13
13
  export { MDataTable, MDataTableHeader } from './datatable';
14
14
  export { MField } from './field';
15
+ export { MDropdown } from './dropdown';
15
16
  export { MFileUploader } from './fileuploader';
16
17
  export { MFlag } from './flag';
17
18
  export { MHero } from './hero';
@@ -1,46 +1,50 @@
1
1
  <template>
2
2
  <ul
3
- v-if="items.length > 0"
4
- ref="listbox"
5
3
  role="listbox"
6
4
  class="mc-listbox"
7
5
  aria-labelledby="listbox"
8
6
  :class="{ 'is-open': open, 'mc-listbox--multi': multiple }"
9
7
  >
10
- <li v-for="item in localItems" :key="item.id" class="mc-listbox__item">
11
- <m-icon
12
- v-if="item.icon"
13
- :name="item.icon"
14
- class="mc-listbox__icon"
15
- color="#666666"
16
- />
17
- <label
18
- :for="`listboxItem-${item[dataKeyExpr]}-${uuid}`"
19
- class="mc-listbox__label"
8
+ <template v-if="!isFiltered">
9
+ <li
10
+ v-for="(item, index) in localItems"
11
+ :key="item.id"
12
+ class="mc-listbox__item"
20
13
  >
21
- {{ item[dataTextExpr] }}
22
- </label>
23
- <input
24
- :id="`listboxItem-${item[dataKeyExpr]}-${uuid}`"
25
- ref="input"
26
- v-model="localValue"
27
- class="mc-checkbox__input mc-listbox__input"
28
- type="checkbox"
29
- :value="item[dataValueExpr]"
30
- @change="onChange"
31
- />
14
+ <MIcon
15
+ v-if="item.icon"
16
+ :name="item.icon"
17
+ class="mc-listbox__icon"
18
+ color="#666666"
19
+ />
20
+ <input
21
+ :id="setItemId(index)"
22
+ v-model="localValue"
23
+ class="mc-listbox__input"
24
+ :class="{ 'mc-checkbox__input': multiple }"
25
+ :type="multiple ? 'checkbox' : 'radio'"
26
+ :value="item[dataValueExpr]"
27
+ :name="!multiple ? `listboxradio-${uuid}` : null"
28
+ @change="onChange"
29
+ />
30
+ <label :for="setItemId(index)" class="mc-listbox__label">
31
+ <slot name="item" :item="item">
32
+ {{ item[dataTextExpr] }}
33
+ </slot>
34
+ </label>
35
+ </li>
36
+ </template>
37
+ <li v-else class="mc-listbox__item">
38
+ <span class="mc-listbox__empty">{{ emptySearchLabel }}</span>
32
39
  </li>
33
40
  </ul>
34
- <div v-else class="mc-listbox__empty">
35
- {{ emptySearchLabel }}
36
- </div>
37
41
  </template>
38
42
 
39
43
  <script>
40
44
  import MIcon from '../icon/MIcon.vue';
41
45
 
42
46
  export default {
43
- name: 'MListbox',
47
+ name: 'MListBox',
44
48
 
45
49
  components: {
46
50
  MIcon,
@@ -51,13 +55,17 @@ export default {
51
55
  },
52
56
 
53
57
  props: {
58
+ items: {
59
+ type: Array,
60
+ required: true,
61
+ },
54
62
  open: {
55
63
  type: Boolean,
56
64
  default: false,
57
65
  },
58
- items: {
59
- type: Array,
60
- default: () => [],
66
+ isFiltered: {
67
+ type: Boolean,
68
+ default: false,
61
69
  },
62
70
  multiple: {
63
71
  type: Boolean,
@@ -67,38 +75,38 @@ export default {
67
75
  type: String,
68
76
  default: 'No item matching your criteria found',
69
77
  },
70
- icon: {
71
- type: Boolean,
72
- default: false,
73
- },
74
78
  dataKeyExpr: {
75
79
  type: String,
76
80
  default: 'id',
77
81
  },
78
82
  dataTextExpr: {
79
83
  type: String,
80
- default: 'text',
84
+ default: 'label',
81
85
  },
82
86
  dataValueExpr: {
83
87
  type: String,
84
- default: 'text',
88
+ default: 'value',
85
89
  },
86
90
  value: {
87
- type: [Array, String],
91
+ type: [Array, String, Number],
88
92
  default: undefined,
89
93
  },
90
94
  },
91
95
 
92
96
  data() {
93
97
  return {
94
- localItems: null,
95
- localValue: [],
96
- selected: [],
97
- uuid: null,
98
- inputBaseId: 'listboxInput',
98
+ uuid: Math.random(),
99
+ localItems: undefined,
100
+ localValue: undefined,
99
101
  };
100
102
  },
101
103
 
104
+ computed: {
105
+ setItemId() {
106
+ return (index) => `listboxItem-${index + this.uuid}`;
107
+ },
108
+ },
109
+
102
110
  watch: {
103
111
  items: {
104
112
  handler: function (val) {
@@ -107,44 +115,15 @@ export default {
107
115
  immediate: true,
108
116
  },
109
117
  value: {
110
- handler: function (value) {
111
- this.localValue = value;
112
- },
113
- immediate: true,
114
- },
115
- localValue: {
116
- handler: function (localValue) {
117
- const inputs = this.$refs.input;
118
- if (!this.multiple && inputs) {
119
- const selectedValue = Array.from(localValue);
120
-
121
- inputs.forEach(function (input) {
122
- const listItem = input.closest('.mc-listbox__item');
123
-
124
- if (input.value == selectedValue[0]) {
125
- listItem.classList.add('is-checked');
126
- } else {
127
- listItem.classList.remove('is-checked');
128
- }
129
- });
130
- }
118
+ handler: function (val) {
119
+ this.localValue = val;
131
120
  },
132
121
  immediate: true,
133
122
  },
134
123
  },
135
124
 
136
- mounted() {
137
- this.uuid = Math.random();
138
- },
139
-
140
125
  methods: {
141
126
  onChange() {
142
- if (!this.multiple) {
143
- const currentValue = this.localValue;
144
- this.localValue = [];
145
- this.localValue = currentValue.slice(-1);
146
- }
147
-
148
127
  this.$emit('change', this.localValue);
149
128
  },
150
129
  },
@@ -153,126 +132,10 @@ export default {
153
132
 
154
133
  <style lang="scss">
155
134
  @import 'settings-tools/all-settings';
135
+ @import 'components/c.checkbox';
136
+ @import 'components/c.listbox';
156
137
 
157
- .mc-listbox {
158
- $parent: get-parent-selector(&);
159
-
160
- @include unstyle-list();
161
-
162
- background-color: $color-grey-000;
163
- border: 1px solid $color-grey-600;
164
- border-radius: 3px;
165
- position: absolute;
166
- overflow-y: auto;
167
- margin-top: 5px;
168
- margin-bottom: 0;
169
- max-height: 13.8125rem; // =221px
170
- min-width: 17.875rem; // =286px
171
- opacity: 0;
172
- visibility: hidden;
173
- width: 100%;
174
-
175
- &.is-open {
176
- opacity: 1;
177
- visibility: visible;
178
- z-index: 11;
179
- }
180
-
181
- &::-webkit-scrollbar {
182
- background-color: $color-grey-100;
183
- width: $mu025;
184
-
185
- &-thumb {
186
- background: $color-grey-600;
187
- }
188
- }
189
-
190
- &__item {
191
- align-items: center;
192
- display: flex;
193
- gap: $mu050;
194
- min-height: $mu300;
195
- padding-left: $mu075;
196
- padding-right: $mu075;
197
- position: relative;
198
- justify-content: space-between;
199
-
200
- &:not(:last-child) {
201
- border-bottom: 1px solid $color-grey-300;
202
- }
203
-
204
- &:hover {
205
- background-color: $color-grey-100;
206
- box-shadow: inset 9px 0 0 -7px $color-grey-900;
207
- }
208
- }
209
-
210
- &__flag,
211
- &__icon {
212
- width: $mu200;
213
- height: $mu200;
214
- }
215
-
216
- &__flag {
217
- @include set-font-scale('07', 'm');
218
-
219
- text-align: center;
220
- }
221
-
222
- &__empty {
223
- @include set-font-scale('04', 'm');
224
-
225
- color: $color-fields-error;
226
- display: inline-block;
227
- margin-top: $mu025;
228
- }
229
-
230
- &__label {
231
- cursor: pointer;
232
- margin-right: auto;
233
-
234
- &::after {
235
- content: '';
236
- position: absolute;
237
- inset: 0;
238
- z-index: 2;
239
- }
240
- }
241
-
242
- .is-focus {
243
- background-color: $color-grey-100;
244
- box-shadow: inset 9px 0 0 -7px $color-grey-900;
245
- }
246
-
247
- .is-disabled {
248
- cursor: not-allowed;
249
- box-shadow: none;
250
- background-color: $color-grey-200;
251
- color: $color-grey-600;
252
- pointer-events: none;
253
- }
254
-
255
- &--left {
256
- right: 0;
257
- transform: translateX(-100%);
258
- }
259
-
260
- &:not(.mc-listbox--multi) {
261
- .is-checked {
262
- background-size: $mu150;
263
- background-image: url(inline-icons(
264
- 'notification-available-24',
265
- $color-grey-900
266
- ));
267
- background-repeat: no-repeat;
268
- background-position: right $mu075 center;
269
- }
270
-
271
- #{$parent} {
272
- &__input {
273
- @include visually-hidden();
274
- }
275
- }
276
- }
138
+ .mc-listbox__empty {
139
+ margin-top: 0;
277
140
  }
278
141
  </style>
@@ -67,7 +67,7 @@
67
67
  </ul>
68
68
  </div>
69
69
  <input
70
- id="smallField"
70
+ :id="id"
71
71
  type="tel"
72
72
  class="mc-phone-number__input mc-text-input mc-text-input--m mc-field__input"
73
73
  name="phone-number-input"
@@ -126,6 +126,10 @@ export default {
126
126
  type: [String, Number],
127
127
  default: '',
128
128
  },
129
+ id: {
130
+ type: String,
131
+ default: null,
132
+ },
129
133
  maxlength: {
130
134
  type: Number,
131
135
  default: 25,
@@ -16,6 +16,7 @@
16
16
 
17
17
  <MTextInputField
18
18
  :id="id"
19
+ ref="input"
19
20
  :value="currentValue"
20
21
  type="number"
21
22
  class="mc-quantity-selector__input"
@@ -29,6 +30,7 @@
29
30
  :disabled="disabled"
30
31
  role="spinbutton"
31
32
  @input="handle"
33
+ @focus="onFocus"
32
34
  @keypress="integerOnly && formatValue($event)"
33
35
  v-on="$listeners"
34
36
  />
@@ -151,6 +153,9 @@ export default {
151
153
  e.preventDefault();
152
154
  }
153
155
  },
156
+ onFocus(e) {
157
+ e.target.select();
158
+ },
154
159
  },
155
160
  };
156
161
  </script>
@@ -21,7 +21,7 @@
21
21
  :style="`--steps: ${steps.length}; --current: ${idx + 1};`"
22
22
  @click="isStepValidated(idx) && $emit('step-changed', step)"
23
23
  >
24
- <a v-if="step.href" :href="step.href" class="mc-stepper__link">
24
+ <a v-if="step.href && isStepValidated(idx)" :href="step.href" class="mc-stepper__link">
25
25
  <div class="mc-stepper__indicator" aria-hidden="true">
26
26
  <m-icon
27
27
  v-if="isStepValidated(idx)"