@itfin/components 1.0.76 → 1.0.80

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": "@itfin/components",
3
- "version": "1.0.76",
3
+ "version": "1.0.80",
4
4
  "main": "dist/itfin-components.umd.js",
5
5
  "unpkg": "dist/itfin-components.common.js",
6
6
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
@@ -7,7 +7,9 @@
7
7
  >
8
8
  <div class="modal-content itf-append-context" ref="content">
9
9
  <div class="modal-header">
10
- <h5 class="modal-title" :id="modalId">{{title}}</h5>
10
+ <slot name="title">
11
+ <h5 class="modal-title" :id="modalId">{{title}}</h5>
12
+ </slot>
11
13
  <itf-button icon data-bs-dismiss="modal" aria-label="Close" class="btn-close"></itf-button>
12
14
  </div>
13
15
  <div class="modal-body" v-if="value">
@@ -96,7 +98,7 @@ class itfModal extends Vue {
96
98
  setTimeout(() => {
97
99
  this.modalEl.show();
98
100
  modalStack.push(this);
99
- }, 500);
101
+ }, modalStack.length ? 500 : 0);
100
102
  } else {
101
103
  this.modalEl.hide();
102
104
  modalStack.pop();
@@ -104,7 +106,7 @@ class itfModal extends Vue {
104
106
  setTimeout(() => {
105
107
  modalStack[modalStack.length - 1].show();
106
108
  modalStack[modalStack.length - 1].bindEvents();
107
- }, 500);
109
+ }, modalStack.length ? 500 : 0);
108
110
  }
109
111
  }
110
112
  }
@@ -131,12 +133,16 @@ class itfModal extends Vue {
131
133
 
132
134
  bindEvents() {
133
135
  this.preventEvents = false;
134
- this.$refs.modal.addEventListener('hidden.bs.modal', this.onClose);
136
+ if (this.$refs.modal) {
137
+ this.$refs.modal.addEventListener('hidden.bs.modal', this.onClose);
138
+ }
135
139
  }
136
140
 
137
141
  unbindEvents() {
138
142
  this.preventEvents = true;
139
- this.$refs.modal.removeEventListener('hidden.bs.modal', this.onClose);
143
+ if (this.$refs.modal) {
144
+ this.$refs.modal.removeEventListener('hidden.bs.modal', this.onClose);
145
+ }
140
146
  }
141
147
 
142
148
  show() {
@@ -165,6 +165,8 @@ class itfSegmentedControl extends Vue {
165
165
  @Prop({ type: Boolean, default: false }) returnObject;
166
166
  @Prop({ type: Boolean, default: false }) disabled;
167
167
 
168
+ timers = [];
169
+
168
170
  get name () {
169
171
  return `sc${this._uid}`;
170
172
  }
@@ -210,20 +212,25 @@ class itfSegmentedControl extends Vue {
210
212
  updatePillPosition(this);
211
213
 
212
214
  function updatePillPosition (component) {
215
+ component.timers.forEach((timer) => {
216
+ clearTimeout(timer);
217
+ });
218
+ component.timers = [];
213
219
  component.$refs.input.forEach((elem, index) => {
214
220
  if (elem.checked) {
215
221
  component.$nextTick(() => moveBackgroundPillToElement(component, elem, index));
216
- setTimeout(() => moveBackgroundPillToElement(component, elem, index), 500);
217
- setTimeout(() => moveBackgroundPillToElement(component, elem, index), 750);
218
- setTimeout(() => moveBackgroundPillToElement(component, elem, index), 1500);
219
- setTimeout(() => moveBackgroundPillToElement(component, elem, index), 3000);
222
+ component.timers = [
223
+ setTimeout(() => moveBackgroundPillToElement(component, elem, index), 500),
224
+ setTimeout(() => moveBackgroundPillToElement(component, elem, index), 750),
225
+ setTimeout(() => moveBackgroundPillToElement(component, elem, index), 1500),
226
+ setTimeout(() => moveBackgroundPillToElement(component, elem, index), 3000)
227
+ ];
220
228
  }
221
229
  })
222
230
  }
223
231
 
224
232
  function moveBackgroundPillToElement (component, elem, index) {
225
233
  const slider = component.$refs.slider;
226
- console.info('init 2', slider);
227
234
  if (slider) {
228
235
  slider.style.transform = 'translateX(' + (elem.offsetWidth * index) + 'px)';
229
236
  }
@@ -0,0 +1,355 @@
1
+ <template>
2
+ <div
3
+ class="select"
4
+ ref="selectRef"
5
+ :class="[variant]"
6
+ @keydown="handleFocusedSelectKeydown"
7
+ >
8
+ <div
9
+ class="valueContainer text-textDarkest"
10
+ :data-testid="name ? `select:${name}` : 'select'"
11
+ @click="activateDropdown"
12
+ >
13
+ <div class="placeholder" v-if="isValueEmpty">{{ placeholder }}</div>
14
+
15
+ <slot
16
+ v-if="!isValueEmpty && !multiple && customRender"
17
+ :option="getOption(localValue)"
18
+ />
19
+ <template v-else>
20
+ {{ getOptionLabel(localValue) }}
21
+ </template>
22
+
23
+ <div v-if="!isValueEmpty && multiple" class="valueMulti text-textDarkest">
24
+ <div
25
+ class="my-1 mx-1 flex items-center"
26
+ v-for="optionValue in localValue"
27
+ :key="optionValue"
28
+ >
29
+ <slot
30
+ v-if="customRender"
31
+ :option="getOption(optionValue)"
32
+ :value="optionValue"
33
+ :remove="removeOptionValue"
34
+ />
35
+ <div v-else class="valueMultiItem text-textDarkest" :style="getOptionColor(optionValue) ? `background-color: #${getOptionColor(optionValue)}; color: #fff` : ''">
36
+ <div class="valueMultiItemLabel">
37
+ {{ getOptionLabel(optionValue) }}
38
+ </div>
39
+ <div
40
+ @click="removeOptionValue(optionValue)"
41
+ class="valueMultiItemClose"
42
+ >
43
+ <svg class="icon" viewBox="0 0 24 24">
44
+ <path
45
+ d="M16.192 6.344L11.949 10.586 7.707 6.344 6.293 7.758 10.535 12 6.293 16.242 7.707 17.656 11.949 13.414 16.192 17.656 17.606 16.242 13.364 12 17.606 7.758z"
46
+ />
47
+ </svg>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="addMore m-1">
53
+ <svg
54
+ class="icon"
55
+ viewBox="0 0 24 24"
56
+ focusable="false"
57
+ role="presentation"
58
+ >
59
+ <path
60
+ d="M13 11V3.993A.997.997 0 0 0 12 3c-.556 0-1 .445-1 .993V11H3.993A.997.997 0 0 0 3 12c0 .557.445 1 .993 1H11v7.007c0 .548.448.993 1 .993.556 0 1-.445 1-.993V13h7.007A.997.997 0 0 0 21 12c0-.556-.445-1-.993-1H13z"
61
+ fill="currentColor"
62
+ fill-rule="evenodd"
63
+ ></path>
64
+ </svg>
65
+ <div class="font-medium">Add more</div>
66
+ </div>
67
+ </div>
68
+
69
+ <icon
70
+ v-if="(!multiple || isValueEmpty) && variant !== 'empty'"
71
+ class="ml-auto mr-1"
72
+ name="chevron_down"
73
+ ></icon>
74
+ </div>
75
+
76
+ <Dropdown
77
+ v-if="isDropdownOpen"
78
+ ref="dropdownRef"
79
+ isValueEmpty
80
+ :dropdownWidth="dropdownWidth"
81
+ :searchable="searchable"
82
+ :searchValue="searchValue"
83
+ :value="localValue"
84
+ :deactivateDropdown="deactivateDropdown"
85
+ :options="options"
86
+ :multiple="multiple"
87
+ :item-key="itemKey"
88
+ :item-text="itemText"
89
+ :withClearValue="withClearValue"
90
+ @change="handleChange"
91
+ @searchValueChange="handleSearchValueChange"
92
+ >
93
+ <template #option="{ option }">
94
+ <slot v-if="customRenderOption" name="option" :option="option" />
95
+ <slot v-else :option="option" />
96
+ </template>
97
+ </Dropdown>
98
+ </div>
99
+ </template>
100
+
101
+ <script>
102
+ import { Component, Model, Prop, Ref } from 'vue-property-decorator';
103
+ import Vue from 'vue';
104
+ import Icon from '../icon/Icon.vue';
105
+ import Dropdown from './Dropdown.vue';
106
+ import { useOutsideClick } from './useOutsideClick';
107
+
108
+ const { bind, unbind } = useOutsideClick();
109
+
110
+ export default @Component({
111
+ name: 'itfAirSelect',
112
+ components: { Dropdown, Icon },
113
+ })
114
+ class itfAirSelect extends Vue {
115
+ @Model('input', { type: [Array, String, Number], default: undefined }) value;
116
+
117
+ @Prop({ type: Number, default: undefined }) dropdownWidth;
118
+
119
+ @Prop({ type: String, default: 'normal' }) variant;
120
+
121
+ @Prop({ type: String, default: 'value' }) itemKey;
122
+ @Prop({ type: String, default: 'label' }) itemText;
123
+ @Prop({ type: String, default: 'color' }) itemColor;
124
+
125
+ @Prop({ type: String, default: undefined }) name;
126
+
127
+ @Prop({ type: Boolean, default: false }) searchable;
128
+
129
+ @Prop({ type: [String, Array, Number], default: undefined }) defaultValue;
130
+
131
+ @Prop({ type: String, default: 'Select' }) placeholder;
132
+
133
+ @Prop({ type: Boolean, default: false }) invalid;
134
+
135
+ @Prop({ type: Array, required: true }) options;
136
+
137
+ @Prop({ type: Boolean, default: false }) multiple;
138
+
139
+ @Prop({ type: Boolean, default: false }) withClearValue;
140
+
141
+ @Prop({ type: Boolean, default: false }) customRender;
142
+ @Prop({ type: Boolean, default: false }) customRenderOption;
143
+
144
+ @Ref('selectRef') selectRef;
145
+
146
+ @Ref('dropdownRef') dropdownRef;
147
+
148
+ isDropdownOpen = false;
149
+
150
+ searchValue = '';
151
+
152
+ data() {
153
+ return {
154
+ stateValue: this.defaultValue || (this.multiple ? [] : null),
155
+ };
156
+ }
157
+
158
+ getOption(optionValue) {
159
+ return this.options.find((option) => option[this.itemKey] === optionValue);
160
+ }
161
+
162
+ getOptionLabel(optionValue) {
163
+ return (this.getOption(optionValue) || { [this.itemText]: '' })[this.itemText];
164
+ }
165
+
166
+ getOptionColor(optionValue) {
167
+ return (this.getOption(optionValue) || { [this.itemColor]: '' })[this.itemColor];
168
+ }
169
+
170
+ get isControlled() {
171
+ return this.value !== undefined;
172
+ }
173
+
174
+ get localValue() {
175
+ return this.isControlled ? this.value : this.stateValue;
176
+ }
177
+
178
+ preserveValueType(newValue) {
179
+ const areOptionValuesNumbers = this.options.some(
180
+ (option) => typeof option[this.itemKey] === 'number',
181
+ );
182
+ if (areOptionValuesNumbers) {
183
+ if (this.multiple) {
184
+ return (newValue).map(Number);
185
+ }
186
+ if (newValue) {
187
+ return Number(newValue);
188
+ }
189
+ }
190
+ return newValue;
191
+ }
192
+
193
+ handleChange(newValue) {
194
+ if (!this.isControlled) {
195
+ this.stateValue = this.preserveValueType(newValue);
196
+ }
197
+ this.$emit('input', this.preserveValueType(newValue));
198
+ }
199
+
200
+ removeOptionValue(optionValue) {
201
+ this.handleChange(this.localValue.filter((val) => val !== optionValue));
202
+ }
203
+
204
+ get isValueEmpty() {
205
+ return this.multiple ? !this.localValue.length : !this.getOption(this.localValue);
206
+ }
207
+
208
+ handleFocusedSelectKeydown(event) {
209
+ if (this.isDropdownOpen) return;
210
+ if (event.keyCode === 13) {
211
+ event.preventDefault();
212
+ }
213
+ if (event.keyCode !== 27 && event.keyCode !== 9 && !event.shiftKey) {
214
+ this.isDropdownOpen = true;
215
+ }
216
+ }
217
+
218
+ async deactivateDropdown() {
219
+ this.isDropdownOpen = false;
220
+ this.searchValue = '';
221
+ await this.$nextTick();
222
+ this.selectRef && this.selectRef.focus();
223
+ }
224
+
225
+ async activateDropdown() {
226
+ this.isDropdownOpen = true;
227
+ await this.$nextTick();
228
+ this.selectRef && this.selectRef.blur();
229
+ // eslint-disable-next-line
230
+ this.searchable && this.dropdownRef && this.dropdownRef.$refs.inputRef.focus()
231
+ }
232
+
233
+ handleSearchValueChange(newValue) {
234
+ this.searchValue = newValue;
235
+ }
236
+
237
+ mounted () {
238
+ bind(this.selectRef, this.deactivateDropdown);
239
+ }
240
+
241
+ beforeDestroy () {
242
+ unbind();
243
+ }
244
+ }
245
+ </script>
246
+
247
+ <style lang="scss" scoped>
248
+ .select {
249
+ position: relative;
250
+ border-radius: 4px;
251
+ cursor: pointer;
252
+ font-size: 14px;
253
+ transition: background 0.1s;
254
+ &:focus {
255
+ outline: none;
256
+ }
257
+ }
258
+ .valueContainer {
259
+ display: flex;
260
+ align-items: center;
261
+ width: 100%;
262
+ min-height: 32px;
263
+ justify-content: space-between;
264
+ }
265
+ .placeholder {
266
+ color: #8993a4;
267
+ }
268
+ .valueMulti {
269
+ display: flex;
270
+ align-items: center;
271
+ flex-wrap: wrap;
272
+ // padding-top: 5px;
273
+ }
274
+ .valueMultiItem {
275
+ height: 22px;
276
+ overflow: hidden;
277
+ border-radius: 3px;
278
+ display: flex;
279
+ align-items: center;
280
+ user-select: none;
281
+ background: #f0f0f0;
282
+ font-size: 14px;
283
+ .valueMultiItemLabel {
284
+ flex: auto;
285
+ padding: 0 4px;
286
+ height: 100%;
287
+ display: flex;
288
+ align-items: center;
289
+ border-right: 1px solid #ddd;
290
+ }
291
+ .valueMultiItemClose {
292
+ border-left: none;
293
+ overflow: hidden;
294
+ padding: 0 2px;
295
+ height: 100%;
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ &:hover {
300
+ background: #ff4757;
301
+ color: white;
302
+ }
303
+ .icon {
304
+ width: 18px;
305
+ height: 18px;
306
+ fill: currentColor;
307
+ }
308
+ }
309
+ }
310
+ .addMore {
311
+ font-size: 12.5px;
312
+ cursor: pointer;
313
+ display: flex;
314
+ align-items: center;
315
+ color: #0052cc;
316
+ &:hover,
317
+ &:visited,
318
+ &:active {
319
+ color: #0052cc;
320
+ }
321
+ &:hover {
322
+ text-decoration: underline;
323
+ }
324
+ .icon {
325
+ width: 16px;
326
+ height: 16px;
327
+ fill: currentColor;
328
+ margin-right: 2px;
329
+ }
330
+ }
331
+ .select.normal {
332
+ width: 100%;
333
+ padding-left: 0.5rem;
334
+ padding-right: 0.5rem;
335
+ border-width: 1px;
336
+ border-color: #dfe1e6;
337
+ background-color: #f4f5f7;
338
+ position: relative;
339
+ border-radius: 4px;
340
+ cursor: pointer;
341
+ font-size: 14px;
342
+ -webkit-transition: background .1s;
343
+ transition: background .1s;
344
+ }
345
+ .select.normal:hover {
346
+ @apply bg-backgroundLight;
347
+ }
348
+ .select.normal:focus {
349
+ @apply border border-borderInputFocus bg-white text-borderInputFocus;
350
+ box-shadow: 0 0 0 1px currentColor;
351
+ }
352
+ .select.empty {
353
+ display: inline-block;
354
+ }
355
+ </style>
@@ -0,0 +1,277 @@
1
+ <template>
2
+ <div class="dropdown" :style="{ width: `${dropdownWidth}px` }">
3
+ <input
4
+ v-if="searchable"
5
+ class="dropdownInput"
6
+ type="text"
7
+ ref="inputRef"
8
+ placeholder="Search"
9
+ @input="handleSearchValueChange"
10
+ @keydown="handleInputKeyDown"
11
+ />
12
+
13
+ <div class="options" ref="optionsRef">
14
+ <div
15
+ class="option text-textDarkest"
16
+ v-for="option in filteredOptions"
17
+ :key="option[itemKey]"
18
+ :data-select-option-value="option[itemKey]"
19
+ :data-testid="`select-option:${option[itemText]}`"
20
+ @mouseenter="handleOptionMouseEnter"
21
+ @click="selectOptionValue(option[itemKey])"
22
+ >
23
+ <slot name="option" v-bind="option">{{ option[itemText] }}</slot>
24
+ </div>
25
+
26
+ <div
27
+ v-if="isOptionCreatable"
28
+ class="option text-textDarkest"
29
+ :data-create-option-label="{ searchValue }"
30
+ @mouseenter="handleOptionMouseEnter"
31
+ @click="createOption(searchValue)"
32
+ >
33
+ {{
34
+ isCreatingOption
35
+ ? `Creating "${searchValue}"...`
36
+ : `Create
37
+ "${searchValue}"`
38
+ }}
39
+ </div>
40
+ </div>
41
+
42
+ <div v-if="filteredOptions.length === 0" class="optionsNoResults">
43
+ No results
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script>
49
+ import Vue from 'vue';
50
+ import { Component, Prop, Ref } from 'vue-property-decorator';
51
+
52
+ const activeOptionClass = 'select-option-is-active';
53
+
54
+ export default @Component({
55
+ name: 'itfDropdown',
56
+ components: {}
57
+ })
58
+ class itfSegmentedControl extends Vue {
59
+ @Prop({ type: Number, default: undefined }) dropdownWidth;
60
+ @Prop({ type: [Array, String, Number], default: undefined }) value;
61
+ @Prop({ type: Boolean }) isValueEmpty;
62
+ @Prop({ type: Boolean }) searchable;
63
+ @Prop({ type: String, default: 'value' }) itemKey;
64
+ @Prop({ type: String, default: 'label' }) itemText;
65
+ @Prop({ type: String, default: '' }) searchValue;
66
+ @Prop({ type: Function, required: true }) deactivateDropdown;
67
+ @Prop({ type: Array, required: true }) options;
68
+ @Prop({ type: Function, default: undefined }) onCreate;
69
+ @Prop({ type: Boolean }) multiple;
70
+ @Prop({ type: Boolean }) withClearValue;
71
+
72
+ @Ref('optionsRef') optionsRef;
73
+ @Ref('inputRef') inputRef;
74
+
75
+ isCreatingOption = false;
76
+
77
+ get optionsFilteredBySearchValue() {
78
+ return this.options.filter((option) => option[this.itemText]
79
+ .toString()
80
+ .toLowerCase()
81
+ .includes(this.searchValue.toLowerCase()));
82
+ }
83
+
84
+ get filteredOptions() {
85
+ return this.multiple
86
+ ? this.optionsFilteredBySearchValue.filter(
87
+ (option) => !(this.value).includes(option[this.itemKey]),
88
+ )
89
+ : this.optionsFilteredBySearchValue.filter(
90
+ (option) => this.value !== option[this.itemKey],
91
+ );
92
+ }
93
+
94
+ get isOptionCreatable() {
95
+ return this.onCreate
96
+ && this.searchValue
97
+ && !this.options.map((option) => option[this.itemText]).includes(this.searchValue);
98
+ }
99
+
100
+ handleSearchValueChange(event) {
101
+ this.$emit('searchValueChange', (event.target)[this.itemKey]);
102
+ }
103
+
104
+ async getActiveOptionNode() {
105
+ await this.$nextTick();
106
+ return this.optionsRef
107
+ ? this.optionsRef.querySelector(`.${activeOptionClass}`)
108
+ : null;
109
+ }
110
+
111
+ selectOptionValue (optionValue) {
112
+ if (this.multiple) {
113
+ this.$emit('change', [
114
+ ...new Set([...(this.value), optionValue]),
115
+ ]);
116
+ } else {
117
+ this.deactivateDropdown();
118
+ this.$emit('change', optionValue);
119
+ }
120
+ }
121
+
122
+ createOption (newOptionLabel) {
123
+ this.isCreatingOption = true;
124
+ this.onCreate(
125
+ newOptionLabel,
126
+ (createdOptionValue) => {
127
+ this.isCreatingOption = false;
128
+ this.selectOptionValue(createdOptionValue);
129
+ },
130
+ );
131
+ };
132
+
133
+ async handleInputEnterKeyDown (event) {
134
+ event.preventDefault();
135
+ const $active = await this.getActiveOptionNode();
136
+ if (!$active) return;
137
+ const optionValueToSelect = $active.getAttribute(
138
+ 'data-select-option-value',
139
+ );
140
+ const optionLabelToCreate = $active.getAttribute(
141
+ 'data-create-option-label',
142
+ );
143
+ console.info($active, optionValueToSelect);
144
+ if (optionValueToSelect) {
145
+ this.selectOptionValue(optionValueToSelect);
146
+ } else if (optionLabelToCreate) {
147
+ this.createOption(optionLabelToCreate);
148
+ }
149
+ }
150
+
151
+ async handleInputArrowUpOrDownKeyDown (event) {
152
+ const $active = (await this.getActiveOptionNode());
153
+ const $options = this.optionsRef;
154
+ if (!$active || !$options) return;
155
+ const $optionsHeight = $options.getBoundingClientRect().height;
156
+ const $activeHeight = $active.getBoundingClientRect().height;
157
+ if (event.keyCode === 40) {
158
+ if ($options.lastElementChild === $active) {
159
+ $active.classList.remove(activeOptionClass);
160
+ $options.firstElementChild?.classList.add(activeOptionClass);
161
+ $options.scrollTop = 0;
162
+ } else {
163
+ $active.classList.remove(activeOptionClass);
164
+ $active.nextElementSibling?.classList.add(activeOptionClass);
165
+ if ($active.offsetTop > $options.scrollTop + $optionsHeight / 1.4) {
166
+ $options.scrollTop += $activeHeight;
167
+ }
168
+ }
169
+ } else if (event.keyCode === 38) {
170
+ if ($options.firstElementChild === $active) {
171
+ $active.classList.remove(activeOptionClass);
172
+ $options.lastElementChild?.classList.add(activeOptionClass);
173
+ $options.scrollTop = $options.scrollHeight;
174
+ } else {
175
+ $active.classList.remove(activeOptionClass);
176
+ $active.previousElementSibling?.classList.add(activeOptionClass);
177
+ if ($active.offsetTop < $options.scrollTop + $optionsHeight / 2.4) {
178
+ $options.scrollTop -= $activeHeight;
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ handleInputEscapeKeyDown(event) {
185
+ event.stopImmediatePropagation();
186
+ this.deactivateDropdown();
187
+ }
188
+
189
+ handleInputKeyDown(event) {
190
+ if (event.keyCode === 27) {
191
+ this.handleInputEscapeKeyDown(event);
192
+ } else if (event.keyCode === 13) {
193
+ this.handleInputEnterKeyDown(event);
194
+ } else if (event.keyCode === 40 || event.keyCode === 38) {
195
+ this.handleInputArrowUpOrDownKeyDown(event);
196
+ }
197
+ }
198
+
199
+ async handleOptionMouseEnter(event) {
200
+ const $active = await this.getActiveOptionNode();
201
+ if ($active) $active.classList.remove(activeOptionClass);
202
+ (event.currentTarget).classList.add(activeOptionClass);
203
+ }
204
+
205
+ async setFirstOptionAsActive() {
206
+ const $active = await this.getActiveOptionNode();
207
+ if (!this.optionsRef) return;
208
+ if ($active) $active.classList.remove(activeOptionClass);
209
+ if (this.optionsRef.firstElementChild) {
210
+ this.optionsRef.firstElementChild.classList.add(activeOptionClass);
211
+ }
212
+ }
213
+
214
+ mounted() {
215
+ this.setFirstOptionAsActive();
216
+ }
217
+ }
218
+ </script>
219
+
220
+ <style scoped lang="scss">
221
+ .dropdown {
222
+ z-index: 101;
223
+ position: absolute;
224
+ top: calc(100% + 4px);
225
+ left: -1px;
226
+ border-radius: 0 0 4px 4px;
227
+ background: #fff;
228
+ box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px,
229
+ rgba(9, 30, 66, 0.31) 0px 0px 1px;
230
+ width: 100%;
231
+ }
232
+
233
+ .dropdownInput {
234
+ padding: 10px 14px 8px;
235
+ width: 100%;
236
+ border: none;
237
+ color: #172b4d;
238
+ background: none;
239
+
240
+ &:focus {
241
+ outline: none;
242
+ }
243
+ }
244
+
245
+ .options {
246
+ padding: 5px 0;
247
+ max-height: 200px;
248
+ overflow-x: hidden;
249
+ overflow-y: auto;
250
+ -webkit-overflow-scrolling: touch;
251
+ // &::-webkit-scrollbar {
252
+ // width: 7px;
253
+ // }
254
+ // &::-webkit-scrollbar-track {
255
+ // background: none;
256
+ // }
257
+ // &::-webkit-scrollbar-thumb {
258
+ // // border-radius: 99px;
259
+ // background: #d1d1d1;
260
+ // }
261
+ }
262
+
263
+ .option {
264
+ padding: 8px 14px;
265
+ word-break: break-word;
266
+ cursor: pointer;
267
+
268
+ &.select-option-is-active {
269
+ background: #e8ecee;
270
+ }
271
+ }
272
+
273
+ .optionsNoResults {
274
+ padding: 0px 15px 10px;
275
+ color: #8993a4;
276
+ }
277
+ </style>
@@ -195,12 +195,14 @@
195
195
  }
196
196
  &__dropdown-menu {
197
197
  background-color: $body-bg;
198
- border: 2px solid $input-focus-border;
198
+ //border: 2px solid $input-focus-border;
199
+ border: 0 none;
199
200
  padding-left: 0.125rem !important;
200
201
  padding-right: 0.125rem !important;
201
202
  left: -2px;
202
203
  right: -2px;
203
204
  width: auto;
205
+ box-shadow: rgb(9 30 66 / 25%) 0px 4px 8px -2px, rgb(9 30 66 / 31%) 0px 0px 1px;
204
206
 
205
207
  @media (prefers-color-scheme: notdark) {
206
208
  background-color: $dark-body-bg;
@@ -1,5 +1,7 @@
1
1
  import { storiesOf } from '@storybook/vue';
2
2
  import itfSelect from './Select.vue';
3
+ import itfDropdown from './Dropdown';
4
+ import itfSelect2 from './AirSelect';
3
5
  import itfForm from '../form/Form.vue';
4
6
  import itfLabel from '../form/Label.vue';
5
7
  import itfButton from '../button/Button.vue';
@@ -15,6 +17,8 @@ storiesOf('Common', module)
15
17
  itfLabel,
16
18
  itfButton,
17
19
  itfSelect,
20
+ itfSelect2,
21
+ itfDropdown,
18
22
  itfDatePicker,
19
23
  itfDateRangePicker
20
24
  },
@@ -29,11 +33,16 @@ storiesOf('Common', module)
29
33
  customDays: {
30
34
  '2021-10-21': { text: '🎉', class: 'test' }
31
35
  },
36
+ value: [],
32
37
  countries: [
33
38
  {code: 'CA', country: 'Canada'},
34
39
  {code: 'US', country: 'United states'},
35
40
  {code: 'UA', country: 'Ukraine'},
36
41
  {code: 'TEST', country: 'ASdasd sdas dasd sad sadas da ad asdsad ad d ada dsadsadsadasd sadsa das dasd asdasd asdas da '}
42
+ ],
43
+ issuePriorityOptions: [
44
+ { label: 'High', value: 1 },
45
+ { label: 'Low', value: 2 }
37
46
  ]
38
47
  }
39
48
  },
@@ -65,6 +74,25 @@ storiesOf('Common', module)
65
74
 
66
75
  <itf-form ref="form">
67
76
 
77
+ <itf-select2
78
+ multiple
79
+ searchable
80
+ variant="normal"
81
+ :dropdownWidth="300"
82
+ v-model="value"
83
+ :options="issuePriorityOptions"
84
+ customRender
85
+ >
86
+ <template #default="{ label, icon, color }">
87
+ <itf-button secondary>
88
+ <div class="flex items-center">
89
+ <div class="pr-1 pl-2">
90
+ {{ label }}
91
+ </div>
92
+ </div>
93
+ </itf-button>
94
+ </template>
95
+ </itf-select2>
68
96
  <div class="row">
69
97
  <div class="col-6">
70
98
  <itf-label :rules="[$v.emptyValidation()]" :value="selected" label="From" required>
@@ -0,0 +1,36 @@
1
+ export const useOutsideClick = () => {
2
+ const root = typeof document !== 'undefined' ? document.body : null;
3
+ let handleClickOutside = null;
4
+ let handleKeydown = null;
5
+
6
+ return {
7
+ bind(element, onOutsideClick) {
8
+ handleClickOutside = (e) => {
9
+ if (element && !element.contains(e.target)) {
10
+ onOutsideClick();
11
+ }
12
+ };
13
+ handleKeydown = (e) => {
14
+ if (e.key === 'Escape') {
15
+ onOutsideClick();
16
+ }
17
+ };
18
+ if (root) {
19
+ root.addEventListener(
20
+ 'mousedown',
21
+ handleClickOutside,
22
+ );
23
+ root.addEventListener('keydown', handleKeydown);
24
+ }
25
+ },
26
+ unbind() {
27
+ if (root) {
28
+ root.removeEventListener(
29
+ 'mousedown',
30
+ handleClickOutside,
31
+ );
32
+ root.removeEventListener('keydown', handleKeydown);
33
+ }
34
+ }
35
+ };
36
+ };