@buerokratt-ria/common-gui-components 0.0.59 → 0.0.61

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.
@@ -49,6 +49,10 @@
49
49
  display: block;
50
50
  }
51
51
 
52
+ + #{$self}__menu--combobox {
53
+ display: flex;
54
+ }
55
+
52
56
  +#{$self}__menu_up {
53
57
  display: block;
54
58
  }
@@ -124,7 +128,8 @@
124
128
  }
125
129
 
126
130
  #{$self}__search,
127
- #{$self}__options {
131
+ #{$self}__options,
132
+ #{$self}__actions {
128
133
  background-color: get-color(white);
129
134
  }
130
135
  }
@@ -152,21 +157,56 @@
152
157
  }
153
158
 
154
159
  &__menu--combobox {
160
+ flex-direction: column;
155
161
  top: auto;
156
162
  bottom: 100%;
163
+ max-height: none;
157
164
  overflow: hidden;
158
165
  margin-top: 0;
159
166
  margin-bottom: 3px;
160
167
  }
161
168
 
169
+ &__menu--down {
170
+ top: 100%;
171
+ bottom: auto;
172
+ margin-top: 3px;
173
+ margin-bottom: 0;
174
+ }
175
+
176
+ &__menu--portal {
177
+ display: flex;
178
+ position: fixed;
179
+ top: 100%;
180
+ bottom: auto;
181
+ right: auto;
182
+ margin-top: 3px;
183
+ margin-bottom: 0;
184
+ z-index: 10000;
185
+ }
186
+
162
187
  &__options {
163
- max-height: 224px;
188
+ flex: 1 1 auto;
189
+ max-height: 220px;
190
+ min-height: 0;
164
191
  overflow: auto;
165
192
  list-style: none;
166
193
  margin: 0;
167
194
  padding: 0;
168
195
  }
169
196
 
197
+ &__actions {
198
+ display: flex;
199
+ flex: 0 0 auto;
200
+ justify-content: center;
201
+ padding: 12px 16px 16px;
202
+
203
+ > .btn.btn--s {
204
+ justify-content: center;
205
+ min-width: 106px;
206
+ padding: 4px 16px;
207
+ }
208
+ }
209
+
170
210
  &__search {
171
211
  padding: get-spacing(paldiski);
172
212
  position: relative;
@@ -227,7 +267,9 @@
227
267
  }
228
268
 
229
269
  &__option--combobox {
270
+ box-sizing: border-box;
230
271
  cursor: pointer;
272
+ min-height: 40px;
231
273
 
232
274
  &[aria-selected=true] {
233
275
  background-color: get-color(white);
@@ -5,15 +5,17 @@ import {
5
5
  ReactNode,
6
6
  useEffect,
7
7
  useId,
8
+ useLayoutEffect,
8
9
  useMemo,
9
10
  useRef,
10
11
  useState
11
12
  } from 'react';
13
+ import { createPortal } from 'react-dom';
12
14
  import clsx from 'clsx';
13
15
  import { useTranslation } from 'react-i18next';
14
16
  import { MdArrowDropDown, MdExpandMore, MdSearch } from 'react-icons/md';
15
17
 
16
- import { Icon } from '../..';
18
+ import { Button, Icon } from '../..';
17
19
  import './FormCombobox.scss';
18
20
 
19
21
  type FormComboboxOption = {
@@ -32,6 +34,10 @@ type FormComboboxBaseProps = {
32
34
  readonly style?: CSSProperties;
33
35
  readonly isSearchEnabled?: boolean;
34
36
  readonly hideInputStyle?: boolean;
37
+ readonly isMenuPortaled?: boolean;
38
+ readonly allOptionValue?: string;
39
+ readonly direction?: 'down' | 'up';
40
+ readonly selectedOptionsCount?: number;
35
41
  };
36
42
 
37
43
  type FormComboboxSingleProps = FormComboboxBaseProps & {
@@ -48,6 +54,7 @@ type FormComboboxMultipleProps = FormComboboxBaseProps & {
48
54
  readonly defaultValue?: string[];
49
55
  readonly onChange?: (value: string[]) => void;
50
56
  readonly onSelectionChange?: (selection: FormComboboxOption[] | null) => void;
57
+ readonly isApplyBtnVisible?: boolean;
51
58
  };
52
59
 
53
60
  type FormComboboxProps = FormComboboxSingleProps | FormComboboxMultipleProps;
@@ -122,6 +129,39 @@ const orderSelectedOptionsFirst = (
122
129
  return [...selectedOptions, ...unselectedOptions];
123
130
  };
124
131
 
132
+ const getNextMultipleValues = (
133
+ option: FormComboboxOption,
134
+ options: FormComboboxOption[],
135
+ selectedValues: string[],
136
+ allOptionValue?: string
137
+ ): string[] => {
138
+ if (!allOptionValue) {
139
+ return selectedValues.includes(option.value)
140
+ ? selectedValues.filter((value) => value !== option.value)
141
+ : [...selectedValues, option.value];
142
+ }
143
+
144
+ const optionValues = options.map((item) => item.value);
145
+ const realOptionValues = optionValues.filter((value) => value !== allOptionValue);
146
+
147
+ if (option.value === allOptionValue) {
148
+ return selectedValues.includes(allOptionValue) ? [] : [allOptionValue, ...realOptionValues];
149
+ }
150
+
151
+ const nextRealValues = selectedValues.includes(option.value)
152
+ ? selectedValues.filter((value) => value !== option.value && value !== allOptionValue)
153
+ : [...selectedValues.filter((value) => value !== allOptionValue), option.value];
154
+
155
+ if (
156
+ realOptionValues.length > 0 &&
157
+ realOptionValues.every((value) => nextRealValues.includes(value))
158
+ ) {
159
+ return [allOptionValue, ...realOptionValues];
160
+ }
161
+
162
+ return nextRealValues;
163
+ };
164
+
125
165
  export const FormCombobox: FC<FormComboboxProps> = ({
126
166
  label,
127
167
  hideLabel,
@@ -132,6 +172,9 @@ export const FormCombobox: FC<FormComboboxProps> = ({
132
172
  style,
133
173
  isSearchEnabled = false,
134
174
  hideInputStyle = false,
175
+ isMenuPortaled = false,
176
+ allOptionValue,
177
+ direction = 'up',
135
178
  ...props
136
179
  }) => {
137
180
  const id = useId();
@@ -144,15 +187,23 @@ export const FormCombobox: FC<FormComboboxProps> = ({
144
187
  const [internalMultipleValue, setInternalMultipleValue] = useState<string[]>(
145
188
  getInitialMultipleValue(props)
146
189
  );
190
+ const [menuStyle, setMenuStyle] = useState<CSSProperties>({});
147
191
  const wrapperRef = useRef<HTMLDivElement | null>(null);
192
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
193
+ const menuRef = useRef<HTMLDivElement | null>(null);
148
194
  const searchInputRef = useRef<HTMLInputElement | null>(null);
149
195
 
150
196
  const isMultiple = props.multiple === true;
151
- const selectedValues = getSelectedValues(props, internalSingleValue, internalMultipleValue);
197
+ const isApplyBtnVisible = props.multiple === true ? props.isApplyBtnVisible : false;
198
+ const selectedValues = useMemo(() => (
199
+ getSelectedValues(props, internalSingleValue, internalMultipleValue)
200
+ ), [props.multiple, props.value, internalSingleValue, internalMultipleValue]);
201
+ const [draftMultipleValue, setDraftMultipleValue] = useState<string[]>(selectedValues);
202
+ const menuSelectedValues = isApplyBtnVisible ? draftMultipleValue : selectedValues;
152
203
 
153
204
  const orderedOptions = useMemo(() => (
154
- orderSelectedOptionsFirst(options, selectedValues)
155
- ), [options, selectedValues]);
205
+ orderSelectedOptionsFirst(options, menuSelectedValues)
206
+ ), [options, menuSelectedValues]);
156
207
 
157
208
  const filteredOptions = useMemo(() => {
158
209
  const normalizedQuery = query.trim().toLowerCase();
@@ -167,15 +218,54 @@ export const FormCombobox: FC<FormComboboxProps> = ({
167
218
  options.filter((option) => selectedValues.includes(option.value))
168
219
  ), [options, selectedValues]);
169
220
 
221
+ const updateMenuPosition = () => {
222
+ const triggerRect = triggerRef.current?.getBoundingClientRect();
223
+
224
+ if (!triggerRect) return;
225
+
226
+ const offset = 10;
227
+
228
+ setMenuStyle({
229
+ left: triggerRect.left,
230
+ minWidth: hideInputStyle ? 296 : triggerRect.width,
231
+ top: triggerRect.bottom + offset,
232
+ });
233
+ };
234
+
235
+ useLayoutEffect(() => {
236
+ if (!isOpen || !isMenuPortaled) return;
237
+
238
+ updateMenuPosition();
239
+
240
+ window.addEventListener('resize', updateMenuPosition);
241
+ document.addEventListener('scroll', updateMenuPosition, true);
242
+
243
+ return () => {
244
+ window.removeEventListener('resize', updateMenuPosition);
245
+ document.removeEventListener('scroll', updateMenuPosition, true);
246
+ };
247
+ }, [hideInputStyle, isMenuPortaled, isOpen]);
248
+
170
249
  useEffect(() => {
171
250
  if (isOpen) {
172
251
  searchInputRef.current?.focus();
173
252
  }
174
253
  }, [isOpen]);
175
254
 
255
+ useEffect(() => {
256
+ if (isOpen && isApplyBtnVisible) {
257
+ setDraftMultipleValue(selectedValues);
258
+ }
259
+ }, [isOpen, isApplyBtnVisible]);
260
+
176
261
  useEffect(() => {
177
262
  const handleDocumentClick = (event: MouseEvent) => {
178
- if (!wrapperRef.current?.contains(event.target as Node)) {
263
+ const target = event.target as Node;
264
+
265
+ if (
266
+ !wrapperRef.current?.contains(target) &&
267
+ !menuRef.current?.contains(target)
268
+ ) {
179
269
  setIsOpen(false);
180
270
  setQuery('');
181
271
  }
@@ -189,7 +279,12 @@ export const FormCombobox: FC<FormComboboxProps> = ({
189
279
  }, []);
190
280
 
191
281
  const closeOnFocusOutside = (event: ReactFocusEvent<HTMLDivElement>) => {
192
- if (!event.currentTarget.contains(event.relatedTarget)) {
282
+ const relatedTarget = event.relatedTarget as Node | null;
283
+
284
+ if (
285
+ !event.currentTarget.contains(relatedTarget) &&
286
+ !menuRef.current?.contains(relatedTarget)
287
+ ) {
193
288
  setIsOpen(false);
194
289
  setQuery('');
195
290
  }
@@ -215,11 +310,15 @@ export const FormCombobox: FC<FormComboboxProps> = ({
215
310
  if (!props.multiple) return;
216
311
  const multipleProps = props as FormComboboxMultipleProps;
217
312
 
218
- const nextValues = selectedValues.includes(option.value)
219
- ? selectedValues.filter((value) => value !== option.value)
220
- : [...selectedValues, option.value];
313
+ const currentValues = isApplyBtnVisible ? draftMultipleValue : selectedValues;
314
+ const nextValues = getNextMultipleValues(option, options, currentValues, allOptionValue);
221
315
  const nextSelection = options.filter((item) => nextValues.includes(item.value));
222
316
 
317
+ if (isApplyBtnVisible) {
318
+ setDraftMultipleValue(nextValues);
319
+ return;
320
+ }
321
+
223
322
  if (multipleProps.value === undefined) {
224
323
  setInternalMultipleValue(nextValues);
225
324
  }
@@ -239,10 +338,29 @@ export const FormCombobox: FC<FormComboboxProps> = ({
239
338
  setSingleValue(option);
240
339
  };
241
340
 
341
+ const applyMultipleValue = () => {
342
+ if (!props.multiple) return;
343
+
344
+ const multipleProps = props as FormComboboxMultipleProps;
345
+
346
+ if (multipleProps.value === undefined) {
347
+ setInternalMultipleValue(draftMultipleValue);
348
+ }
349
+
350
+ multipleProps.onChange?.(draftMultipleValue);
351
+ const nextSelection = options.filter((item) => draftMultipleValue.includes(item.value));
352
+
353
+ multipleProps.onSelectionChange?.(nextSelection.length ? nextSelection : null);
354
+ setIsOpen(false);
355
+ setQuery('');
356
+ };
357
+
242
358
  const placeholderValue = placeholder || t('global.choose');
243
359
  const triggerLabel = isMultiple
244
360
  ? selectedOptions.length > 0
245
- ? selectedOptions.map((option) => option.label).join(', ')
361
+ ? props.selectedOptionsCount !== undefined
362
+ ? `${placeholder ?? t('global.chosen')} (${props.selectedOptionsCount})`
363
+ : selectedOptions.map((option) => option.label).join(', ')
246
364
  : placeholderValue
247
365
  : selectedOptions[0]?.label ?? placeholderValue;
248
366
  const triggerContent = hideInputStyle ? label ?? triggerLabel : triggerLabel;
@@ -254,11 +372,84 @@ export const FormCombobox: FC<FormComboboxProps> = ({
254
372
  hideInputStyle && 'select--plain',
255
373
  );
256
374
 
375
+ const menu = (
376
+ <div
377
+ ref={menuRef}
378
+ className={clsx(
379
+ 'select__menu select__menu--combobox',
380
+ `select__menu--${direction}`,
381
+ isMenuPortaled && 'select__menu--portal'
382
+ )}
383
+ style={isMenuPortaled ? menuStyle : undefined}
384
+ >
385
+ {
386
+ isSearchEnabled && <div className='select__search'>
387
+ <Icon
388
+ label='Search icon'
389
+ size='medium'
390
+ className='select__search-icon'
391
+ icon={<MdSearch className='search__icon-size' color='#5D6071' />}
392
+ />
393
+ <input
394
+ ref={searchInputRef}
395
+ className='select__search-input'
396
+ value={query}
397
+ onChange={(event) => setQuery(event.target.value)}
398
+ placeholder={searchPlaceholder ?? t('global.search')}
399
+ disabled={disabled}
400
+ />
401
+ </div>
402
+ }
403
+
404
+ <ul className='select__options' role='listbox' aria-label={searchPlaceholder ?? t('global.search')}>
405
+ {filteredOptions.length > 0 ? (
406
+ filteredOptions.map((option) => {
407
+ const isSelected = menuSelectedValues.includes(option.value);
408
+
409
+ return (
410
+ <li
411
+ key={option.value}
412
+ role='option'
413
+ aria-selected={isSelected}
414
+ className='select__option select__option--combobox'
415
+ onMouseDown={(event) => event.preventDefault()}
416
+ onClick={() => selectOption(option)}
417
+ >
418
+ <input
419
+ type={isMultiple ? 'checkbox' : 'radio'}
420
+ checked={isSelected}
421
+ value={option.value}
422
+ onChange={() => null}
423
+ onClick={(event) => event.preventDefault()}
424
+ />
425
+ <span>{option.label}</span>
426
+ </li>
427
+ );
428
+ })
429
+ ) : null}
430
+ </ul>
431
+
432
+ {isApplyBtnVisible && (
433
+ <div className='select__actions'>
434
+ <Button
435
+ size='s'
436
+ type='button'
437
+ onClick={applyMultipleValue}
438
+ disabled={disabled}
439
+ >
440
+ {t('global.apply')}
441
+ </Button>
442
+ </div>
443
+ )}
444
+ </div>
445
+ );
446
+
257
447
  return (
258
448
  <div ref={wrapperRef} className={selectClasses} style={style} onBlur={closeOnFocusOutside}>
259
449
  {label && !hideLabel && <label htmlFor={id} className='select__label'>{label}</label>}
260
450
  <div className='select__wrapper'>
261
451
  <button
452
+ ref={triggerRef}
262
453
  id={id}
263
454
  type='button'
264
455
  className='select__trigger'
@@ -277,54 +468,9 @@ export const FormCombobox: FC<FormComboboxProps> = ({
277
468
  </button>
278
469
 
279
470
  {isOpen && (
280
- <div className='select__menu select__menu--combobox'>
281
- {
282
- isSearchEnabled && <div className='select__search'>
283
- <Icon
284
- label='Search icon'
285
- size='medium'
286
- className='select__search-icon'
287
- icon={<MdSearch className='search__icon-size' color='#5D6071' />}
288
- />
289
- <input
290
- ref={searchInputRef}
291
- className='select__search-input'
292
- value={query}
293
- onChange={(event) => setQuery(event.target.value)}
294
- placeholder={searchPlaceholder ?? t('global.search')}
295
- disabled={disabled}
296
- />
297
- </div>
298
- }
299
-
300
- <ul className='select__options' role='listbox' aria-label={searchPlaceholder ?? t('global.search')}>
301
- {filteredOptions.length > 0 ? (
302
- filteredOptions.map((option) => {
303
- const isSelected = selectedValues.includes(option.value);
304
-
305
- return (
306
- <li
307
- key={option.value}
308
- role='option'
309
- aria-selected={isSelected}
310
- className='select__option select__option--combobox'
311
- onMouseDown={(event) => event.preventDefault()}
312
- onClick={() => selectOption(option)}
313
- >
314
- <input
315
- type={isMultiple ? 'checkbox' : 'radio'}
316
- checked={isSelected}
317
- value={option.value}
318
- onChange={() => null}
319
- onClick={(event) => event.preventDefault()}
320
- />
321
- <span>{option.label}</span>
322
- </li>
323
- );
324
- })
325
- ) : null}
326
- </ul>
327
- </div>
471
+ isMenuPortaled && typeof document !== 'undefined'
472
+ ? createPortal(menu, document.body)
473
+ : menu
328
474
  )}
329
475
  </div>
330
476
  </div>
@@ -1,10 +1,10 @@
1
- import React, { FC, ReactNode, SelectHTMLAttributes, useId, useState } from 'react';
1
+ import React, { FC, ReactNode, SelectHTMLAttributes, useEffect, useId, useState } from 'react';
2
2
  import { useSelect } from 'downshift';
3
3
  import clsx from 'clsx';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { MdArrowDropDown } from 'react-icons/md';
6
6
 
7
- import { Icon } from '../..';
7
+ import { Button, Icon } from '../..';
8
8
  import './FormSelect.scss';
9
9
 
10
10
  type SelectOption = { label: string, value: string };
@@ -18,6 +18,58 @@ type FormMultiselectProps = SelectHTMLAttributes<HTMLSelectElement> & {
18
18
  selectedOptions?: SelectOption[];
19
19
  selectedOptionsCount?: number;
20
20
  onSelectionChange?: (selection: SelectOption[] | null) => void;
21
+ isApplyBtnVisible?: boolean;
22
+ allOptionValue?: string;
23
+ };
24
+
25
+ const getNextSelectedItems = (
26
+ selectedItem: SelectOption,
27
+ options: SelectOption[],
28
+ selectedItems: SelectOption[],
29
+ allOptionValue?: string
30
+ ): SelectOption[] => {
31
+ if (!allOptionValue) {
32
+ const index = selectedItems.findIndex((item) => item.value === selectedItem.value);
33
+ const items: SelectOption[] = [];
34
+
35
+ if (index > 0) {
36
+ items.push(
37
+ ...selectedItems.slice(0, index),
38
+ ...selectedItems.slice(index + 1)
39
+ );
40
+ } else if (index === 0) {
41
+ items.push(...selectedItems.slice(1));
42
+ } else {
43
+ items.push(...selectedItems, selectedItem);
44
+ }
45
+
46
+ return items;
47
+ }
48
+
49
+ const realOptions = options.filter((option) => option.value !== allOptionValue);
50
+ const allOption = options.find((option) => option.value === allOptionValue);
51
+
52
+ if (selectedItem.value === allOptionValue) {
53
+ return selectedItems.some((item) => item.value === allOptionValue)
54
+ ? []
55
+ : [...(allOption ? [allOption] : []), ...realOptions];
56
+ }
57
+
58
+ const selectedWithoutAll = selectedItems.filter((item) => item.value !== allOptionValue);
59
+ const isSelected = selectedWithoutAll.some((item) => item.value === selectedItem.value);
60
+ const nextRealItems = isSelected
61
+ ? selectedWithoutAll.filter((item) => item.value !== selectedItem.value)
62
+ : [...selectedWithoutAll, selectedItem];
63
+
64
+ if (
65
+ allOption &&
66
+ realOptions.length > 0 &&
67
+ realOptions.every((option) => nextRealItems.some((item) => item.value === option.value))
68
+ ) {
69
+ return [allOption, ...realOptions];
70
+ }
71
+
72
+ return nextRealItems;
21
73
  };
22
74
 
23
75
  const FormMultiselect: FC<FormMultiselectProps> = (
@@ -31,6 +83,8 @@ const FormMultiselect: FC<FormMultiselectProps> = (
31
83
  selectedOptions,
32
84
  selectedOptionsCount,
33
85
  onSelectionChange,
86
+ isApplyBtnVisible = false,
87
+ allOptionValue,
34
88
  ...rest
35
89
  },
36
90
  ) => {
@@ -63,30 +117,35 @@ const FormMultiselect: FC<FormMultiselectProps> = (
63
117
  if (!selectedItem) {
64
118
  return;
65
119
  }
66
- const index = selectedItems.findIndex((item) => item.value === selectedItem.value);
67
- const items = [];
68
- if (index > 0) {
69
- items.push(
70
- ...selectedItems.slice(0, index),
71
- ...selectedItems.slice(index + 1)
72
- );
73
- } else if (index === 0) {
74
- items.push(...selectedItems.slice(1));
75
- } else {
76
- items.push(...selectedItems, selectedItem);
77
- }
120
+ const items = getNextSelectedItems(selectedItem, options, selectedItems, allOptionValue);
121
+
78
122
  setSelectedItems(items);
79
- if (onSelectionChange) onSelectionChange(items);
123
+ if (!isApplyBtnVisible) {
124
+ onSelectionChange?.(items.length ? items : null);
125
+ }
80
126
  },
81
127
  });
82
128
 
129
+ useEffect(() => {
130
+ setSelectedItems(selectedOptions ?? []);
131
+ }, [selectedOptions]);
132
+
133
+ const applySelection = () => {
134
+ onSelectionChange?.(selectedItems.length ? selectedItems : null);
135
+ };
136
+
83
137
  const selectClasses = clsx(
84
138
  'select',
85
139
  disabled && 'select--disabled',
86
140
  );
87
141
 
88
142
  const placeholderValue = placeholder || t('global.choose');
89
- const displaySelectedCount = selectedOptionsCount ?? selectedItems.length;
143
+ const selectedItemsCount = allOptionValue
144
+ ? selectedItems.filter((item) => item.value !== allOptionValue).length
145
+ : selectedItems.length;
146
+ const displaySelectedCount = isApplyBtnVisible
147
+ ? selectedItemsCount
148
+ : selectedOptionsCount ?? selectedItems.length;
90
149
 
91
150
  return (
92
151
  <div className={selectClasses} style={rest.style}>
@@ -97,27 +156,40 @@ const FormMultiselect: FC<FormMultiselectProps> = (
97
156
  <Icon label='Dropdown icon' size='medium' icon={<MdArrowDropDown color='#5D6071' />} />
98
157
  </div>
99
158
 
100
- <ul className='select__menu' {...getMenuProps()}>
101
- {isOpen &&
102
- options.map((item, index) => (
103
- <li
104
- key={`${item.label}-${index}`}
105
- className={clsx('select__option', { 'select__option--selected': highlightedIndex === index })}
106
- {...getItemProps({
107
- item,
108
- index,
109
- })}
159
+ <div className='select__menu select__menu--multiselect'>
160
+ <ul className='select__options' {...getMenuProps()}>
161
+ {isOpen &&
162
+ options.map((item, index) => (
163
+ <li
164
+ key={`${item.label}-${index}`}
165
+ className={clsx('select__option', { 'select__option--selected': highlightedIndex === index })}
166
+ {...getItemProps({
167
+ item,
168
+ index,
169
+ })}
170
+ >
171
+ <input
172
+ type='checkbox'
173
+ checked={selectedItems.map((s) => s.value).includes(item.value)}
174
+ value={item.value}
175
+ onChange={() => null}
176
+ />
177
+ <span>{item.label}</span>
178
+ </li>
179
+ ))}
180
+ </ul>
181
+ {isOpen && isApplyBtnVisible && (
182
+ <div className='select__actions'>
183
+ <Button
184
+ size='s'
185
+ type='button'
186
+ onClick={applySelection}
110
187
  >
111
- <input
112
- type='checkbox'
113
- checked={selectedItems.map((s) => s.value).includes(item.value)}
114
- value={item.value}
115
- onChange={() => null}
116
- />
117
- <span>{item.label}</span>
118
- </li>
119
- ))}
120
- </ul>
188
+ {t('global.apply')}
189
+ </Button>
190
+ </div>
191
+ )}
192
+ </div>
121
193
  </div>
122
194
  </div>
123
195
  );
@@ -48,6 +48,10 @@
48
48
  display: block;
49
49
  }
50
50
 
51
+ + #{$self}__menu--multiselect {
52
+ display: flex;
53
+ }
54
+
51
55
  +#{$self}__menu_up {
52
56
  display: block;
53
57
  }
@@ -74,6 +78,21 @@
74
78
  margin-top: 3px;
75
79
  }
76
80
 
81
+ &__menu--multiselect {
82
+ flex-direction: column;
83
+ overflow: hidden;
84
+ }
85
+
86
+ &__options {
87
+ flex: 1 1 auto;
88
+ max-height: 224px;
89
+ min-height: 0;
90
+ overflow: auto;
91
+ list-style: none;
92
+ margin: 0;
93
+ padding: 0;
94
+ }
95
+
77
96
  &__menu_up {
78
97
  display: none;
79
98
  position: absolute;
@@ -118,4 +137,17 @@
118
137
  color: get-color(black-coral-20);
119
138
  }
120
139
  }
140
+
141
+ &__actions {
142
+ display: flex;
143
+ flex: 0 0 auto;
144
+ justify-content: center;
145
+ padding: 12px 16px 16px;
146
+
147
+ > .btn.btn--s {
148
+ justify-content: center;
149
+ min-width: 106px;
150
+ padding: 4px 16px;
151
+ }
152
+ }
121
153
  }