@true-engineering/true-react-common-ui-kit 3.0.3 → 3.0.5

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