@true-engineering/true-react-common-ui-kit 4.0.0-alpha74 → 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.
@@ -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 { hasExactParent } from '../../helpers';
29
- import { useDropdown, useIsMounted, useOnClickOutsideWithRef, useTweakStyles } from '../../hooks';
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 { IChangeSelectEvent, IMultipleSelectValue, ISelectFooter } from './types';
45
- import { getInputStyles, ISelectStyles, useStyles } from './Select.styles';
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?: IDropdownWithPopperOptions;
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
- onOpen?: () => void;
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 [isListOpen, setIsListOpen] = useState(false);
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 closeList = useCallback(() => {
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 handleFocus = (event: FocusEvent<HTMLInputElement>) => {
280
- onFocus?.(event);
281
- handleListOpen();
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 handleOnClick = () => {
285
- handleListOpen();
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 = useCallback(
309
- (index: number, isSelected: boolean, event: ChangeEvent<HTMLElement> | KeyboardEvent) => {
310
- if (!isMultiSelect) {
311
- return;
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
- if (index === DEFAULT_OPTION_INDEX || (index === ALL_OPTION_INDEX && !isSelected)) {
316
- handleChange(undefined, event);
317
- return;
318
- }
319
- if (index === ALL_OPTION_INDEX && isSelected) {
320
- handleChange(availableOptions as IMultipleSelectValue<Value>, event);
321
- return;
322
- }
323
- const option = filteredOptions[index];
324
- handleChange(
325
- isSelected
326
- ? // Добавляем
327
- ([...(value ?? []), option] as IMultipleSelectValue<Value>)
328
- : // Убираем
329
- value?.filter((o) => convertToId(o) !== convertToId(option)),
330
- event,
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 (!isListOpen) {
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 onArrowClick = () => {
437
- if (isListOpen) {
445
+ const handleArrowClick = (event: IClickHandlerEvent) => {
446
+ if (isDropdownActive) {
438
447
  input.current?.blur();
439
- closeList();
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
- shouldUsePopper = false,
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
- useEffect(() => {
520
- if (isOpen) {
477
+ useDidMountEffect(() => {
478
+ if (isDropdownActive) {
521
479
  onOpen?.();
480
+ } else {
481
+ onClose?.();
522
482
  }
523
- }, [isOpen]);
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 listEl = (
546
- <div
547
- className={clsx(classes.listWrapper, {
548
- [classes.withoutPopper]: !shouldUsePopper,
549
- [classes.listWrapperInBody]: shouldRenderInBody,
550
- })}
551
- ref={list}
552
- style={popperData?.styles.popper as CSSProperties}
553
- tabIndex={0}
554
- onBlur={handleBlur} // обработка для Tab из списка
555
- {...popperData?.attributes.popper}
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
- {isOpen && (
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
  }
@@ -13,6 +13,7 @@ export const useStyles = createThemedStyles('SelectList', {
13
13
  padding: [CONTAINER_PADDING, 0],
14
14
  fontSize: 16,
15
15
  overflow: 'hidden',
16
+ cursor: 'default',
16
17
  },
17
18
 
18
19
  withListHeader: {
@@ -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
+ }