@true-engineering/true-react-common-ui-kit 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/LICENSE +201 -201
  2. package/dist/components/Flag/augment.d.ts +1 -1
  3. package/dist/components/FlexibleTable/types.d.ts +2 -2
  4. package/dist/components/Icon/complexIcons/augment.d.ts +1 -1
  5. package/dist/components/Select/Select.d.ts +1 -1
  6. package/dist/true-react-common-ui-kit.js +64 -64
  7. package/dist/true-react-common-ui-kit.js.map +1 -1
  8. package/dist/true-react-common-ui-kit.umd.cjs +64 -64
  9. package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
  10. package/dist/vite-env.d.ts +1 -1
  11. package/package.json +91 -91
  12. package/src/components/AccountInfo/AccountInfo.stories.tsx +35 -35
  13. package/src/components/AccountInfo/AccountInfo.styles.ts +55 -55
  14. package/src/components/AccountInfo/AccountInfo.tsx +106 -106
  15. package/src/components/AccountInfo/index.ts +2 -2
  16. package/src/components/AddButton/AddButton.stories.tsx +21 -21
  17. package/src/components/AddButton/AddButton.styles.ts +34 -34
  18. package/src/components/AddButton/AddButton.tsx +49 -49
  19. package/src/components/AddButton/index.ts +2 -2
  20. package/src/components/Button/Button.stories.tsx +61 -61
  21. package/src/components/Button/Button.styles.ts +196 -196
  22. package/src/components/Button/Button.tsx +195 -195
  23. package/src/components/Button/index.ts +2 -2
  24. package/src/components/Checkbox/Checkbox.stories.tsx +35 -35
  25. package/src/components/Checkbox/Checkbox.styles.ts +62 -62
  26. package/src/components/Checkbox/Checkbox.tsx +106 -106
  27. package/src/components/Checkbox/index.ts +2 -2
  28. package/src/components/CloseButton/CloseButton.styles.ts +34 -34
  29. package/src/components/CloseButton/CloseButton.tsx +37 -37
  30. package/src/components/CloseButton/index.ts +2 -2
  31. package/src/components/Colors/Colors.stories.tsx +7 -7
  32. package/src/components/Colors/Colors.styles.ts +38 -38
  33. package/src/components/Colors/Colors.tsx +34 -34
  34. package/src/components/Colors/index.ts +2 -2
  35. package/src/components/CssBaseline/CssBaseline.styles.ts +15 -15
  36. package/src/components/CssBaseline/CssBaseline.tsx +17 -17
  37. package/src/components/CssBaseline/index.ts +2 -2
  38. package/src/components/DateInput/DateInput.stories.tsx +63 -63
  39. package/src/components/DateInput/DateInput.styles.ts +14 -14
  40. package/src/components/DateInput/DateInput.tsx +60 -60
  41. package/src/components/DateInput/index.ts +2 -2
  42. package/src/components/DatePicker/DatePicker.stories.tsx +96 -96
  43. package/src/components/DatePicker/DatePicker.styles.ts +54 -54
  44. package/src/components/DatePicker/DatePicker.tsx +358 -358
  45. package/src/components/DatePicker/DatePickerHeader/DatePickerHeader.styles.ts +84 -84
  46. package/src/components/DatePicker/DatePickerHeader/DatePickerHeader.tsx +94 -94
  47. package/src/components/DatePicker/DatePickerHeader/index.ts +1 -1
  48. package/src/components/DatePicker/DatePickerInput/DatePickerInput.styles.ts +25 -25
  49. package/src/components/DatePicker/DatePickerInput/DatePickerInput.tsx +31 -31
  50. package/src/components/DatePicker/DatePickerInput/index.ts +1 -1
  51. package/src/components/DatePicker/index.ts +4 -4
  52. package/src/components/Description/Description.stories.tsx +29 -29
  53. package/src/components/Description/Description.styles.ts +31 -31
  54. package/src/components/Description/Description.tsx +69 -69
  55. package/src/components/Description/index.ts +2 -2
  56. package/src/components/FiltersPane/FilterInterval/FilterInterval.styles.ts +64 -64
  57. package/src/components/FiltersPane/FilterInterval/FilterInterval.tsx +162 -162
  58. package/src/components/FiltersPane/FilterInterval/index.ts +1 -1
  59. package/src/components/FiltersPane/FilterMultiSelect/FilterMultiSelect.tsx +14 -14
  60. package/src/components/FiltersPane/FilterMultiSelect/index.ts +1 -1
  61. package/src/components/FiltersPane/FilterSelect/FilterSelect.styles.ts +144 -144
  62. package/src/components/FiltersPane/FilterSelect/FilterSelect.tsx +397 -397
  63. package/src/components/FiltersPane/FilterSelect/index.ts +1 -1
  64. package/src/components/FiltersPane/FilterSelect/locales.ts +37 -37
  65. package/src/components/FiltersPane/FilterValueView/FilterValueView.styles.tsx +15 -15
  66. package/src/components/FiltersPane/FilterValueView/FilterValueView.tsx +186 -186
  67. package/src/components/FiltersPane/FilterValueView/index.tsx +1 -1
  68. package/src/components/FiltersPane/FilterWithDates/FilterWithDates.styles.ts +60 -60
  69. package/src/components/FiltersPane/FilterWithDates/FilterWithDates.tsx +222 -222
  70. package/src/components/FiltersPane/FilterWithDates/index.ts +1 -1
  71. package/src/components/FiltersPane/FilterWithPeriod/FilterWithPeriod.styles.ts +17 -17
  72. package/src/components/FiltersPane/FilterWithPeriod/FilterWithPeriod.tsx +231 -231
  73. package/src/components/FiltersPane/FilterWithPeriod/index.ts +1 -1
  74. package/src/components/FiltersPane/FilterWrapper/FilterWrapper.styles.ts +110 -110
  75. package/src/components/FiltersPane/FilterWrapper/FilterWrapper.tsx +360 -360
  76. package/src/components/FiltersPane/FilterWrapper/index.ts +1 -1
  77. package/src/components/FiltersPane/FiltersPane.stories.tsx +308 -308
  78. package/src/components/FiltersPane/FiltersPane.styles.ts +71 -71
  79. package/src/components/FiltersPane/FiltersPane.tsx +193 -193
  80. package/src/components/FiltersPane/FiltersPaneSearch/FiltersPaneSearch.styles.ts +109 -109
  81. package/src/components/FiltersPane/FiltersPaneSearch/FiltersPaneSearch.tsx +175 -175
  82. package/src/components/FiltersPane/FiltersPaneSearch/index.ts +1 -1
  83. package/src/components/FiltersPane/index.ts +20 -20
  84. package/src/components/FiltersPane/locales.ts +107 -107
  85. package/src/components/FiltersPane/types.ts +126 -126
  86. package/src/components/Flag/Flag.stories.tsx +29 -29
  87. package/src/components/Flag/Flag.styles.ts +18 -18
  88. package/src/components/Flag/Flag.tsx +28 -28
  89. package/src/components/Flag/augment.d.ts +1 -1
  90. package/src/components/Flag/index.ts +2 -2
  91. package/src/components/FlexibleTable/FlexibleTable.stories.tsx +86 -80
  92. package/src/components/FlexibleTable/FlexibleTable.styles.ts +131 -131
  93. package/src/components/FlexibleTable/FlexibleTable.tsx +243 -243
  94. package/src/components/FlexibleTable/TableRow.tsx +171 -171
  95. package/src/components/FlexibleTable/TableValue.tsx +83 -83
  96. package/src/components/FlexibleTable/fixture-test.ts +254 -254
  97. package/src/components/FlexibleTable/index.ts +3 -3
  98. package/src/components/FlexibleTable/types.ts +58 -58
  99. package/src/components/Icon/ComplexIconBoilerplate.tsx +17 -17
  100. package/src/components/Icon/Icon.stories.tsx +88 -88
  101. package/src/components/Icon/Icon.styles.ts +10 -10
  102. package/src/components/Icon/Icon.tsx +34 -34
  103. package/src/components/Icon/IconBoilerplate.tsx +42 -42
  104. package/src/components/Icon/complexIcons/augment.d.ts +1 -1
  105. package/src/components/Icon/complexIcons/avatarGreen.svg +57 -57
  106. package/src/components/Icon/complexIcons/icons.ts +7 -7
  107. package/src/components/Icon/complexIcons/index.ts +1 -1
  108. package/src/components/Icon/icons/icons.ts +838 -838
  109. package/src/components/Icon/icons/index.ts +1 -1
  110. package/src/components/Icon/index.ts +4 -4
  111. package/src/components/IncrementInput/ChangeButton.tsx +34 -34
  112. package/src/components/IncrementInput/IncrementInput.stories.tsx +34 -34
  113. package/src/components/IncrementInput/IncrementInput.styles.ts +77 -77
  114. package/src/components/IncrementInput/IncrementInput.tsx +95 -95
  115. package/src/components/IncrementInput/index.ts +2 -2
  116. package/src/components/Input/Input.stories.tsx +92 -92
  117. package/src/components/Input/Input.styles.ts +305 -305
  118. package/src/components/Input/Input.tsx +318 -318
  119. package/src/components/Input/index.ts +2 -2
  120. package/src/components/List/List.stories.tsx +62 -62
  121. package/src/components/List/List.styles.ts +52 -52
  122. package/src/components/List/List.tsx +82 -82
  123. package/src/components/List/index.ts +2 -2
  124. package/src/components/Modal/Modal.stories.tsx +113 -113
  125. package/src/components/Modal/Modal.styles.ts +308 -308
  126. package/src/components/Modal/Modal.tsx +210 -210
  127. package/src/components/Modal/index.ts +2 -2
  128. package/src/components/MoreMenu/MoreMenu.stories.tsx +46 -46
  129. package/src/components/MoreMenu/MoreMenu.styles.ts +70 -70
  130. package/src/components/MoreMenu/MoreMenu.tsx +102 -102
  131. package/src/components/MoreMenu/index.ts +2 -2
  132. package/src/components/MultiSelect/MultiSelect.stories.tsx +46 -46
  133. package/src/components/MultiSelect/MultiSelect.styles.ts +55 -55
  134. package/src/components/MultiSelect/MultiSelect.tsx +98 -98
  135. package/src/components/MultiSelect/MultiSelectInput/MultiSelectInput.styles.ts +73 -73
  136. package/src/components/MultiSelect/MultiSelectInput/MultiSelectInput.tsx +62 -62
  137. package/src/components/MultiSelect/MultiSelectInput/index.ts +1 -1
  138. package/src/components/MultiSelect/index.ts +3 -3
  139. package/src/components/MultiSelectList/MultiSelectList.styles.ts +125 -125
  140. package/src/components/MultiSelectList/MultiSelectList.tsx +519 -519
  141. package/src/components/MultiSelectList/index.ts +2 -2
  142. package/src/components/MultiSelectList/locales.ts +37 -37
  143. package/src/components/Notification/Notification.stories.tsx +51 -51
  144. package/src/components/Notification/Notification.styles.ts +50 -50
  145. package/src/components/Notification/Notification.tsx +84 -84
  146. package/src/components/Notification/index.ts +2 -2
  147. package/src/components/NumberInput/NumberInput.stories.tsx +36 -36
  148. package/src/components/NumberInput/NumberInput.tsx +154 -154
  149. package/src/components/NumberInput/helpers.ts +87 -87
  150. package/src/components/NumberInput/index.ts +1 -1
  151. package/src/components/PhoneInput/PhoneInput.stories.tsx +71 -71
  152. package/src/components/PhoneInput/PhoneInput.styles.ts +84 -84
  153. package/src/components/PhoneInput/PhoneInput.tsx +223 -223
  154. package/src/components/PhoneInput/PhoneInputCountryList/PhoneInputCountryList.stories.tsx +21 -21
  155. package/src/components/PhoneInput/PhoneInputCountryList/PhoneInputCountryList.styles.ts +100 -100
  156. package/src/components/PhoneInput/PhoneInputCountryList/PhoneInputCountryList.tsx +171 -171
  157. package/src/components/PhoneInput/PhoneInputCountryList/index.ts +2 -2
  158. package/src/components/PhoneInput/index.ts +6 -6
  159. package/src/components/PhoneInput/phone-info.ts +2167 -2167
  160. package/src/components/PhoneInput/types.ts +16 -16
  161. package/src/components/RadioButton/RadioButton.stories.tsx +46 -46
  162. package/src/components/RadioButton/RadioButton.styles.ts +37 -37
  163. package/src/components/RadioButton/RadioButton.tsx +56 -56
  164. package/src/components/RadioButton/index.ts +2 -2
  165. package/src/components/ScrollIntoViewIfNeeded/ScrollIntoViewIfNeeded.ts +66 -66
  166. package/src/components/ScrollIntoViewIfNeeded/index.ts +1 -1
  167. package/src/components/SearchInput/SearchInput.stories.tsx +24 -24
  168. package/src/components/SearchInput/SearchInput.styles.ts +50 -50
  169. package/src/components/SearchInput/SearchInput.tsx +63 -63
  170. package/src/components/SearchInput/index.ts +2 -2
  171. package/src/components/Select/Select.stories.tsx +258 -258
  172. package/src/components/Select/Select.styles.ts +85 -85
  173. package/src/components/Select/Select.tsx +510 -514
  174. package/src/components/Select/SelectList/SelectList.styles.ts +68 -68
  175. package/src/components/Select/SelectList/SelectList.tsx +139 -139
  176. package/src/components/Select/SelectList/index.ts +1 -1
  177. package/src/components/Select/helpers.ts +21 -21
  178. package/src/components/Select/index.ts +3 -3
  179. package/src/components/SmartInput/SmartInput.stories.tsx +63 -63
  180. package/src/components/SmartInput/SmartInput.tsx +180 -180
  181. package/src/components/SmartInput/helpers.ts +85 -85
  182. package/src/components/SmartInput/index.ts +1 -1
  183. package/src/components/Switch/Switch.stories.tsx +40 -40
  184. package/src/components/Switch/Switch.styles.ts +75 -75
  185. package/src/components/Switch/Switch.tsx +89 -89
  186. package/src/components/Switch/index.ts +2 -2
  187. package/src/components/TextArea/TextArea.stories.tsx +35 -35
  188. package/src/components/TextArea/TextArea.styles.ts +153 -153
  189. package/src/components/TextArea/TextArea.tsx +178 -178
  190. package/src/components/TextArea/index.ts +2 -2
  191. package/src/components/TextWithInfo/TextWithInfo.stories.tsx +53 -53
  192. package/src/components/TextWithInfo/TextWithInfo.styles.ts +60 -60
  193. package/src/components/TextWithInfo/TextWithInfo.tsx +67 -67
  194. package/src/components/TextWithInfo/index.ts +2 -2
  195. package/src/components/TextWithTooltip/TextWithTooltip.stories.tsx +58 -58
  196. package/src/components/TextWithTooltip/TextWithTooltip.styles.ts +19 -19
  197. package/src/components/TextWithTooltip/TextWithTooltip.tsx +163 -163
  198. package/src/components/TextWithTooltip/index.ts +2 -2
  199. package/src/components/ThemedPreloader/ThemedPreloader.stories.tsx +41 -41
  200. package/src/components/ThemedPreloader/ThemedPreloader.styles.ts +21 -21
  201. package/src/components/ThemedPreloader/ThemedPreloader.tsx +56 -56
  202. package/src/components/ThemedPreloader/components/DefaultPreloader/DefaultPreloader.tsx +34 -34
  203. package/src/components/ThemedPreloader/components/DefaultPreloader/index.ts +1 -1
  204. package/src/components/ThemedPreloader/components/DotsPreloader/DotsPreloader.styles.ts +54 -54
  205. package/src/components/ThemedPreloader/components/DotsPreloader/DotsPreloader.tsx +18 -18
  206. package/src/components/ThemedPreloader/components/DotsPreloader/index.ts +2 -2
  207. package/src/components/ThemedPreloader/components/SvgPreloader/SvgPreloader.styles.ts +11 -11
  208. package/src/components/ThemedPreloader/components/SvgPreloader/SvgPreloader.tsx +32 -32
  209. package/src/components/ThemedPreloader/components/SvgPreloader/index.ts +2 -2
  210. package/src/components/ThemedPreloader/components/index.ts +2 -2
  211. package/src/components/ThemedPreloader/index.ts +2 -2
  212. package/src/components/Toaster/Toaster.stories.tsx +34 -34
  213. package/src/components/Toaster/Toaster.styles.ts +59 -59
  214. package/src/components/Toaster/Toaster.tsx +113 -113
  215. package/src/components/Toaster/index.ts +2 -2
  216. package/src/components/Tooltip/Tooltip.stories.tsx +21 -21
  217. package/src/components/Tooltip/Tooltip.styles.ts +45 -45
  218. package/src/components/Tooltip/Tooltip.tsx +40 -40
  219. package/src/components/Tooltip/index.ts +3 -3
  220. package/src/components/Tooltip/types.ts +1 -1
  221. package/src/components/index.ts +36 -36
  222. package/src/helpers/colors.ts +2 -2
  223. package/src/helpers/dateHelpers/date-helpers.ts +9 -9
  224. package/src/helpers/index.ts +4 -4
  225. package/src/helpers/phone.ts +106 -106
  226. package/src/helpers/popper-helpers.ts +17 -17
  227. package/src/helpers/snippets.tsx +5 -5
  228. package/src/helpers/utils.ts +219 -219
  229. package/src/hooks/index.ts +6 -6
  230. package/src/hooks/use-did-mount-effect.ts +21 -21
  231. package/src/hooks/use-dropdown.ts +85 -85
  232. package/src/hooks/use-is-mounted.ts +15 -15
  233. package/src/hooks/use-on-click-outside.ts +92 -92
  234. package/src/hooks/use-theme.ts +36 -36
  235. package/src/hooks/use-tweak-styles.ts +14 -14
  236. package/src/index.ts +6 -6
  237. package/src/theme.ts +155 -155
  238. package/src/types.ts +106 -106
  239. package/src/vite-env.d.ts +1 -1
@@ -1,514 +1,510 @@
1
- import {
2
- ReactNode,
3
- FocusEvent,
4
- KeyboardEvent,
5
- MouseEvent,
6
- useCallback,
7
- useEffect,
8
- useMemo,
9
- useRef,
10
- useState,
11
- SyntheticEvent,
12
- } from 'react';
13
- import { Styles } from 'jss';
14
- import clsx from 'clsx';
15
- import { merge } from 'lodash';
16
- import { debounce } from 'ts-debounce';
17
- import { Portal } from 'react-overlays';
18
- import { SelectList } from './SelectList';
19
- import { IInputProps, Input } from '../Input';
20
- import { IIconType, Icon } from '../Icon';
21
- import {
22
- useIsMounted,
23
- useTheme,
24
- useOnClickOutsideWithRef,
25
- useDropdown,
26
- useTweakStyles,
27
- } from '../../hooks';
28
- import { IDropdownWithPopperOptions } from '../../types';
29
- import { getTestId, hasExactParent, isNotEmpty } from '../../helpers';
30
- import {
31
- defaultConvertFunction,
32
- defaultCompareFunction,
33
- getActiveValueIndex,
34
- defaultIsOptionDisabled,
35
- } from './helpers';
36
- import { SelectStyles, styles } from './Select.styles';
37
- import { ISearchInputProps, SearchInput } from '../SearchInput';
38
-
39
- export interface ISelectProps<Value>
40
- extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type'> {
41
- tweakStyles?: SelectStyles;
42
- defaultOptionLabel?: string;
43
- noMatchesLabel?: string;
44
- loadingLabel?: ReactNode;
45
- optionsMode?: 'search' | 'dynamic' | 'normal';
46
- debounceTime?: number;
47
- minSymbolsCountToOpenList?: number;
48
- dropdownOptions?: IDropdownWithPopperOptions;
49
- dropdownIcon?: IIconType;
50
- options: Value[];
51
- value: Value | undefined;
52
- shouldScrollToList?: boolean;
53
- searchInput?: { shouldRenderInList: true } & Pick<
54
- ISearchInputProps,
55
- 'placeholder'
56
- >;
57
- isOptionDisabled?(option: Value): boolean;
58
- onChange(value: Value | undefined): void; // подумать как возвращать индекс
59
- onBlur?(event: Event | SyntheticEvent): void;
60
- onType?(value: string): Promise<void>;
61
- optionsFilter?(options: Value[], query: string): Value[];
62
- onOpen?(): void;
63
- compareValuesOnChange?(v1: Value | undefined, v2: Value | undefined): boolean;
64
- // Для избежания проблем юзайте useCallback на эти функции
65
- // или выносите их из компонента (чтобы не было сайдэфектов от перерендеринга их)
66
- convertValueToString?(value: Value): string | undefined;
67
- convertValueToReactNode?(value: Value, isDisabled: boolean): ReactNode;
68
- convertValueToId?(value: Value): string | undefined;
69
- }
70
-
71
- export function Select<Value>({
72
- options,
73
- value,
74
- defaultOptionLabel,
75
- debounceTime = 400,
76
- optionsMode = 'normal',
77
- noMatchesLabel,
78
- loadingLabel,
79
- tweakStyles,
80
- testId,
81
- isDisabled,
82
- dropdownOptions,
83
- minSymbolsCountToOpenList = 0,
84
- dropdownIcon = 'chevron-down',
85
- shouldScrollToList = true,
86
- searchInput,
87
- onChange,
88
- onFocus,
89
- onBlur,
90
- onType,
91
- onOpen,
92
- isOptionDisabled = defaultIsOptionDisabled,
93
- compareValuesOnChange = defaultCompareFunction,
94
- convertValueToString = defaultConvertFunction,
95
- convertValueToId,
96
- convertValueToReactNode,
97
- optionsFilter,
98
- ...inputProps
99
- }: ISelectProps<Value>): JSX.Element {
100
- const { classes, componentStyles } = useTheme('Select', styles, tweakStyles);
101
- const isMounted = useIsMounted();
102
- const [isListOpen, setIsListOpen] = useState(false);
103
- const [areOptionsLoading, setAreOptionsLoading] = useState(false);
104
- const hasDefaultOption = defaultOptionLabel !== undefined;
105
-
106
- const [focusedListCellIndex, setFocusedListCellIndex] = useState(-1);
107
- const [searchValue, setSearchValue] = useState('');
108
-
109
- // если мы ввели что то в строку поиска - то этот булеан будет отключаться
110
- // вынесен отдельно, из-за проблем с дебаунсом при динамич. опциях
111
- const [shouldShowDefaultOption, setShouldShowDefaultOption] = useState(true);
112
-
113
- const inputWrapper = useRef<HTMLDivElement>(null);
114
- const list = useRef<HTMLDivElement>(null);
115
- const input = useRef<HTMLInputElement>(null); // TODO ref снаружи?
116
-
117
- const shouldRenderSearchInputInList =
118
- searchInput?.shouldRenderInList === true;
119
-
120
- const hasSearchInputInList =
121
- optionsMode !== 'normal' && shouldRenderSearchInputInList;
122
-
123
- const stringValue = isNotEmpty(value)
124
- ? convertValueToString(value)
125
- : undefined;
126
-
127
- const filteredOptions = useMemo(() => {
128
- if (optionsMode !== 'search') {
129
- return options;
130
- }
131
- if (isNotEmpty(optionsFilter)) {
132
- return optionsFilter(options, searchValue);
133
- }
134
- const lowerCaseValue = searchValue?.toLowerCase();
135
- return options.filter((option) => {
136
- const convertedOption = convertValueToString(option);
137
- return (
138
- convertedOption !== undefined &&
139
- convertedOption.toLowerCase().includes(lowerCaseValue)
140
- );
141
- });
142
- }, [optionsMode, optionsFilter, options, convertValueToString, searchValue]);
143
-
144
- useEffect(() => {
145
- setFocusedListCellIndex(
146
- getActiveValueIndex(filteredOptions, value, convertValueToString),
147
- );
148
- }, [filteredOptions, value, convertValueToString]);
149
-
150
- const handleListClose = useCallback(
151
- (event: Event | SyntheticEvent) => {
152
- setIsListOpen(false);
153
- setSearchValue('');
154
- setShouldShowDefaultOption(true);
155
- onBlur?.(event);
156
- },
157
- [onBlur],
158
- );
159
-
160
- const handleListOpen = () => {
161
- if (!isListOpen) {
162
- setIsListOpen(true);
163
- }
164
- };
165
-
166
- const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
167
- onFocus?.(event);
168
- handleListOpen();
169
- };
170
-
171
- const handleOnClick = () => {
172
- handleListOpen();
173
- };
174
-
175
- const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
176
- // Когда что-то блокирует открытие листа, но блур все равно должен сработать
177
- // например minSymbolsCount
178
- if (isListOpen && !isOpen) {
179
- handleListClose(event);
180
- return;
181
- }
182
-
183
- if (
184
- !isNotEmpty(event.relatedTarget) ||
185
- !isNotEmpty(list.current) ||
186
- !isNotEmpty(inputWrapper.current)
187
- ) {
188
- return;
189
- }
190
-
191
- const isActionInsideSelect =
192
- hasExactParent(event.relatedTarget as Element, list.current) ||
193
- hasExactParent(event.relatedTarget as Element, inputWrapper.current);
194
-
195
- // Ниче не делаем если клик был внутри селекта
196
- if (!isActionInsideSelect) {
197
- handleListClose(event);
198
- }
199
- };
200
-
201
- const handleOnChange = useCallback(
202
- (newValue: Value | undefined) => {
203
- const areValuesEqual = compareValuesOnChange(value, newValue);
204
- if (areValuesEqual) {
205
- return;
206
- }
207
- onChange(newValue);
208
- },
209
- [value, compareValuesOnChange, onChange],
210
- );
211
-
212
- const handleOptionSelect = useCallback(
213
- (index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
214
- handleOnChange(index === -1 ? undefined : filteredOptions[index]);
215
- handleListClose(event);
216
- input.current?.blur();
217
- },
218
- [handleOnChange, handleListClose, filteredOptions],
219
- );
220
-
221
- const handleOnType = useCallback(
222
- async (v: string) => {
223
- if (onType === undefined) {
224
- return;
225
- }
226
- if (isMounted()) {
227
- setAreOptionsLoading(true);
228
- }
229
- await onType(v);
230
- if (isMounted()) {
231
- setAreOptionsLoading(false);
232
- }
233
- if (optionsMode === 'dynamic') {
234
- setShouldShowDefaultOption(v === '');
235
- }
236
- },
237
- [onType, optionsMode],
238
- );
239
-
240
- const debounceHandleOnType = useCallback(
241
- debounce(handleOnType, debounceTime),
242
- [handleOnType, debounceTime],
243
- );
244
-
245
- const handleInputChange = (v: string) => {
246
- if (onType !== undefined) {
247
- debounceHandleOnType(v);
248
- }
249
-
250
- if (optionsMode !== 'dynamic') {
251
- setShouldShowDefaultOption(v === '');
252
- }
253
-
254
- if (v === '' && !hasSearchInputInList) {
255
- handleOnChange(undefined);
256
- }
257
-
258
- setSearchValue(v);
259
- };
260
-
261
- const handleKeyDown = (event: KeyboardEvent) => {
262
- if (!isListOpen) {
263
- return;
264
- }
265
-
266
- event.stopPropagation();
267
-
268
- switch (event.code) {
269
- case 'Enter':
270
- case 'NumpadEnter': {
271
- let indexToClick = focusedListCellIndex;
272
-
273
- // если осталось одна опция в списке,
274
- // то выбираем ее нажатием на enter
275
- if (indexToClick === -1 && filteredOptions.length === 1) {
276
- indexToClick = 0;
277
- }
278
-
279
- handleOptionSelect(indexToClick, event);
280
- break;
281
- }
282
-
283
- case 'ArrowDown': {
284
- // чтобы убрать перемещение курсора в инпуте
285
- event.preventDefault();
286
- let newIndex: number = focusedListCellIndex ?? -1;
287
- // ищем первый незадизейбленный вариант, либо дефолтную опцию
288
- for (let i = newIndex; i < newIndex + filteredOptions.length; i++) {
289
- const targetIndex = (i + 1) % filteredOptions.length;
290
- if (
291
- shouldShowDefaultOption &&
292
- hasDefaultOption &&
293
- newIndex > -1 &&
294
- targetIndex === 0
295
- ) {
296
- newIndex = -1;
297
- break;
298
- }
299
- if (!isOptionDisabled(filteredOptions[targetIndex])) {
300
- newIndex = targetIndex;
301
- break;
302
- }
303
- }
304
- setFocusedListCellIndex(newIndex);
305
- break;
306
- }
307
-
308
- case 'ArrowUp': {
309
- // чтобы убрать перемещение курсора в инпуте
310
- event.preventDefault();
311
- const newIndex: number =
312
- (focusedListCellIndex ?? -1) === -1 ? 0 : focusedListCellIndex;
313
- // ищем первый незадизейбленный вариант, либо дефолтную опцию
314
- for (let i = newIndex; i > newIndex - filteredOptions.length; i--) {
315
- const targetIndex = (i > 0 ? i : filteredOptions.length + i) - 1;
316
- if (
317
- shouldShowDefaultOption &&
318
- hasDefaultOption &&
319
- focusedListCellIndex !== -1 &&
320
- targetIndex === filteredOptions.length - 1
321
- ) {
322
- // не выносить сет наружу (приведет к багу)
323
- setFocusedListCellIndex(-1);
324
- break;
325
- }
326
- if (!isOptionDisabled(filteredOptions[targetIndex])) {
327
- // не выносить сет наружу (приведет к багу)
328
- setFocusedListCellIndex(targetIndex);
329
- break;
330
- }
331
- }
332
- break;
333
- }
334
- }
335
- };
336
-
337
- const onArrowClick = () => {
338
- if (isListOpen) {
339
- input.current?.blur();
340
- } else {
341
- input.current?.focus();
342
- }
343
- };
344
-
345
- useOnClickOutsideWithRef(list, handleListClose, inputWrapper);
346
-
347
- const hasEnoughSymbolsToSearch =
348
- searchValue.trim().length >= minSymbolsCountToOpenList;
349
-
350
- const isOpen =
351
- // Пользователь пытается открыть лист
352
- isListOpen &&
353
- // Нам есть что показать:
354
- // Есть опции
355
- (filteredOptions.length > 0 ||
356
- // Дефолтная опция
357
- (defaultOptionLabel !== undefined && !hasEnoughSymbolsToSearch) ||
358
- // Текст "Загрузка..."
359
- inputProps.isLoading ||
360
- // Текст "Совпадений не найдено"
361
- noMatchesLabel !== undefined ||
362
- // У нас есть инпут с поиском внутри листа
363
- hasSearchInputInList) &&
364
- // Последняя проверка на случай, если мы че то ищем в опциях
365
- (optionsMode === 'normal' || hasEnoughSymbolsToSearch);
366
-
367
- const { isReadonly = true } = inputProps;
368
- const shouldUsePointerCursor =
369
- (optionsMode === 'normal' || shouldRenderSearchInputInList) && isReadonly;
370
-
371
- const tweakInputStyles = useMemo(
372
- () =>
373
- merge(
374
- {},
375
- componentStyles.tweakInput,
376
- {
377
- ...(shouldUsePointerCursor
378
- ? { input: { cursor: 'pointer' } }
379
- : undefined),
380
- },
381
- tweakStyles?.tweakInput,
382
- ) as Styles,
383
- [tweakStyles?.tweakInput, shouldUsePointerCursor],
384
- );
385
-
386
- const tweakSearchInputStyles = useTweakStyles(
387
- componentStyles,
388
- tweakStyles,
389
- 'tweakSearchInput',
390
- );
391
-
392
- // Эти значения ставятся в false по дефолту также в useDropdown
393
- const {
394
- shouldUsePopper = false,
395
- shouldRenderInBody = false,
396
- shouldHideOnScroll = false,
397
- } = dropdownOptions ?? {};
398
-
399
- const popperData = useDropdown({
400
- isOpen,
401
- onDropdownClose: handleListClose,
402
- referenceElement: inputWrapper.current,
403
- dropdownElement: list.current,
404
- options: dropdownOptions,
405
- dependenciesForPositionUpdating: [
406
- inputProps.isLoading,
407
- filteredOptions.length,
408
- ],
409
- });
410
-
411
- useEffect(() => {
412
- if (isOpen && onOpen !== undefined) {
413
- onOpen();
414
- }
415
- }, [isOpen, onOpen]);
416
-
417
- const listEl = (
418
- <div
419
- className={clsx(classes.listWrapper, {
420
- [classes.withoutPopper]: !shouldUsePopper,
421
- [classes.listWrapperInBody]: shouldRenderInBody,
422
- })}
423
- ref={list}
424
- style={popperData?.styles.popper as Styles}
425
- onBlur={handleBlur} // обработка для Tab из списка
426
- {...popperData?.attributes.popper}
427
- >
428
- {isOpen && (
429
- <SelectList
430
- options={filteredOptions}
431
- defaultOptionLabel={
432
- hasDefaultOption && shouldShowDefaultOption
433
- ? defaultOptionLabel
434
- : undefined
435
- }
436
- customListHeader={
437
- hasSearchInputInList ? (
438
- <SearchInput
439
- value={searchValue}
440
- onChange={handleInputChange}
441
- tweakStyles={tweakSearchInputStyles}
442
- placeholder="Поиск"
443
- {...searchInput}
444
- />
445
- ) : undefined
446
- }
447
- noMatchesLabel={noMatchesLabel}
448
- focusedIndex={focusedListCellIndex}
449
- activeValue={value}
450
- isLoading={inputProps.isLoading}
451
- loadingLabel={loadingLabel}
452
- tweakStyles={tweakStyles?.tweakSelectList as Styles}
453
- testId={getTestId(testId, 'list')}
454
- // скролл не работает с включеным поппером
455
- shouldScrollToList={
456
- shouldScrollToList && !shouldUsePopper && !shouldHideOnScroll
457
- }
458
- isOptionDisabled={isOptionDisabled}
459
- convertValueToString={convertValueToString}
460
- convertValueToReactNode={convertValueToReactNode}
461
- convertValueToId={convertValueToId}
462
- onOptionClick={handleOptionSelect}
463
- />
464
- )}
465
- </div>
466
- );
467
-
468
- return (
469
- <div className={classes.root} onKeyDown={handleKeyDown}>
470
- <div
471
- className={clsx(classes.inputWrapper, isDisabled && classes.disabled)}
472
- onClick={isDisabled ? undefined : handleOnClick}
473
- ref={inputWrapper}
474
- >
475
- <Input
476
- value={
477
- searchValue !== '' && !shouldRenderSearchInputInList
478
- ? searchValue
479
- : stringValue
480
- }
481
- onChange={handleInputChange}
482
- isActive={isListOpen}
483
- isReadonly={optionsMode === 'normal' || shouldRenderSearchInputInList}
484
- onFocus={handleFocus}
485
- onBlur={handleBlur}
486
- isDisabled={isDisabled}
487
- ref={input}
488
- isLoading={areOptionsLoading}
489
- tweakStyles={tweakInputStyles}
490
- testId={testId}
491
- {...inputProps}
492
- />
493
- <div
494
- onMouseDown={(event: MouseEvent) => {
495
- event.preventDefault();
496
- }}
497
- onClick={onArrowClick}
498
- className={clsx(classes.arrow, isOpen && classes.activeArrow)}
499
- >
500
- <Icon type={dropdownIcon} />
501
- </div>
502
- </div>
503
- {shouldUsePopper ? (
504
- <Portal
505
- container={shouldRenderInBody ? document.body : inputWrapper.current}
506
- >
507
- <>{listEl}</>
508
- </Portal>
509
- ) : (
510
- <>{isOpen && listEl}</>
511
- )}
512
- </div>
513
- );
514
- }
1
+ import {
2
+ ReactNode,
3
+ FocusEvent,
4
+ KeyboardEvent,
5
+ MouseEvent,
6
+ useCallback,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ SyntheticEvent,
12
+ } from 'react';
13
+ import { Styles } from 'jss';
14
+ import clsx from 'clsx';
15
+ import { merge } from 'lodash';
16
+ import { debounce } from 'ts-debounce';
17
+ import { Portal } from 'react-overlays';
18
+ import { SelectList } from './SelectList';
19
+ import { IInputProps, Input } from '../Input';
20
+ import { IIconType, Icon } from '../Icon';
21
+ import {
22
+ useIsMounted,
23
+ useTheme,
24
+ useOnClickOutsideWithRef,
25
+ useDropdown,
26
+ useTweakStyles,
27
+ } from '../../hooks';
28
+ import { IDropdownWithPopperOptions } from '../../types';
29
+ import { getTestId, hasExactParent, isNotEmpty } from '../../helpers';
30
+ import {
31
+ defaultConvertFunction,
32
+ defaultCompareFunction,
33
+ getActiveValueIndex,
34
+ defaultIsOptionDisabled,
35
+ } from './helpers';
36
+ import { SelectStyles, styles } from './Select.styles';
37
+ import { ISearchInputProps, SearchInput } from '../SearchInput';
38
+
39
+ export interface ISelectProps<Value>
40
+ extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type'> {
41
+ tweakStyles?: SelectStyles;
42
+ defaultOptionLabel?: string;
43
+ noMatchesLabel?: string;
44
+ loadingLabel?: ReactNode;
45
+ optionsMode?: 'search' | 'dynamic' | 'normal';
46
+ debounceTime?: number;
47
+ minSymbolsCountToOpenList?: number;
48
+ dropdownOptions?: IDropdownWithPopperOptions;
49
+ dropdownIcon?: IIconType;
50
+ options: Value[];
51
+ value: Value | undefined;
52
+ shouldScrollToList?: boolean;
53
+ searchInput?: { shouldRenderInList: true } & Pick<
54
+ ISearchInputProps,
55
+ 'placeholder'
56
+ >;
57
+ isOptionDisabled?(option: Value): boolean;
58
+ onChange(value: Value | undefined): void; // подумать как возвращать индекс
59
+ onBlur?(event: Event | SyntheticEvent): void;
60
+ onType?(value: string): Promise<void>;
61
+ optionsFilter?(options: Value[], query: string): Value[];
62
+ onOpen?(): void;
63
+ compareValuesOnChange?(v1: Value | undefined, v2: Value | undefined): boolean;
64
+ // Для избежания проблем юзайте useCallback на эти функции
65
+ // или выносите их из компонента (чтобы не было сайдэфектов от перерендеринга их)
66
+ convertValueToString?(value: Value): string | undefined;
67
+ convertValueToReactNode?(value: Value, isDisabled: boolean): ReactNode;
68
+ convertValueToId?(value: Value): string | undefined;
69
+ }
70
+
71
+ export function Select<Value>({
72
+ options,
73
+ value,
74
+ defaultOptionLabel,
75
+ debounceTime = 400,
76
+ optionsMode = 'normal',
77
+ noMatchesLabel,
78
+ loadingLabel,
79
+ tweakStyles,
80
+ testId,
81
+ isReadonly,
82
+ isDisabled,
83
+ dropdownOptions,
84
+ minSymbolsCountToOpenList = 0,
85
+ dropdownIcon = 'chevron-down',
86
+ shouldScrollToList = true,
87
+ searchInput,
88
+ onChange,
89
+ onFocus,
90
+ onBlur,
91
+ onType,
92
+ onOpen,
93
+ isOptionDisabled = defaultIsOptionDisabled,
94
+ compareValuesOnChange = defaultCompareFunction,
95
+ convertValueToString = defaultConvertFunction,
96
+ convertValueToId,
97
+ convertValueToReactNode,
98
+ optionsFilter,
99
+ ...inputProps
100
+ }: ISelectProps<Value>): JSX.Element {
101
+ const { classes, componentStyles } = useTheme('Select', styles, tweakStyles);
102
+ const isMounted = useIsMounted();
103
+ const [isListOpen, setIsListOpen] = useState(false);
104
+ const [areOptionsLoading, setAreOptionsLoading] = useState(false);
105
+ const hasDefaultOption = defaultOptionLabel !== undefined;
106
+
107
+ const [focusedListCellIndex, setFocusedListCellIndex] = useState(-1);
108
+ const [searchValue, setSearchValue] = useState('');
109
+
110
+ // если мы ввели что то в строку поиска - то этот булеан будет отключаться
111
+ // вынесен отдельно, из-за проблем с дебаунсом при динамич. опциях
112
+ const [shouldShowDefaultOption, setShouldShowDefaultOption] = useState(true);
113
+
114
+ const inputWrapper = useRef<HTMLDivElement>(null);
115
+ const list = useRef<HTMLDivElement>(null);
116
+ const input = useRef<HTMLInputElement>(null); // TODO ref снаружи?
117
+
118
+ const shouldRenderSearchInputInList =
119
+ searchInput?.shouldRenderInList === true;
120
+
121
+ const hasSearchInputInList =
122
+ optionsMode !== 'normal' && shouldRenderSearchInputInList;
123
+
124
+ const stringValue = isNotEmpty(value)
125
+ ? convertValueToString(value)
126
+ : undefined;
127
+
128
+ const filteredOptions = useMemo(() => {
129
+ if (optionsMode !== 'search') {
130
+ return options;
131
+ }
132
+ if (isNotEmpty(optionsFilter)) {
133
+ return optionsFilter(options, searchValue);
134
+ }
135
+ const lowerCaseValue = searchValue?.toLowerCase();
136
+ return options.filter((option) => {
137
+ const convertedOption = convertValueToString(option);
138
+ return (
139
+ convertedOption !== undefined &&
140
+ convertedOption.toLowerCase().includes(lowerCaseValue)
141
+ );
142
+ });
143
+ }, [optionsMode, optionsFilter, options, convertValueToString, searchValue]);
144
+
145
+ useEffect(() => {
146
+ setFocusedListCellIndex(
147
+ getActiveValueIndex(filteredOptions, value, convertValueToString),
148
+ );
149
+ }, [filteredOptions, value, convertValueToString]);
150
+
151
+ const handleListClose = useCallback(
152
+ (event: Event | SyntheticEvent) => {
153
+ setIsListOpen(false);
154
+ setSearchValue('');
155
+ setShouldShowDefaultOption(true);
156
+ onBlur?.(event);
157
+ },
158
+ [onBlur],
159
+ );
160
+
161
+ const handleListOpen = () => {
162
+ if (!isListOpen) {
163
+ setIsListOpen(true);
164
+ }
165
+ };
166
+
167
+ const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
168
+ onFocus?.(event);
169
+ handleListOpen();
170
+ };
171
+
172
+ const handleOnClick = () => {
173
+ handleListOpen();
174
+ };
175
+
176
+ const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
177
+ // Когда что-то блокирует открытие листа, но блур все равно должен сработать
178
+ // например minSymbolsCount
179
+ if (isListOpen && !isOpen) {
180
+ handleListClose(event);
181
+ return;
182
+ }
183
+
184
+ if (
185
+ !isNotEmpty(event.relatedTarget) ||
186
+ !isNotEmpty(list.current) ||
187
+ !isNotEmpty(inputWrapper.current)
188
+ ) {
189
+ return;
190
+ }
191
+
192
+ const isActionInsideSelect =
193
+ hasExactParent(event.relatedTarget as Element, list.current) ||
194
+ hasExactParent(event.relatedTarget as Element, inputWrapper.current);
195
+
196
+ // Ниче не делаем если клик был внутри селекта
197
+ if (!isActionInsideSelect) {
198
+ handleListClose(event);
199
+ }
200
+ };
201
+
202
+ const handleOnChange = useCallback(
203
+ (newValue: Value | undefined) => {
204
+ const areValuesEqual = compareValuesOnChange(value, newValue);
205
+ if (areValuesEqual) {
206
+ return;
207
+ }
208
+ onChange(newValue);
209
+ },
210
+ [value, compareValuesOnChange, onChange],
211
+ );
212
+
213
+ const handleOptionSelect = useCallback(
214
+ (index: number, event: MouseEvent<HTMLElement> | KeyboardEvent) => {
215
+ handleOnChange(index === -1 ? undefined : filteredOptions[index]);
216
+ handleListClose(event);
217
+ input.current?.blur();
218
+ },
219
+ [handleOnChange, handleListClose, filteredOptions],
220
+ );
221
+
222
+ const handleOnType = useCallback(
223
+ async (v: string) => {
224
+ if (onType === undefined) {
225
+ return;
226
+ }
227
+ if (isMounted()) {
228
+ setAreOptionsLoading(true);
229
+ }
230
+ await onType(v);
231
+ if (isMounted()) {
232
+ setAreOptionsLoading(false);
233
+ }
234
+ if (optionsMode === 'dynamic') {
235
+ setShouldShowDefaultOption(v === '');
236
+ }
237
+ },
238
+ [onType, optionsMode],
239
+ );
240
+
241
+ const debounceHandleOnType = useCallback(
242
+ debounce(handleOnType, debounceTime),
243
+ [handleOnType, debounceTime],
244
+ );
245
+
246
+ const handleInputChange = (v: string) => {
247
+ if (onType !== undefined) {
248
+ debounceHandleOnType(v);
249
+ }
250
+
251
+ if (optionsMode !== 'dynamic') {
252
+ setShouldShowDefaultOption(v === '');
253
+ }
254
+
255
+ if (v === '' && !hasSearchInputInList) {
256
+ handleOnChange(undefined);
257
+ }
258
+
259
+ setSearchValue(v);
260
+ };
261
+
262
+ const handleKeyDown = (event: KeyboardEvent) => {
263
+ if (!isListOpen) {
264
+ return;
265
+ }
266
+
267
+ event.stopPropagation();
268
+
269
+ switch (event.code) {
270
+ case 'Enter':
271
+ case 'NumpadEnter': {
272
+ let indexToClick = focusedListCellIndex;
273
+
274
+ // если осталось одна опция в списке,
275
+ // то выбираем ее нажатием на enter
276
+ if (indexToClick === -1 && filteredOptions.length === 1) {
277
+ indexToClick = 0;
278
+ }
279
+
280
+ handleOptionSelect(indexToClick, event);
281
+ break;
282
+ }
283
+
284
+ case 'ArrowDown': {
285
+ // чтобы убрать перемещение курсора в инпуте
286
+ event.preventDefault();
287
+ let newIndex: number = focusedListCellIndex ?? -1;
288
+ // ищем первый незадизейбленный вариант, либо дефолтную опцию
289
+ for (let i = newIndex; i < newIndex + filteredOptions.length; i++) {
290
+ const targetIndex = (i + 1) % filteredOptions.length;
291
+ if (
292
+ shouldShowDefaultOption &&
293
+ hasDefaultOption &&
294
+ newIndex > -1 &&
295
+ targetIndex === 0
296
+ ) {
297
+ newIndex = -1;
298
+ break;
299
+ }
300
+ if (!isOptionDisabled(filteredOptions[targetIndex])) {
301
+ newIndex = targetIndex;
302
+ break;
303
+ }
304
+ }
305
+ setFocusedListCellIndex(newIndex);
306
+ break;
307
+ }
308
+
309
+ case 'ArrowUp': {
310
+ // чтобы убрать перемещение курсора в инпуте
311
+ event.preventDefault();
312
+ const newIndex: number =
313
+ (focusedListCellIndex ?? -1) === -1 ? 0 : focusedListCellIndex;
314
+ // ищем первый незадизейбленный вариант, либо дефолтную опцию
315
+ for (let i = newIndex; i > newIndex - filteredOptions.length; i--) {
316
+ const targetIndex = (i > 0 ? i : filteredOptions.length + i) - 1;
317
+ if (
318
+ shouldShowDefaultOption &&
319
+ hasDefaultOption &&
320
+ focusedListCellIndex !== -1 &&
321
+ targetIndex === filteredOptions.length - 1
322
+ ) {
323
+ // не выносить сет наружу (приведет к багу)
324
+ setFocusedListCellIndex(-1);
325
+ break;
326
+ }
327
+ if (!isOptionDisabled(filteredOptions[targetIndex])) {
328
+ // не выносить сет наружу (приведет к багу)
329
+ setFocusedListCellIndex(targetIndex);
330
+ break;
331
+ }
332
+ }
333
+ break;
334
+ }
335
+ }
336
+ };
337
+
338
+ const onArrowClick = () => {
339
+ if (isListOpen) {
340
+ input.current?.blur();
341
+ } else {
342
+ input.current?.focus();
343
+ }
344
+ };
345
+
346
+ useOnClickOutsideWithRef(list, handleListClose, inputWrapper);
347
+
348
+ const hasEnoughSymbolsToSearch =
349
+ searchValue.trim().length >= minSymbolsCountToOpenList;
350
+
351
+ const isOpen =
352
+ // Пользователь пытается открыть лист
353
+ isListOpen &&
354
+ // Нам есть что показать:
355
+ // Есть опции
356
+ (filteredOptions.length > 0 ||
357
+ // Дефолтная опция
358
+ (defaultOptionLabel !== undefined && !hasEnoughSymbolsToSearch) ||
359
+ // Текст "Загрузка..."
360
+ inputProps.isLoading ||
361
+ // Текст "Совпадений не найдено"
362
+ noMatchesLabel !== undefined ||
363
+ // У нас есть инпут с поиском внутри листа
364
+ hasSearchInputInList) &&
365
+ // Последняя проверка на случай, если мы че то ищем в опциях
366
+ (optionsMode === 'normal' || hasEnoughSymbolsToSearch);
367
+
368
+ const hasReadonlyInput =
369
+ isReadonly || optionsMode === 'normal' || shouldRenderSearchInputInList;
370
+
371
+ const tweakInputStyles = useMemo(
372
+ () =>
373
+ merge(
374
+ {},
375
+ componentStyles.tweakInput,
376
+ { ...(hasReadonlyInput && { input: { cursor: 'pointer' } }) },
377
+ tweakStyles?.tweakInput,
378
+ ) as Styles,
379
+ [tweakStyles?.tweakInput, hasReadonlyInput],
380
+ );
381
+
382
+ const tweakSearchInputStyles = useTweakStyles(
383
+ componentStyles,
384
+ tweakStyles,
385
+ 'tweakSearchInput',
386
+ );
387
+
388
+ // Эти значения ставятся в false по дефолту также в useDropdown
389
+ const {
390
+ shouldUsePopper = false,
391
+ shouldRenderInBody = false,
392
+ shouldHideOnScroll = false,
393
+ } = dropdownOptions ?? {};
394
+
395
+ const popperData = useDropdown({
396
+ isOpen,
397
+ onDropdownClose: handleListClose,
398
+ referenceElement: inputWrapper.current,
399
+ dropdownElement: list.current,
400
+ options: dropdownOptions,
401
+ dependenciesForPositionUpdating: [
402
+ inputProps.isLoading,
403
+ filteredOptions.length,
404
+ ],
405
+ });
406
+
407
+ useEffect(() => {
408
+ if (isOpen && onOpen !== undefined) {
409
+ onOpen();
410
+ }
411
+ }, [isOpen, onOpen]);
412
+
413
+ const listEl = (
414
+ <div
415
+ className={clsx(classes.listWrapper, {
416
+ [classes.withoutPopper]: !shouldUsePopper,
417
+ [classes.listWrapperInBody]: shouldRenderInBody,
418
+ })}
419
+ ref={list}
420
+ style={popperData?.styles.popper as Styles}
421
+ onBlur={handleBlur} // обработка для Tab из списка
422
+ {...popperData?.attributes.popper}
423
+ >
424
+ {isOpen && (
425
+ <SelectList
426
+ options={filteredOptions}
427
+ defaultOptionLabel={
428
+ hasDefaultOption && shouldShowDefaultOption
429
+ ? defaultOptionLabel
430
+ : undefined
431
+ }
432
+ customListHeader={
433
+ hasSearchInputInList ? (
434
+ <SearchInput
435
+ value={searchValue}
436
+ onChange={handleInputChange}
437
+ tweakStyles={tweakSearchInputStyles}
438
+ placeholder="Поиск"
439
+ {...searchInput}
440
+ />
441
+ ) : undefined
442
+ }
443
+ noMatchesLabel={noMatchesLabel}
444
+ focusedIndex={focusedListCellIndex}
445
+ activeValue={value}
446
+ isLoading={inputProps.isLoading}
447
+ loadingLabel={loadingLabel}
448
+ tweakStyles={tweakStyles?.tweakSelectList as Styles}
449
+ testId={getTestId(testId, 'list')}
450
+ // скролл не работает с включеным поппером
451
+ shouldScrollToList={
452
+ shouldScrollToList && !shouldUsePopper && !shouldHideOnScroll
453
+ }
454
+ isOptionDisabled={isOptionDisabled}
455
+ convertValueToString={convertValueToString}
456
+ convertValueToReactNode={convertValueToReactNode}
457
+ convertValueToId={convertValueToId}
458
+ onOptionClick={handleOptionSelect}
459
+ />
460
+ )}
461
+ </div>
462
+ );
463
+
464
+ return (
465
+ <div className={classes.root} onKeyDown={handleKeyDown}>
466
+ <div
467
+ className={clsx(classes.inputWrapper, isDisabled && classes.disabled)}
468
+ onClick={isDisabled ? undefined : handleOnClick}
469
+ ref={inputWrapper}
470
+ >
471
+ <Input
472
+ value={
473
+ searchValue !== '' && !shouldRenderSearchInputInList
474
+ ? searchValue
475
+ : stringValue
476
+ }
477
+ onChange={handleInputChange}
478
+ isActive={isListOpen}
479
+ isReadonly={hasReadonlyInput}
480
+ onFocus={handleFocus}
481
+ onBlur={handleBlur}
482
+ isDisabled={isDisabled}
483
+ ref={input}
484
+ isLoading={areOptionsLoading}
485
+ tweakStyles={tweakInputStyles}
486
+ testId={testId}
487
+ {...inputProps}
488
+ />
489
+ <div
490
+ onMouseDown={(event: MouseEvent) => {
491
+ event.preventDefault();
492
+ }}
493
+ onClick={onArrowClick}
494
+ className={clsx(classes.arrow, isOpen && classes.activeArrow)}
495
+ >
496
+ <Icon type={dropdownIcon} />
497
+ </div>
498
+ </div>
499
+ {shouldUsePopper ? (
500
+ <Portal
501
+ container={shouldRenderInBody ? document.body : inputWrapper.current}
502
+ >
503
+ <>{listEl}</>
504
+ </Portal>
505
+ ) : (
506
+ <>{isOpen && listEl}</>
507
+ )}
508
+ </div>
509
+ );
510
+ }