@true-engineering/true-react-common-ui-kit 4.0.0-alpha75 → 4.0.0-alpha76
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/dist/components/Select/Select.d.ts +7 -4
- package/dist/components/Select/Select.styles.d.ts +4 -1
- package/dist/components/Select/types.d.ts +17 -0
- package/dist/components/WithPopup/WithPopup.d.ts +4 -2
- package/dist/helpers/misc.d.ts +0 -1
- package/dist/true-react-common-ui-kit.js +200 -234
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.ts +2 -1
- package/src/components/Select/CustomSelect.stories.tsx +0 -14
- package/src/components/Select/MultiSelect.stories.tsx +5 -16
- package/src/components/Select/Select.stories.tsx +5 -16
- package/src/components/Select/Select.styles.ts +8 -19
- package/src/components/Select/Select.tsx +203 -231
- package/src/components/Select/components/SelectList/SelectList.styles.ts +1 -0
- package/src/components/Select/types.ts +18 -0
- package/src/components/WithPopup/WithPopup.tsx +18 -5
- package/src/helpers/misc.ts +0 -14
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeEvent,
|
|
3
|
-
CSSProperties,
|
|
4
3
|
FocusEvent,
|
|
5
4
|
KeyboardEvent,
|
|
6
5
|
MouseEvent,
|
|
@@ -13,7 +12,6 @@ import {
|
|
|
13
12
|
useRef,
|
|
14
13
|
useState,
|
|
15
14
|
} from 'react';
|
|
16
|
-
import { Portal } from 'react-overlays';
|
|
17
15
|
import clsx from 'clsx';
|
|
18
16
|
import { debounce } from 'ts-debounce';
|
|
19
17
|
import {
|
|
@@ -25,14 +23,14 @@ import {
|
|
|
25
23
|
isReactNodeNotEmpty,
|
|
26
24
|
isStringNotEmpty,
|
|
27
25
|
} from '@true-engineering/true-react-platform-helpers';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { IDropdownWithPopperOptions, IReplaceTweakStylesProps } from '../../types';
|
|
26
|
+
import { useDidMountEffect, useIsMounted, useTweakStyles } from '../../hooks';
|
|
27
|
+
import { IClickHandlerEvent, IReplaceTweakStylesProps } from '../../types';
|
|
31
28
|
import { IControlWrapperIcon } from '../ControlWrapper';
|
|
32
29
|
import { IIcon, renderIcon } from '../Icon';
|
|
33
30
|
import { IChangeInputEvent, IInputProps, InputBase } from '../Input';
|
|
34
31
|
import { ISearchInputProps, SearchInput } from '../SearchInput';
|
|
35
32
|
import { WithMessages } from '../WithMessages';
|
|
33
|
+
import { IWithPopupProps, WithPopup } from '../WithPopup';
|
|
36
34
|
import { SelectList } from './components';
|
|
37
35
|
import { ALL_OPTION_INDEX, DEFAULT_OPTION_INDEX } from './constants';
|
|
38
36
|
import {
|
|
@@ -41,8 +39,13 @@ import {
|
|
|
41
39
|
defaultIsOptionDisabled,
|
|
42
40
|
getDefaultConvertToIdFunction,
|
|
43
41
|
} from './helpers';
|
|
44
|
-
import {
|
|
45
|
-
|
|
42
|
+
import {
|
|
43
|
+
IChangeSelectEvent,
|
|
44
|
+
IFloatingDropdownOptions,
|
|
45
|
+
IMultipleSelectValue,
|
|
46
|
+
ISelectFooter,
|
|
47
|
+
} from './types';
|
|
48
|
+
import { getInputStyles, ISelectStyles, useStyles, withPopupStyles } from './Select.styles';
|
|
46
49
|
|
|
47
50
|
export interface ISelectProps<Value>
|
|
48
51
|
extends Omit<
|
|
@@ -61,7 +64,7 @@ export interface ISelectProps<Value>
|
|
|
61
64
|
debounceTime?: number;
|
|
62
65
|
/** @default 0 */
|
|
63
66
|
minSymbolsCountToOpenList?: number;
|
|
64
|
-
dropdownOptions?:
|
|
67
|
+
dropdownOptions?: IFloatingDropdownOptions;
|
|
65
68
|
/** @default 'chevron-down' */
|
|
66
69
|
dropdownIcon?: IIcon | null;
|
|
67
70
|
options: Value[] | readonly Value[];
|
|
@@ -79,7 +82,10 @@ export interface ISelectProps<Value>
|
|
|
79
82
|
onBlur?: (event: Event | SyntheticEvent) => void;
|
|
80
83
|
onType?: (value: string) => Promise<void>;
|
|
81
84
|
optionsFilter?: (options: Value[], query: string) => Value[];
|
|
82
|
-
|
|
85
|
+
/** @description Функция должна быть мемоизирована */
|
|
86
|
+
onOpen?: VoidFunction;
|
|
87
|
+
/** @description Функция должна быть мемоизирована */
|
|
88
|
+
onClose?: VoidFunction;
|
|
83
89
|
compareValuesOnChange?: (v1?: Value, v2?: Value) => boolean;
|
|
84
90
|
/** @description Функция должна быть мемоизирована с целью избежания ререндера */
|
|
85
91
|
convertValueToString?: (value: Value) => string | undefined;
|
|
@@ -135,6 +141,7 @@ export function Select<Value>(
|
|
|
135
141
|
onBlur,
|
|
136
142
|
onType,
|
|
137
143
|
onOpen,
|
|
144
|
+
onClose,
|
|
138
145
|
isOptionDisabled = defaultIsOptionDisabled,
|
|
139
146
|
compareValuesOnChange = defaultCompareFunction,
|
|
140
147
|
convertValueToString = defaultConvertFunction,
|
|
@@ -178,8 +185,16 @@ export function Select<Value>(
|
|
|
178
185
|
currentComponentName: 'Select',
|
|
179
186
|
});
|
|
180
187
|
|
|
188
|
+
const tweakWithPopupStyles = useTweakStyles({
|
|
189
|
+
innerStyles: withPopupStyles,
|
|
190
|
+
tweakStyles,
|
|
191
|
+
className: 'tweakWithPopup',
|
|
192
|
+
currentComponentName: 'WithTooltip',
|
|
193
|
+
});
|
|
194
|
+
|
|
181
195
|
const isMounted = useIsMounted();
|
|
182
|
-
const
|
|
196
|
+
const isDropdownOpenState = useState(false);
|
|
197
|
+
const [isDropdownOpen, setIsDropdownOpen] = isDropdownOpenState;
|
|
183
198
|
const [areOptionsLoading, setAreOptionsLoading] = useState(false);
|
|
184
199
|
const hasDefaultOption = isReactNodeNotEmpty(defaultOptionLabel);
|
|
185
200
|
|
|
@@ -189,9 +204,6 @@ export function Select<Value>(
|
|
|
189
204
|
// вынесен отдельно, из-за проблем с дебаунсом при динамич. опциях
|
|
190
205
|
const [shouldShowDefaultOption, setShouldShowDefaultOption] = useState(true);
|
|
191
206
|
|
|
192
|
-
const root = useRef<HTMLDivElement>(null);
|
|
193
|
-
const inputWrapper = useRef<HTMLDivElement>(null);
|
|
194
|
-
const list = useRef<HTMLDivElement>(null);
|
|
195
207
|
const input = useRef<HTMLInputElement>(null); // TODO ref снаружи?
|
|
196
208
|
|
|
197
209
|
const strValue = isMultiSelect ? value?.[0] : value;
|
|
@@ -247,91 +259,84 @@ export function Select<Value>(
|
|
|
247
259
|
const showedStringValue =
|
|
248
260
|
areAllOptionsSelected && isNotEmpty(allOptionsLabel) ? allOptionsLabel : stringValue;
|
|
249
261
|
|
|
262
|
+
const hasEnoughSymbolsToSearch = searchValue.trim().length >= minSymbolsCountToOpenList;
|
|
263
|
+
|
|
264
|
+
const hasContentInDropdown =
|
|
265
|
+
// Есть опции
|
|
266
|
+
(filteredOptions.length > 0 ||
|
|
267
|
+
// Дефолтная опция
|
|
268
|
+
(defaultOptionLabel !== undefined && !hasEnoughSymbolsToSearch) ||
|
|
269
|
+
// Текст "Загрузка..."
|
|
270
|
+
inputProps.isLoading ||
|
|
271
|
+
// Текст "Совпадений не найдено"
|
|
272
|
+
noMatchesLabel !== undefined ||
|
|
273
|
+
// У нас есть инпут с поиском внутри листа
|
|
274
|
+
hasSearchInputInList) &&
|
|
275
|
+
// Последняя проверка на случай, если мы че то ищем в опциях
|
|
276
|
+
(optionsMode === 'normal' || hasEnoughSymbolsToSearch);
|
|
277
|
+
|
|
278
|
+
const isDropdownDisabled = isDisabled || isReadonly || !hasContentInDropdown;
|
|
279
|
+
const isDropdownActive = isDropdownOpen && !isDropdownDisabled;
|
|
280
|
+
|
|
250
281
|
const convertToId = useCallback(
|
|
251
282
|
(v: Value) => (convertValueToId ?? getDefaultConvertToIdFunction(convertValueToString))(v),
|
|
252
283
|
[convertValueToId, convertValueToString],
|
|
253
284
|
);
|
|
254
285
|
|
|
255
|
-
const
|
|
256
|
-
setIsListOpen(false);
|
|
286
|
+
const reset = () => {
|
|
257
287
|
setSearchValue('');
|
|
258
288
|
setShouldShowDefaultOption(true);
|
|
259
|
-
}, [dropdownOptions?.shouldUsePopper]);
|
|
260
|
-
|
|
261
|
-
const handleListClose = useCallback(
|
|
262
|
-
(event: Event | SyntheticEvent) => {
|
|
263
|
-
if (!isListOpen) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
closeList();
|
|
268
|
-
onBlur?.(event);
|
|
269
|
-
},
|
|
270
|
-
[isListOpen, closeList, onBlur],
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
const handleListOpen = () => {
|
|
274
|
-
if (!isReadonly && !isListOpen) {
|
|
275
|
-
setIsListOpen(true);
|
|
276
|
-
}
|
|
277
289
|
};
|
|
278
290
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
291
|
+
const handleChange = (
|
|
292
|
+
newValue: Value | IMultipleSelectValue<Value> | undefined,
|
|
293
|
+
event: IChangeSelectEvent,
|
|
294
|
+
) => {
|
|
295
|
+
// Тут беда с типами, сорри
|
|
296
|
+
if (!compareValuesOnChange(value as never, newValue as never)) {
|
|
297
|
+
onChange(newValue as (Value & IMultipleSelectValue<Value>) | undefined, event);
|
|
298
|
+
}
|
|
282
299
|
};
|
|
283
300
|
|
|
284
|
-
const
|
|
285
|
-
|
|
301
|
+
const handleOptionSelect = (index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
|
|
302
|
+
if (!isMultiSelect) {
|
|
303
|
+
setIsDropdownOpen(false);
|
|
304
|
+
event.stopPropagation();
|
|
305
|
+
}
|
|
306
|
+
handleChange(index === DEFAULT_OPTION_INDEX ? undefined : filteredOptions[index], event);
|
|
307
|
+
reset();
|
|
308
|
+
input.current?.blur();
|
|
286
309
|
};
|
|
287
310
|
|
|
288
|
-
const handleChange = useCallback(
|
|
289
|
-
(newValue: Value | IMultipleSelectValue<Value> | undefined, event: IChangeSelectEvent) => {
|
|
290
|
-
// Тут беда с типами, сорри
|
|
291
|
-
if (!compareValuesOnChange(value as never, newValue as never)) {
|
|
292
|
-
onChange(newValue as (Value & IMultipleSelectValue<Value>) | undefined, event);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
[value, compareValuesOnChange, onChange],
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
const handleOptionSelect = useCallback(
|
|
299
|
-
(index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
|
|
300
|
-
handleChange(index === DEFAULT_OPTION_INDEX ? undefined : filteredOptions[index], event);
|
|
301
|
-
handleListClose(event);
|
|
302
|
-
input.current?.blur();
|
|
303
|
-
},
|
|
304
|
-
[handleChange, handleListClose, filteredOptions],
|
|
305
|
-
);
|
|
306
|
-
|
|
307
311
|
// MultiSelect
|
|
308
|
-
const handleToggleOptionCheckbox =
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
const handleToggleOptionCheckbox = (
|
|
313
|
+
index: number,
|
|
314
|
+
isSelected: boolean,
|
|
315
|
+
event: ChangeEvent<HTMLElement> | KeyboardEvent,
|
|
316
|
+
) => {
|
|
317
|
+
if (!isMultiSelect) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
313
320
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
[isMultiSelect, filteredOptions, handleChange, value, availableOptions, convertToId],
|
|
334
|
-
);
|
|
321
|
+
// Если выбрана не дефолтная опция, которая сетит андеф
|
|
322
|
+
if (index === DEFAULT_OPTION_INDEX || (index === ALL_OPTION_INDEX && !isSelected)) {
|
|
323
|
+
handleChange(undefined, event);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (index === ALL_OPTION_INDEX && isSelected) {
|
|
327
|
+
handleChange(availableOptions as IMultipleSelectValue<Value>, event);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const option = filteredOptions[index];
|
|
331
|
+
handleChange(
|
|
332
|
+
isSelected
|
|
333
|
+
? // Добавляем
|
|
334
|
+
([...(value ?? []), option] as IMultipleSelectValue<Value>)
|
|
335
|
+
: // Убираем
|
|
336
|
+
value?.filter((o) => convertToId(o) !== convertToId(option)),
|
|
337
|
+
event,
|
|
338
|
+
);
|
|
339
|
+
};
|
|
335
340
|
|
|
336
341
|
const handleOnType = useCallback(
|
|
337
342
|
async (v: string) => {
|
|
@@ -374,7 +379,7 @@ export function Select<Value>(
|
|
|
374
379
|
};
|
|
375
380
|
|
|
376
381
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
377
|
-
if (!
|
|
382
|
+
if (!isDropdownActive) {
|
|
378
383
|
return;
|
|
379
384
|
}
|
|
380
385
|
|
|
@@ -384,6 +389,10 @@ export function Select<Value>(
|
|
|
384
389
|
);
|
|
385
390
|
|
|
386
391
|
switch (event.code) {
|
|
392
|
+
case 'Escape':
|
|
393
|
+
case 'Tab':
|
|
394
|
+
setIsDropdownOpen(false);
|
|
395
|
+
break;
|
|
387
396
|
case 'Enter':
|
|
388
397
|
case 'NumpadEnter': {
|
|
389
398
|
let indexToSelect = focusedListCellIndex;
|
|
@@ -433,78 +442,27 @@ export function Select<Value>(
|
|
|
433
442
|
}
|
|
434
443
|
};
|
|
435
444
|
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
445
|
+
const handleArrowClick = (event: IClickHandlerEvent) => {
|
|
446
|
+
if (isDropdownActive) {
|
|
438
447
|
input.current?.blur();
|
|
439
|
-
|
|
448
|
+
setIsDropdownOpen(false);
|
|
440
449
|
} else {
|
|
441
450
|
input.current?.focus();
|
|
451
|
+
event.stopPropagation();
|
|
442
452
|
}
|
|
443
453
|
};
|
|
444
454
|
|
|
445
|
-
useOnClickOutsideWithRef(list, handleListClose, inputWrapper);
|
|
446
|
-
|
|
447
|
-
const hasEnoughSymbolsToSearch = searchValue.trim().length >= minSymbolsCountToOpenList;
|
|
448
|
-
|
|
449
|
-
const isOpen =
|
|
450
|
-
// Пользователь пытается открыть лист
|
|
451
|
-
isListOpen &&
|
|
452
|
-
// Нам есть что показать:
|
|
453
|
-
// Есть опции
|
|
454
|
-
(filteredOptions.length > 0 ||
|
|
455
|
-
// Дефолтная опция
|
|
456
|
-
(defaultOptionLabel !== undefined && !hasEnoughSymbolsToSearch) ||
|
|
457
|
-
// Текст "Загрузка..."
|
|
458
|
-
inputProps.isLoading ||
|
|
459
|
-
// Текст "Совпадений не найдено"
|
|
460
|
-
noMatchesLabel !== undefined ||
|
|
461
|
-
// У нас есть инпут с поиском внутри листа
|
|
462
|
-
hasSearchInputInList) &&
|
|
463
|
-
// Последняя проверка на случай, если мы че то ищем в опциях
|
|
464
|
-
(optionsMode === 'normal' || hasEnoughSymbolsToSearch);
|
|
465
|
-
|
|
466
|
-
const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
|
|
467
|
-
// Когда что-то блокирует открытие листа, но блур все равно должен сработать
|
|
468
|
-
// например minSymbolsCount
|
|
469
|
-
if (isListOpen && !isOpen) {
|
|
470
|
-
handleListClose(event);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (
|
|
475
|
-
!isNotEmpty(event.relatedTarget) ||
|
|
476
|
-
!isNotEmpty(list.current) ||
|
|
477
|
-
!isNotEmpty(inputWrapper.current)
|
|
478
|
-
) {
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const isActionInsideSelect =
|
|
483
|
-
hasExactParent(event.relatedTarget, list.current) ||
|
|
484
|
-
hasExactParent(event.relatedTarget, inputWrapper.current);
|
|
485
|
-
|
|
486
|
-
// Ничего не делаем, если клик был внутри селекта
|
|
487
|
-
if (!isActionInsideSelect) {
|
|
488
|
-
handleListClose(event);
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
// Эти значения ставятся в false по дефолту также в useDropdown
|
|
493
455
|
const {
|
|
494
|
-
|
|
456
|
+
placement = 'bottom-start',
|
|
457
|
+
popupOffset = 4,
|
|
495
458
|
shouldRenderInBody = false,
|
|
496
459
|
shouldHideOnScroll = false,
|
|
460
|
+
isMinWidthSameAsTrigger = true,
|
|
461
|
+
canBeFlipped = false,
|
|
462
|
+
flipOptions,
|
|
463
|
+
middlewares,
|
|
497
464
|
} = dropdownOptions ?? {};
|
|
498
465
|
|
|
499
|
-
const popperData = useDropdown({
|
|
500
|
-
isOpen,
|
|
501
|
-
onDropdownClose: handleListClose,
|
|
502
|
-
referenceElement: inputWrapper.current,
|
|
503
|
-
dropdownElement: list.current,
|
|
504
|
-
options: dropdownOptions,
|
|
505
|
-
dependenciesForPositionUpdating: [inputProps.isLoading, filteredOptions.length],
|
|
506
|
-
});
|
|
507
|
-
|
|
508
466
|
useEffect(() => {
|
|
509
467
|
const focusedCellIndex = isNotEmpty(strValue)
|
|
510
468
|
? optionsIndexesForNavigation.find((index) => {
|
|
@@ -516,11 +474,13 @@ export function Select<Value>(
|
|
|
516
474
|
setFocusedListCellIndex(focusedCellIndex ?? optionsIndexesForNavigation[0]);
|
|
517
475
|
}, [strValue, filteredOptions, optionsIndexesForNavigation, convertToId]);
|
|
518
476
|
|
|
519
|
-
|
|
520
|
-
if (
|
|
477
|
+
useDidMountEffect(() => {
|
|
478
|
+
if (isDropdownActive) {
|
|
521
479
|
onOpen?.();
|
|
480
|
+
} else {
|
|
481
|
+
onClose?.();
|
|
522
482
|
}
|
|
523
|
-
}, [
|
|
483
|
+
}, [isDropdownActive, onClose, onOpen]);
|
|
524
484
|
|
|
525
485
|
const searchInputEl = hasSearchInputInList && (
|
|
526
486
|
<SearchInput
|
|
@@ -542,19 +502,98 @@ export function Select<Value>(
|
|
|
542
502
|
</>
|
|
543
503
|
);
|
|
544
504
|
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
505
|
+
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
|
|
506
|
+
if (isDisabled) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
onFocus?.(event);
|
|
510
|
+
|
|
511
|
+
if (isDropdownDisabled) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
setIsDropdownOpen(true);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
|
|
518
|
+
if (isDropdownActive) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
reset();
|
|
522
|
+
onBlur?.(event);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const renderTrigger: IWithPopupProps['trigger'] = (
|
|
526
|
+
<div className={classes.root} onKeyDown={handleKeyDown}>
|
|
527
|
+
<div className={clsx(classes.inputWrapper, isDisabled && classes.disabled)}>
|
|
528
|
+
<InputBase
|
|
529
|
+
ref={input}
|
|
530
|
+
value={
|
|
531
|
+
searchValue !== '' && !shouldRenderSearchInputInList ? searchValue : showedStringValue
|
|
532
|
+
}
|
|
533
|
+
size={size}
|
|
534
|
+
isActive={isDropdownOpen || isActive}
|
|
535
|
+
isReadonly={hasReadonlyInput}
|
|
536
|
+
isDisabled={isDisabled}
|
|
537
|
+
isLoading={areOptionsLoading}
|
|
538
|
+
onChange={handleInputChange}
|
|
539
|
+
onFocus={handleFocus}
|
|
540
|
+
onBlur={handleBlur}
|
|
541
|
+
tweakStyles={tweakInputStyles}
|
|
542
|
+
testId={testId}
|
|
543
|
+
endIcon={[
|
|
544
|
+
isMultiSelect && shouldShowMultiSelectCounter
|
|
545
|
+
? ({
|
|
546
|
+
key: 'counter',
|
|
547
|
+
iconComponent: <div className={classes.counter}>(+{value.length - 1})</div>,
|
|
548
|
+
shouldResetSize: true,
|
|
549
|
+
} satisfies IControlWrapperIcon)
|
|
550
|
+
: undefined,
|
|
551
|
+
|
|
552
|
+
...getArray(endIcon),
|
|
553
|
+
|
|
554
|
+
isNotEmpty(dropdownIcon)
|
|
555
|
+
? ({
|
|
556
|
+
key: 'arrow',
|
|
557
|
+
onClick: handleArrowClick,
|
|
558
|
+
tabIndex: undefined,
|
|
559
|
+
iconComponent: (
|
|
560
|
+
<div className={clsx(classes.arrow, { [classes.activeArrow]: isDropdownOpen })}>
|
|
561
|
+
{renderIcon(dropdownIcon)}
|
|
562
|
+
</div>
|
|
563
|
+
),
|
|
564
|
+
} satisfies IControlWrapperIcon)
|
|
565
|
+
: undefined,
|
|
566
|
+
].filter(isNotEmpty)}
|
|
567
|
+
{...inputProps}
|
|
568
|
+
/>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<WithMessages
|
|
575
|
+
errorMessage={errorMessage}
|
|
576
|
+
infoMessage={infoMessage}
|
|
577
|
+
tweakStyles={tweakWithMessagesStyles}
|
|
578
|
+
testId={getTestId(testId, 'wrapper')}
|
|
556
579
|
>
|
|
557
|
-
|
|
580
|
+
<WithPopup
|
|
581
|
+
state={isDropdownOpenState}
|
|
582
|
+
isDisabled={isDropdownDisabled}
|
|
583
|
+
placement={placement}
|
|
584
|
+
popupOffset={popupOffset}
|
|
585
|
+
shouldRenderInBody={shouldRenderInBody}
|
|
586
|
+
shouldHideOnScroll={shouldHideOnScroll}
|
|
587
|
+
isMinWidthSameAsTrigger={isMinWidthSameAsTrigger}
|
|
588
|
+
canBeFlipped={canBeFlipped}
|
|
589
|
+
flipOptions={flipOptions}
|
|
590
|
+
middlewares={middlewares}
|
|
591
|
+
isTriggerWrapped
|
|
592
|
+
clickOptions={{ toggle: false }}
|
|
593
|
+
transitionOptions={{ duration: 0 }}
|
|
594
|
+
trigger={renderTrigger}
|
|
595
|
+
tweakStyles={tweakWithPopupStyles}
|
|
596
|
+
>
|
|
558
597
|
<SelectList
|
|
559
598
|
options={filteredOptions}
|
|
560
599
|
size={size}
|
|
@@ -568,85 +607,18 @@ export function Select<Value>(
|
|
|
568
607
|
activeValue={value}
|
|
569
608
|
isLoading={inputProps.isLoading}
|
|
570
609
|
loadingLabel={loadingLabel}
|
|
571
|
-
tweakStyles={tweakSelectListStyles}
|
|
572
|
-
testId={getTestId(testId, 'list')}
|
|
573
610
|
isMultiSelect={isMultiSelect}
|
|
574
|
-
|
|
575
|
-
shouldScrollToList={shouldScrollToList && !shouldUsePopper && !shouldHideOnScroll}
|
|
611
|
+
shouldScrollToList={shouldScrollToList && !shouldHideOnScroll}
|
|
576
612
|
isOptionDisabled={isOptionDisabled}
|
|
577
613
|
convertValueToString={convertValueToString}
|
|
578
614
|
convertValueToReactNode={convertValueToReactNode}
|
|
579
615
|
convertValueToId={convertToId}
|
|
580
616
|
onOptionSelect={handleOptionSelect}
|
|
581
617
|
onToggleCheckbox={handleToggleOptionCheckbox}
|
|
618
|
+
tweakStyles={tweakSelectListStyles}
|
|
619
|
+
testId={getTestId(testId, 'list')}
|
|
582
620
|
/>
|
|
583
|
-
|
|
584
|
-
</div>
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
return (
|
|
588
|
-
<WithMessages
|
|
589
|
-
errorMessage={errorMessage}
|
|
590
|
-
infoMessage={infoMessage}
|
|
591
|
-
tweakStyles={tweakWithMessagesStyles}
|
|
592
|
-
testId={getTestId(testId, 'wrapper')}
|
|
593
|
-
>
|
|
594
|
-
<div className={classes.root} onKeyDown={handleKeyDown} ref={root}>
|
|
595
|
-
<div
|
|
596
|
-
className={clsx(classes.inputWrapper, isDisabled && classes.disabled)}
|
|
597
|
-
onClick={isDisabled || isReadonly ? undefined : handleOnClick}
|
|
598
|
-
ref={inputWrapper}
|
|
599
|
-
>
|
|
600
|
-
<InputBase
|
|
601
|
-
value={
|
|
602
|
-
searchValue !== '' && !shouldRenderSearchInputInList ? searchValue : showedStringValue
|
|
603
|
-
}
|
|
604
|
-
size={size}
|
|
605
|
-
onChange={handleInputChange}
|
|
606
|
-
isActive={isListOpen || isActive}
|
|
607
|
-
isReadonly={hasReadonlyInput}
|
|
608
|
-
onFocus={handleFocus}
|
|
609
|
-
onBlur={handleBlur}
|
|
610
|
-
isDisabled={isDisabled}
|
|
611
|
-
ref={input}
|
|
612
|
-
isLoading={areOptionsLoading}
|
|
613
|
-
tweakStyles={tweakInputStyles}
|
|
614
|
-
testId={testId}
|
|
615
|
-
endIcon={[
|
|
616
|
-
isMultiSelect && shouldShowMultiSelectCounter
|
|
617
|
-
? ({
|
|
618
|
-
key: 'counter',
|
|
619
|
-
iconComponent: <div className={classes.counter}>(+{value.length - 1})</div>,
|
|
620
|
-
shouldResetSize: true,
|
|
621
|
-
} satisfies IControlWrapperIcon)
|
|
622
|
-
: undefined,
|
|
623
|
-
|
|
624
|
-
...getArray(endIcon),
|
|
625
|
-
|
|
626
|
-
isNotEmpty(dropdownIcon)
|
|
627
|
-
? ({
|
|
628
|
-
key: 'arrow',
|
|
629
|
-
onClick: onArrowClick,
|
|
630
|
-
tabIndex: undefined,
|
|
631
|
-
iconComponent: (
|
|
632
|
-
<div className={clsx(classes.arrow, { [classes.activeArrow]: isOpen })}>
|
|
633
|
-
{renderIcon(dropdownIcon)}
|
|
634
|
-
</div>
|
|
635
|
-
),
|
|
636
|
-
} satisfies IControlWrapperIcon)
|
|
637
|
-
: undefined,
|
|
638
|
-
].filter(isNotEmpty)}
|
|
639
|
-
{...inputProps}
|
|
640
|
-
/>
|
|
641
|
-
</div>
|
|
642
|
-
{shouldUsePopper ? (
|
|
643
|
-
<Portal container={shouldRenderInBody ? document.body : inputWrapper.current}>
|
|
644
|
-
<>{listEl}</>
|
|
645
|
-
</Portal>
|
|
646
|
-
) : (
|
|
647
|
-
<>{isOpen && listEl}</>
|
|
648
|
-
)}
|
|
649
|
-
</div>
|
|
621
|
+
</WithPopup>
|
|
650
622
|
</WithMessages>
|
|
651
623
|
);
|
|
652
624
|
}
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { ChangeEvent, KeyboardEvent } from 'react';
|
|
2
2
|
import { IRenderNode } from '../../types';
|
|
3
3
|
import { IChangeInputEvent } from '../Input';
|
|
4
|
+
import { IWithPopupProps } from '../WithPopup';
|
|
4
5
|
|
|
5
6
|
export type IMultipleSelectValue<Value> = Array<NonNullable<Value>>;
|
|
6
7
|
|
|
7
8
|
export type IChangeSelectEvent = IChangeInputEvent | ChangeEvent<HTMLElement> | KeyboardEvent;
|
|
8
9
|
|
|
9
10
|
export type ISelectFooter<T> = IRenderNode<{ filteredOptions: T[] }>;
|
|
11
|
+
|
|
12
|
+
export interface IFloatingDropdownOptions {
|
|
13
|
+
/** @default 'bottom-start' */
|
|
14
|
+
placement?: IWithPopupProps['placement'];
|
|
15
|
+
/** @default 4 */
|
|
16
|
+
popupOffset?: IWithPopupProps['popupOffset'];
|
|
17
|
+
/** @default false */
|
|
18
|
+
shouldRenderInBody?: IWithPopupProps['shouldRenderInBody'];
|
|
19
|
+
/** @default false */
|
|
20
|
+
shouldHideOnScroll?: IWithPopupProps['shouldHideOnScroll'];
|
|
21
|
+
/** @default true */
|
|
22
|
+
isMinWidthSameAsTrigger?: IWithPopupProps['isMinWidthSameAsTrigger'];
|
|
23
|
+
/** @default false */
|
|
24
|
+
canBeFlipped?: IWithPopupProps['canBeFlipped'];
|
|
25
|
+
flipOptions?: IWithPopupProps['flipOptions'];
|
|
26
|
+
middlewares?: IWithPopupProps['middlewares'];
|
|
27
|
+
}
|