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