@alfalab/core-components-input-autocomplete 9.4.7 → 10.0.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 (237) hide show
  1. package/Component-5e1b8383.d.ts +3 -3
  2. package/Component-63dec22f.d.ts +11 -0
  3. package/Component-89a3cf4c.d.ts +6 -0
  4. package/Component-96988a65.d.ts +9 -0
  5. package/Component-bdb4c6b9.d.ts +10 -7
  6. package/Component-e81c389f.d.ts +5 -0
  7. package/Component-ebda875c.d.ts +222 -54
  8. package/Component-f12ee135.d.ts +5 -4
  9. package/Component.desktop-ebda875c.d.ts +6 -0
  10. package/Component.desktop.d.ts +11 -65
  11. package/Component.desktop.js +4 -4
  12. package/Component.mobile-f12ee135.d.ts +41 -0
  13. package/{esm/Component.mobile-e81c389f.d.ts → Component.mobile-f1f15074.d.ts} +2 -6
  14. package/Component.mobile.d.ts +7 -7
  15. package/Component.mobile.js +7 -7
  16. package/Component.modal.mobile.d.ts +8 -40
  17. package/Component.modal.mobile.js +5 -5
  18. package/Component.responsive.d.ts +12 -70
  19. package/Component.responsive.js +7 -5
  20. package/autocomplete-field/Component.d.ts +1 -1
  21. package/autocomplete-field/Component.js +3 -3
  22. package/autocomplete-field/index.css +2 -2
  23. package/autocomplete-field/index.js +1 -1
  24. package/autocomplete-mobile-field/Component.d.ts +4 -4
  25. package/autocomplete-mobile-field/Component.js +7 -7
  26. package/autocomplete-mobile-field/index.css +8 -8
  27. package/autocomplete-mobile-field/index.js +1 -1
  28. package/cssm/Component-5e1b8383.d.ts +70 -3
  29. package/{Component-3885b0d7.d.ts → cssm/Component-63dec22f.d.ts} +24 -26
  30. package/cssm/Component-89a3cf4c.d.ts +6 -0
  31. package/cssm/Component-bdb4c6b9.d.ts +10 -7
  32. package/cssm/Component-e81c389f.d.ts +5 -0
  33. package/cssm/Component-ebda875c.d.ts +9 -98
  34. package/cssm/Component.desktop-ebda875c.d.ts +6 -0
  35. package/cssm/Component.desktop.d.ts +11 -65
  36. package/cssm/Component.desktop.js +4 -4
  37. package/cssm/Component.mobile-06ffd24b.d.ts +41 -0
  38. package/cssm/Component.mobile-ebda875c.d.ts +6 -0
  39. package/cssm/{Component.mobile-e81c389f.d.ts → Component.mobile-f1f15074.d.ts} +2 -6
  40. package/cssm/Component.mobile.d.ts +6 -6
  41. package/cssm/Component.mobile.js +6 -6
  42. package/cssm/Component.modal.mobile-5e1b8383.d.ts +60 -0
  43. package/cssm/Component.modal.mobile.d.ts +7 -39
  44. package/cssm/Component.modal.mobile.js +4 -4
  45. package/cssm/Component.responsive.d.ts +11 -69
  46. package/cssm/Component.responsive.js +6 -4
  47. package/cssm/autocomplete-field/Component.d.ts +1 -1
  48. package/cssm/autocomplete-field/Component.js +2 -2
  49. package/cssm/autocomplete-field/index.js +1 -1
  50. package/cssm/autocomplete-mobile-field/Component.d.ts +4 -4
  51. package/cssm/autocomplete-mobile-field/Component.js +6 -6
  52. package/cssm/autocomplete-mobile-field/index.js +1 -1
  53. package/cssm/component-89a3cf4c.d.ts +44 -0
  54. package/cssm/desktop-63dec22f.d.ts +6 -0
  55. package/cssm/desktop.d.ts +1 -1
  56. package/cssm/desktop.js +2 -2
  57. package/{esm/hook-b4e70cb0.d.ts → cssm/hook-9ea9e32c.d.ts} +12 -10
  58. package/cssm/{index-3885b0d7.d.ts → index-89a3cf4c.d.ts} +2 -2
  59. package/cssm/index-bdb4c6b9.d.ts +2 -172
  60. package/cssm/index-e81c389f.d.ts +1 -1
  61. package/cssm/index-ebda875c.d.ts +94 -56
  62. package/cssm/index-f12ee135.d.ts +3 -287
  63. package/cssm/index.d.ts +2 -3
  64. package/cssm/index.js +9 -8
  65. package/cssm/mobile-06ffd24b.d.ts +5 -0
  66. package/cssm/mobile-96988a65.d.ts +3 -0
  67. package/cssm/mobile.d.ts +2 -2
  68. package/cssm/mobile.js +3 -4
  69. package/cssm/shared-4cd3936b.d.ts +93 -0
  70. package/cssm/types-d9f9bbcc.d.ts +259 -0
  71. package/cssm/typings-5e1b8383.d.ts +1 -1
  72. package/cssm/typings-9211a437.d.ts +95 -0
  73. package/cssm/utils-1574ad8b.d.ts +29 -0
  74. package/cssm/utils-49cc3c24.d.ts +8 -0
  75. package/desktop-63dec22f.d.ts +6 -0
  76. package/desktop.d.ts +1 -1
  77. package/desktop.js +2 -2
  78. package/esm/Component-5e1b8383.d.ts +70 -3
  79. package/{modern/Component-3885b0d7.d.ts → esm/Component-63dec22f.d.ts} +24 -26
  80. package/esm/Component-89a3cf4c.d.ts +6 -0
  81. package/esm/Component-bdb4c6b9.d.ts +10 -7
  82. package/esm/Component-e81c389f.d.ts +5 -0
  83. package/esm/Component-ebda875c.d.ts +9 -98
  84. package/esm/Component.desktop-ebda875c.d.ts +6 -0
  85. package/esm/Component.desktop.d.ts +11 -65
  86. package/esm/Component.desktop.js +2 -2
  87. package/esm/Component.mobile-06ffd24b.d.ts +41 -0
  88. package/esm/Component.mobile-ebda875c.d.ts +6 -0
  89. package/{Component.mobile-e81c389f.d.ts → esm/Component.mobile-f1f15074.d.ts} +2 -6
  90. package/esm/Component.mobile.d.ts +6 -6
  91. package/esm/Component.mobile.js +6 -6
  92. package/esm/Component.modal.mobile-5e1b8383.d.ts +60 -0
  93. package/esm/Component.modal.mobile.d.ts +7 -39
  94. package/esm/Component.modal.mobile.js +5 -5
  95. package/esm/Component.responsive.d.ts +11 -69
  96. package/esm/Component.responsive.js +7 -5
  97. package/esm/autocomplete-field/Component.d.ts +1 -1
  98. package/esm/autocomplete-field/Component.js +4 -4
  99. package/esm/autocomplete-field/index.css +2 -2
  100. package/esm/autocomplete-field/index.js +1 -1
  101. package/esm/autocomplete-mobile-field/Component.d.ts +4 -4
  102. package/esm/autocomplete-mobile-field/Component.js +7 -7
  103. package/esm/autocomplete-mobile-field/index.css +8 -8
  104. package/esm/autocomplete-mobile-field/index.js +1 -1
  105. package/esm/component-89a3cf4c.d.ts +44 -0
  106. package/esm/desktop-63dec22f.d.ts +6 -0
  107. package/esm/desktop.d.ts +1 -1
  108. package/esm/desktop.js +2 -2
  109. package/{hook-b4e70cb0.d.ts → esm/hook-9ea9e32c.d.ts} +12 -10
  110. package/esm/{index-3885b0d7.d.ts → index-89a3cf4c.d.ts} +2 -2
  111. package/esm/index-bdb4c6b9.d.ts +2 -172
  112. package/esm/index-e81c389f.d.ts +1 -1
  113. package/esm/index-ebda875c.d.ts +94 -56
  114. package/esm/index-f12ee135.d.ts +3 -287
  115. package/esm/index.d.ts +2 -3
  116. package/esm/index.js +10 -8
  117. package/esm/mobile-06ffd24b.d.ts +5 -0
  118. package/esm/mobile-96988a65.d.ts +3 -0
  119. package/esm/mobile.css +4 -4
  120. package/esm/mobile.d.ts +2 -2
  121. package/esm/mobile.js +4 -5
  122. package/esm/{mobile.module-2d568950.js → mobile.module-ed5594fa.js} +1 -1
  123. package/esm/shared-4cd3936b.d.ts +93 -0
  124. package/esm/types-d9f9bbcc.d.ts +259 -0
  125. package/esm/typings-5e1b8383.d.ts +1 -1
  126. package/esm/typings-9211a437.d.ts +95 -0
  127. package/esm/utils-1574ad8b.d.ts +29 -0
  128. package/esm/utils-49cc3c24.d.ts +8 -0
  129. package/{cssm/hook-b4e70cb0.d.ts → hook-9ea9e32c.d.ts} +11 -9
  130. package/hook-ebda875c.d.ts +48 -0
  131. package/{index-3885b0d7.d.ts → index-89a3cf4c.d.ts} +2 -2
  132. package/index-e81c389f.d.ts +1 -1
  133. package/index-ebda875c.d.ts +14 -80
  134. package/index-f12ee135.d.ts +38 -18
  135. package/index.d.ts +2 -3
  136. package/index.js +10 -9
  137. package/mobile-45dc17c6.d.ts +47 -0
  138. package/mobile-96988a65.d.ts +6 -0
  139. package/mobile.css +4 -4
  140. package/mobile.d.ts +2 -2
  141. package/mobile.js +4 -5
  142. package/{mobile.module-80b4093b.js → mobile.module-4fb8c86a.js} +1 -1
  143. package/modern/Component-5e1b8383.d.ts +70 -3
  144. package/{esm/Component-3885b0d7.d.ts → modern/Component-63dec22f.d.ts} +24 -26
  145. package/modern/Component-89a3cf4c.d.ts +6 -0
  146. package/modern/Component-bdb4c6b9.d.ts +10 -7
  147. package/modern/Component-e81c389f.d.ts +5 -0
  148. package/modern/Component-ebda875c.d.ts +9 -98
  149. package/modern/Component.desktop-ebda875c.d.ts +6 -0
  150. package/modern/Component.desktop.d.ts +11 -65
  151. package/modern/Component.desktop.js +2 -2
  152. package/modern/Component.mobile-06ffd24b.d.ts +41 -0
  153. package/modern/Component.mobile-ebda875c.d.ts +6 -0
  154. package/modern/{Component.mobile-e81c389f.d.ts → Component.mobile-f1f15074.d.ts} +2 -6
  155. package/modern/Component.mobile.d.ts +6 -6
  156. package/modern/Component.mobile.js +6 -6
  157. package/modern/Component.modal.mobile-5e1b8383.d.ts +60 -0
  158. package/modern/Component.modal.mobile.d.ts +7 -39
  159. package/modern/Component.modal.mobile.js +5 -5
  160. package/modern/Component.responsive.d.ts +11 -69
  161. package/modern/Component.responsive.js +7 -5
  162. package/modern/autocomplete-field/Component.d.ts +1 -1
  163. package/modern/autocomplete-field/Component.js +4 -4
  164. package/modern/autocomplete-field/index.css +2 -2
  165. package/modern/autocomplete-field/index.js +1 -1
  166. package/modern/autocomplete-mobile-field/Component.d.ts +4 -4
  167. package/modern/autocomplete-mobile-field/Component.js +7 -7
  168. package/modern/autocomplete-mobile-field/index.css +8 -8
  169. package/modern/autocomplete-mobile-field/index.js +1 -1
  170. package/modern/component-89a3cf4c.d.ts +44 -0
  171. package/modern/desktop-63dec22f.d.ts +6 -0
  172. package/modern/desktop.d.ts +1 -1
  173. package/modern/desktop.js +2 -2
  174. package/modern/{hook-b4e70cb0.d.ts → hook-9ea9e32c.d.ts} +12 -10
  175. package/modern/{index-3885b0d7.d.ts → index-89a3cf4c.d.ts} +2 -2
  176. package/modern/index-bdb4c6b9.d.ts +2 -172
  177. package/modern/index-e81c389f.d.ts +1 -1
  178. package/modern/index-ebda875c.d.ts +94 -56
  179. package/modern/index-f12ee135.d.ts +3 -287
  180. package/modern/index.d.ts +2 -3
  181. package/modern/index.js +10 -8
  182. package/modern/mobile-06ffd24b.d.ts +5 -0
  183. package/modern/mobile-96988a65.d.ts +3 -0
  184. package/modern/mobile.css +4 -4
  185. package/modern/mobile.d.ts +2 -2
  186. package/modern/mobile.js +4 -5
  187. package/modern/{mobile.module-93c1e0e2.js → mobile.module-a255d4ec.js} +1 -1
  188. package/modern/shared-4cd3936b.d.ts +93 -0
  189. package/modern/types-d9f9bbcc.d.ts +259 -0
  190. package/modern/typings-5e1b8383.d.ts +1 -1
  191. package/modern/typings-9211a437.d.ts +95 -0
  192. package/modern/utils-1574ad8b.d.ts +29 -0
  193. package/modern/utils-49cc3c24.d.ts +8 -0
  194. package/package.json +25 -5
  195. package/shared-4cd3936b.d.ts +8 -0
  196. package/src/Component.desktop.tsx +89 -0
  197. package/src/Component.mobile.tsx +251 -0
  198. package/src/Component.modal.mobile.tsx +227 -0
  199. package/src/Component.responsive.tsx +42 -0
  200. package/src/autocomplete-field/Component.tsx +91 -0
  201. package/src/autocomplete-field/index.module.css +3 -0
  202. package/src/autocomplete-field/index.ts +1 -0
  203. package/src/autocomplete-mobile-field/Component.tsx +74 -0
  204. package/src/autocomplete-mobile-field/index.module.css +37 -0
  205. package/src/autocomplete-mobile-field/index.ts +1 -0
  206. package/src/desktop.ts +1 -0
  207. package/src/index.ts +4 -0
  208. package/src/mobile.module.css +14 -0
  209. package/src/mobile.ts +5 -0
  210. package/typings-5e1b8383.d.ts +1 -1
  211. package/typings-9211a437.d.ts +95 -0
  212. package/Component-b4e70cb0.d.ts +0 -22
  213. package/cssm/Component-3885b0d7.d.ts +0 -169
  214. package/cssm/Component-425c8522.d.ts +0 -53
  215. package/cssm/Component-aed0af6e.d.ts +0 -11
  216. package/cssm/Component-b4e70cb0.d.ts +0 -22
  217. package/cssm/Component-f12ee135.d.ts +0 -71
  218. package/cssm/index-3e68f8db.d.ts +0 -5
  219. package/cssm/responsive.d.ts +0 -2
  220. package/cssm/responsive.js +0 -26
  221. package/esm/Component-425c8522.d.ts +0 -53
  222. package/esm/Component-aed0af6e.d.ts +0 -11
  223. package/esm/Component-b4e70cb0.d.ts +0 -22
  224. package/esm/Component-f12ee135.d.ts +0 -71
  225. package/esm/index-3e68f8db.d.ts +0 -5
  226. package/esm/responsive.d.ts +0 -2
  227. package/esm/responsive.js +0 -16
  228. package/index-3e68f8db.d.ts +0 -5
  229. package/modern/Component-425c8522.d.ts +0 -53
  230. package/modern/Component-aed0af6e.d.ts +0 -11
  231. package/modern/Component-b4e70cb0.d.ts +0 -22
  232. package/modern/Component-f12ee135.d.ts +0 -71
  233. package/modern/index-3e68f8db.d.ts +0 -5
  234. package/modern/responsive.d.ts +0 -2
  235. package/modern/responsive.js +0 -15
  236. package/responsive.d.ts +0 -2
  237. package/responsive.js +0 -24
@@ -0,0 +1,251 @@
1
+ import React, { ChangeEvent, ElementType, RefObject, useMemo, useRef, useState } from 'react';
2
+ import mergeRefs from 'react-merge-refs';
3
+ import cn from 'classnames';
4
+ import throttle from 'lodash.throttle';
5
+
6
+ import { BottomSheetProps } from '@alfalab/core-components-bottom-sheet';
7
+ import { ButtonMobile, ButtonMobileProps } from '@alfalab/core-components-button/mobile';
8
+ import { Input as CoreInput } from '@alfalab/core-components-input';
9
+ import { SelectMobile, SelectMobileProps } from '@alfalab/core-components-select/mobile';
10
+ import type {
11
+ BaseSelectChangePayload,
12
+ BaseSelectProps,
13
+ } from '@alfalab/core-components-select/shared';
14
+
15
+ import { AutocompleteMobileField } from './autocomplete-mobile-field';
16
+
17
+ import styles from './mobile.module.css';
18
+
19
+ export type InputAutocompleteMobileProps = Omit<
20
+ BaseSelectProps,
21
+ 'OptionsList' | 'Checkmark' | 'onScroll' | 'nativeSelect' | 'autocomplete' | 'valueRenderer'
22
+ > & {
23
+ /**
24
+ * Обработчик выбора
25
+ */
26
+ onChange: (payload: string | BaseSelectChangePayload) => void;
27
+
28
+ /**
29
+ * Обработчик ввода фильтра.
30
+ */
31
+ onFilter: (event: ChangeEvent<HTMLInputElement>) => void;
32
+
33
+ /**
34
+ * Значение поля ввода
35
+ */
36
+ value?: string;
37
+
38
+ /**
39
+ * Значение фильтра.
40
+ */
41
+ filter?: string;
42
+
43
+ /**
44
+ * Обработчик нажатия на кнопку "Отмена".
45
+ */
46
+ onCancel?: () => void;
47
+
48
+ /**
49
+ * Обработчик нажатия на крестик в инпуте фильтра.
50
+ */
51
+ onClearFilter?: () => void;
52
+
53
+ /**
54
+ * Дополнительные пропсы компонента BottomSheet
55
+ */
56
+ bottomSheetProps?: Partial<BottomSheetProps>;
57
+
58
+ /**
59
+ * Дополнительные пропсы на слот под заголовком компонента BottomSheet
60
+ */
61
+ bottomSheetHeaderAddonsProps?: Record<string, unknown>;
62
+
63
+ /**
64
+ * Дополнительные пропсы на кнопку "продолжить"
65
+ */
66
+ continueButtonProps?: ButtonMobileProps;
67
+
68
+ /**
69
+ * Дополнительные пропсы на кнопку "отмена"
70
+ */
71
+ cancelButtonProps?: ButtonMobileProps;
72
+
73
+ /**
74
+ * Кастомный инпут
75
+ */
76
+ Input?: ElementType;
77
+ };
78
+
79
+ const SELECTED: string[] = [];
80
+
81
+ export const InputAutocompleteMobile = React.forwardRef(
82
+ (
83
+ {
84
+ Input,
85
+ bottomSheetProps = {},
86
+ bottomSheetHeaderAddonsProps = {},
87
+ value = '',
88
+ filter = '',
89
+ name,
90
+ Arrow = null,
91
+ label,
92
+ placeholder,
93
+ size = 's',
94
+ open: openProp,
95
+ onFilter,
96
+ onChange,
97
+ onOpen,
98
+ onCancel,
99
+ onClearFilter,
100
+ continueButtonProps,
101
+ cancelButtonProps,
102
+ selected,
103
+ multiple,
104
+ ...restProps
105
+ }: InputAutocompleteMobileProps,
106
+ ref,
107
+ ) => {
108
+ const [open, setOpen] = useState(false);
109
+ const bottomSheetInputRef = useRef<HTMLInputElement>(null);
110
+ const targetRef = useRef<HTMLDivElement>(null);
111
+
112
+ const setBottomSheetVisibility = (isOpen: boolean) => {
113
+ if (openProp === undefined) {
114
+ setOpen(isOpen);
115
+ }
116
+
117
+ if (onOpen) {
118
+ onOpen({ open: isOpen, name });
119
+ }
120
+ };
121
+
122
+ const handleOpen: SelectMobileProps['onOpen'] = (payload) => {
123
+ setBottomSheetVisibility(Boolean(payload.open));
124
+ };
125
+
126
+ const handleOptionsListTouchMove = useMemo(
127
+ () =>
128
+ throttle(() => {
129
+ const input = bottomSheetInputRef.current;
130
+
131
+ if (input && document.activeElement === input) {
132
+ input.blur();
133
+ }
134
+ }, 300),
135
+ [],
136
+ );
137
+
138
+ const handleApply = () => {
139
+ setBottomSheetVisibility(false);
140
+ onChange(filter);
141
+ };
142
+
143
+ const handleChange: SelectMobileProps['onChange'] = (payload) => {
144
+ onChange(payload);
145
+
146
+ if (multiple) {
147
+ // После выбора опции возвращаем фокус в поле ввода.
148
+ bottomSheetInputRef.current?.focus();
149
+ }
150
+ };
151
+
152
+ const handleCancel = () => {
153
+ setBottomSheetVisibility(false);
154
+
155
+ if (onCancel) {
156
+ onCancel();
157
+ }
158
+ };
159
+
160
+ const handleInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {
161
+ const input = bottomSheetInputRef.current;
162
+
163
+ // Перед закрытием шторки снимаем фокус с инпута, чтобы предотвратить скачок шторки.
164
+ if (
165
+ event.relatedTarget === targetRef.current &&
166
+ input &&
167
+ input === document.activeElement
168
+ ) {
169
+ input.blur();
170
+ }
171
+ };
172
+
173
+ const getBottomSheetProps = (): InputAutocompleteMobileProps['bottomSheetProps'] => {
174
+ const Component = Input || CoreInput;
175
+
176
+ return {
177
+ actionButton: (
178
+ <div className={styles.footer}>
179
+ <ButtonMobile
180
+ block={true}
181
+ view='secondary'
182
+ size='m'
183
+ onClick={handleCancel}
184
+ {...cancelButtonProps}
185
+ >
186
+ Отмена
187
+ </ButtonMobile>
188
+ <ButtonMobile
189
+ block={true}
190
+ view='primary'
191
+ size='m'
192
+ onClick={handleApply}
193
+ {...continueButtonProps}
194
+ >
195
+ Продолжить
196
+ </ButtonMobile>
197
+ </div>
198
+ ),
199
+ title: label || placeholder,
200
+ bottomAddons: (
201
+ <Component
202
+ block={true}
203
+ clear={!!onClearFilter}
204
+ onClear={onClearFilter}
205
+ value={filter}
206
+ onInput={onFilter}
207
+ placeholder={placeholder}
208
+ onFocus={handleInputFocus}
209
+ {...bottomSheetHeaderAddonsProps}
210
+ className={cn(
211
+ styles.bottomAddonInput,
212
+ bottomSheetHeaderAddonsProps.className as string,
213
+ )}
214
+ ref={mergeRefs([
215
+ bottomSheetInputRef,
216
+ bottomSheetHeaderAddonsProps.ref as RefObject<HTMLInputElement>,
217
+ ])}
218
+ />
219
+ ),
220
+ initialHeight: 'full',
221
+ ...bottomSheetProps,
222
+ containerProps: {
223
+ onTouchMove: handleOptionsListTouchMove,
224
+ ...bottomSheetProps.containerProps,
225
+ },
226
+ };
227
+ };
228
+
229
+ return (
230
+ <SelectMobile
231
+ ref={mergeRefs([targetRef, ref])}
232
+ selected={selected || SELECTED}
233
+ open={Boolean(open || openProp)}
234
+ onOpen={handleOpen}
235
+ onChange={handleChange}
236
+ Arrow={Arrow}
237
+ Field={AutocompleteMobileField}
238
+ fieldProps={{ value }}
239
+ placeholder={placeholder}
240
+ label={label}
241
+ size={size}
242
+ name={name}
243
+ multiple={multiple}
244
+ useWithApplyHook={false}
245
+ bottomSheetProps={getBottomSheetProps()}
246
+ optionsListProps={{ showFooter: false }}
247
+ {...restProps}
248
+ />
249
+ );
250
+ },
251
+ );
@@ -0,0 +1,227 @@
1
+ import React, { ChangeEvent, ElementType, RefObject, useEffect, useRef } from 'react';
2
+ import mergeRefs from 'react-merge-refs';
3
+ import cn from 'classnames';
4
+ import throttle from 'lodash.throttle';
5
+
6
+ import { ButtonMobile, ButtonMobileProps } from '@alfalab/core-components-button/mobile';
7
+ import { Input as CoreInput } from '@alfalab/core-components-input';
8
+ import {
9
+ SelectMobileProps,
10
+ SelectModalMobile,
11
+ SelectModalMobileProps,
12
+ } from '@alfalab/core-components-select/mobile';
13
+ import type { BaseSelectChangePayload } from '@alfalab/core-components-select/shared';
14
+
15
+ import { AutocompleteMobileField } from './autocomplete-mobile-field';
16
+
17
+ import styles from './mobile.module.css';
18
+
19
+ export type InputAutocompleteModalMobileProps = Omit<
20
+ SelectModalMobileProps,
21
+ 'OptionsList' | 'Checkmark' | 'onScroll' | 'nativeSelect' | 'autocomplete' | 'valueRenderer'
22
+ > & {
23
+ /**
24
+ * Обработчик выбора
25
+ */
26
+ onChange: (payload: string | BaseSelectChangePayload) => void;
27
+
28
+ /**
29
+ * Обработчик ввода фильтра.
30
+ */
31
+ onFilter: (event: ChangeEvent<HTMLInputElement>) => void;
32
+
33
+ /**
34
+ * Значение поля ввода
35
+ */
36
+ value?: string;
37
+
38
+ /**
39
+ * Значение фильтра.
40
+ */
41
+ filter?: string;
42
+
43
+ /**
44
+ * Обработчик нажатия на кнопку "Отмена".
45
+ */
46
+ onCancel?: () => void;
47
+
48
+ /**
49
+ * Обработчик нажатия на крестик в инпуте фильтра.
50
+ */
51
+ onClearFilter?: () => void;
52
+
53
+ /**
54
+ * Дополнительные пропсы на слот под заголовком
55
+ */
56
+ bottomAddonProps?: Record<string, unknown>;
57
+
58
+ /**
59
+ * Дополнительные пропсы на кнопку "продолжить"
60
+ */
61
+ continueButtonProps?: ButtonMobileProps;
62
+
63
+ /**
64
+ * Дополнительные пропсы на кнопку "отмена"
65
+ */
66
+ cancelButtonProps?: ButtonMobileProps;
67
+
68
+ /**
69
+ * Кастомный инпут
70
+ */
71
+ Input?: ElementType;
72
+ };
73
+
74
+ export const InputAutocompleteModalMobile = React.forwardRef<
75
+ HTMLDivElement,
76
+ InputAutocompleteModalMobileProps
77
+ >(
78
+ (
79
+ {
80
+ open: openProp,
81
+ onOpen,
82
+ name,
83
+ size = 's',
84
+ Input = CoreInput,
85
+ Arrow = null,
86
+ onClearFilter,
87
+ filter = '',
88
+ onFilter,
89
+ placeholder,
90
+ bottomAddonProps,
91
+ multiple,
92
+ onChange,
93
+ onCancel,
94
+ value,
95
+ cancelButtonProps,
96
+ continueButtonProps,
97
+ modalProps,
98
+ modalHeaderProps,
99
+ modalFooterProps,
100
+ ...restProps
101
+ }: InputAutocompleteModalMobileProps,
102
+ ref,
103
+ ) => {
104
+ const [open, setOpen] = React.useState(false);
105
+ const targetRef = useRef<HTMLDivElement>(null);
106
+ const modalScrollableRef = useRef<HTMLDivElement>(null);
107
+ const bottomAddonRef = useRef<HTMLInputElement>(null);
108
+
109
+ useEffect(() => {
110
+ const modalContentNode = modalScrollableRef.current;
111
+
112
+ const handleListTouchMove = throttle(() => {
113
+ const input = bottomAddonRef.current;
114
+
115
+ if (input && document.activeElement === input) {
116
+ input.blur();
117
+ }
118
+ }, 300);
119
+
120
+ modalContentNode?.addEventListener('touchmove', handleListTouchMove);
121
+
122
+ return () => modalContentNode?.removeEventListener('touchmove', handleListTouchMove);
123
+ }, [open]);
124
+
125
+ const setOpenModalSelect = (isOpen: boolean) => {
126
+ if (openProp === undefined) {
127
+ setOpen(isOpen);
128
+ }
129
+
130
+ onOpen?.({ open: isOpen, name });
131
+ };
132
+
133
+ const handleOpen: SelectMobileProps['onOpen'] = (payload) => {
134
+ setOpenModalSelect(Boolean(payload.open));
135
+ };
136
+
137
+ const handleApply = () => {
138
+ setOpenModalSelect(false);
139
+ onChange(filter);
140
+ };
141
+
142
+ const handleCancel = () => {
143
+ setOpenModalSelect(false);
144
+ onCancel?.();
145
+ };
146
+
147
+ const handleChange: SelectMobileProps['onChange'] = (payload) => {
148
+ onChange(payload);
149
+
150
+ if (multiple) {
151
+ // После выбора опции возвращаем фокус в поле ввода.
152
+ bottomAddonRef.current?.focus();
153
+ }
154
+ };
155
+
156
+ const renderBottomAddon = () => (
157
+ <Input
158
+ block={true}
159
+ clear={!!onClearFilter}
160
+ onClear={onClearFilter}
161
+ value={filter}
162
+ onInput={onFilter}
163
+ placeholder={placeholder}
164
+ {...bottomAddonProps}
165
+ className={cn(styles.bottomAddonInput, bottomAddonProps?.className as string)}
166
+ ref={mergeRefs([
167
+ bottomAddonRef,
168
+ bottomAddonProps?.ref as RefObject<HTMLInputElement>,
169
+ ])}
170
+ />
171
+ );
172
+
173
+ const renderFooter = () => (
174
+ <React.Fragment>
175
+ <ButtonMobile
176
+ block={true}
177
+ view='secondary'
178
+ size='m'
179
+ onClick={handleCancel}
180
+ {...cancelButtonProps}
181
+ >
182
+ Отмена
183
+ </ButtonMobile>
184
+ <ButtonMobile
185
+ block={true}
186
+ view='primary'
187
+ size='m'
188
+ onClick={handleApply}
189
+ {...continueButtonProps}
190
+ >
191
+ Продолжить
192
+ </ButtonMobile>
193
+ </React.Fragment>
194
+ );
195
+
196
+ return (
197
+ <SelectModalMobile
198
+ ref={mergeRefs([ref, targetRef])}
199
+ name={name}
200
+ Field={AutocompleteMobileField}
201
+ Arrow={Arrow}
202
+ onOpen={handleOpen}
203
+ onChange={handleChange}
204
+ multiple={multiple}
205
+ open={openProp ?? open}
206
+ size={size}
207
+ fieldProps={{ value }}
208
+ placeholder={placeholder}
209
+ useWithApplyHook={false}
210
+ modalProps={{
211
+ ...modalProps,
212
+ componentRef: modalScrollableRef,
213
+ }}
214
+ modalHeaderProps={{
215
+ ...modalHeaderProps,
216
+ bottomAddons: renderBottomAddon(),
217
+ }}
218
+ modalFooterProps={{
219
+ sticky: true,
220
+ ...modalFooterProps,
221
+ children: renderFooter(),
222
+ }}
223
+ {...restProps}
224
+ />
225
+ );
226
+ },
227
+ );
@@ -0,0 +1,42 @@
1
+ import React, { forwardRef } from 'react';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import { BottomSheetProps } from '@alfalab/core-components-bottom-sheet';
5
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
+ import { ButtonMobileProps } from '@alfalab/core-components-button/mobile';
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ import type { BaseSelectProps } from '@alfalab/core-components-select/shared';
9
+ import { useMedia } from '@alfalab/hooks';
10
+
11
+ import { InputAutocompleteDesktop, InputAutocompleteDesktopProps } from './Component.desktop';
12
+ import { InputAutocompleteMobile, InputAutocompleteMobileProps } from './Component.mobile';
13
+
14
+ export type InputAutocompleteResponsiveProps = InputAutocompleteDesktopProps &
15
+ InputAutocompleteMobileProps & {
16
+ /**
17
+ * Контрольная точка, с нее начинается desktop версия
18
+ * @default 1024
19
+ */
20
+ breakpoint?: number;
21
+ };
22
+
23
+ export type InputAutocompleteMedia = 'desktop' | 'mobile';
24
+
25
+ export const InputAutocompleteResponsive = forwardRef<
26
+ HTMLInputElement | HTMLDivElement,
27
+ InputAutocompleteResponsiveProps
28
+ >(({ breakpoint = 1024, ...restProps }, ref) => {
29
+ const [view] = useMedia<InputAutocompleteMedia>(
30
+ [
31
+ ['mobile', `(max-width: ${breakpoint - 1}px)`],
32
+ ['desktop', `(min-width: ${breakpoint}px)`],
33
+ ],
34
+ 'desktop',
35
+ );
36
+
37
+ return view === 'desktop' ? (
38
+ <InputAutocompleteDesktop {...restProps} ref={ref as React.Ref<HTMLInputElement>} />
39
+ ) : (
40
+ <InputAutocompleteMobile {...restProps} ref={ref} />
41
+ );
42
+ });
@@ -0,0 +1,91 @@
1
+ import React, { useCallback, useRef } from 'react';
2
+ import mergeRefs from 'react-merge-refs';
3
+ import cn from 'classnames';
4
+
5
+ import { InputDesktop as DefaultInput } from '@alfalab/core-components-input/desktop';
6
+ import type { FieldProps } from '@alfalab/core-components-select/shared';
7
+
8
+ import { InputAutocompleteDesktopProps } from '../Component.desktop';
9
+
10
+ import styles from './index.module.css';
11
+
12
+ export type AutocompleteFieldProps = FieldProps &
13
+ Pick<InputAutocompleteDesktopProps, 'Input' | 'inputProps' | 'value' | 'onInput' | 'readOnly'>;
14
+
15
+ export const AutocompleteField = ({
16
+ label,
17
+ labelView = 'inner',
18
+ placeholder,
19
+ size,
20
+ Arrow,
21
+ Input = DefaultInput,
22
+ value,
23
+ error,
24
+ success,
25
+ hint,
26
+ disabled,
27
+ readOnly,
28
+ onInput,
29
+ inputProps = {},
30
+ innerProps,
31
+ }: AutocompleteFieldProps) => {
32
+ const inputRef = useRef<HTMLInputElement>(null);
33
+
34
+ const { onClick, onFocus } = innerProps;
35
+
36
+ const inputDisabled = disabled || readOnly;
37
+
38
+ const handleClick = useCallback(
39
+ (event: React.MouseEvent<HTMLDivElement>) => {
40
+ if (onClick) onClick(event);
41
+
42
+ if (inputRef.current) {
43
+ inputRef.current.focus();
44
+ }
45
+ },
46
+ [onClick],
47
+ );
48
+
49
+ return (
50
+ <Input
51
+ {...inputProps}
52
+ {...innerProps}
53
+ wrapperRef={mergeRefs([
54
+ innerProps.ref as React.Ref<HTMLElement>,
55
+ inputProps.wrapperRef as React.Ref<HTMLElement>,
56
+ ])}
57
+ ref={mergeRefs([inputRef, inputProps.ref as React.Ref<HTMLElement>])}
58
+ disabled={disabled}
59
+ readOnly={readOnly}
60
+ block={true}
61
+ label={label}
62
+ labelView={labelView}
63
+ placeholder={placeholder}
64
+ size={size}
65
+ error={error}
66
+ success={success}
67
+ hint={hint}
68
+ onChange={onInput}
69
+ onClick={inputDisabled ? undefined : handleClick}
70
+ onFocus={inputDisabled ? undefined : onFocus}
71
+ autoComplete='off'
72
+ value={value}
73
+ rightAddons={
74
+ (Arrow || inputProps.rightAddons) && (
75
+ <React.Fragment>
76
+ {inputProps.rightAddons}
77
+ {Arrow && (
78
+ <span
79
+ className={cn(styles.arrow, {
80
+ [styles.error]: error,
81
+ })}
82
+ >
83
+ {Arrow}
84
+ </span>
85
+ )}
86
+ </React.Fragment>
87
+ )
88
+ }
89
+ />
90
+ );
91
+ };
@@ -0,0 +1,3 @@
1
+ .arrow.error ~ * {
2
+ display: none;
3
+ }
@@ -0,0 +1 @@
1
+ export * from './Component';
@@ -0,0 +1,74 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import {
5
+ FormControlMobile,
6
+ FormControlMobileProps,
7
+ } from '@alfalab/core-components-form-control/mobile';
8
+ import { InputAutocompleteProps } from '@alfalab/core-components-input-autocomplete';
9
+ import type { FieldProps as BaseFieldProps } from '@alfalab/core-components-select/shared';
10
+ import { useFocus } from '@alfalab/hooks';
11
+
12
+ import styles from './index.module.css';
13
+
14
+ export type AutocompleteMobileFieldProps = FormControlMobileProps &
15
+ Omit<BaseFieldProps, 'selected' | 'multiple' | 'success'> &
16
+ Pick<InputAutocompleteProps, 'value'>;
17
+
18
+ export const AutocompleteMobileField = ({
19
+ size = 'm',
20
+ open,
21
+ disabled,
22
+ value,
23
+ innerProps,
24
+ dataTestId,
25
+ fieldClassName,
26
+ labelView = 'inner',
27
+ placeholder,
28
+ Arrow,
29
+ valueRenderer,
30
+ toggleMenu,
31
+ setSelectedItems,
32
+ selectedMultiple,
33
+ ...restProps
34
+ }: AutocompleteMobileFieldProps) => {
35
+ const [focused, setFocused] = useState(false);
36
+
37
+ const wrapperRef = useRef<HTMLDivElement>(null);
38
+
39
+ const [focusVisible] = useFocus(wrapperRef, 'keyboard');
40
+
41
+ const filled = Boolean(value);
42
+ const showPlaceholder = placeholder && !filled && labelView === 'outer';
43
+
44
+ return (
45
+ <div
46
+ className={styles.component}
47
+ ref={wrapperRef}
48
+ onFocus={() => setFocused(true)}
49
+ onBlur={() => setFocused(false)}
50
+ >
51
+ <FormControlMobile
52
+ fieldClassName={cn(styles.field, fieldClassName, {
53
+ [styles.disabled]: disabled,
54
+ [styles.focusVisible]: focusVisible,
55
+ })}
56
+ block={true}
57
+ size={size}
58
+ focused={focused}
59
+ disabled={disabled}
60
+ filled={filled}
61
+ labelView={labelView}
62
+ rightAddons={Arrow}
63
+ data-test-id={dataTestId}
64
+ {...restProps}
65
+ {...innerProps}
66
+ >
67
+ <div className={styles.contentWrapper}>
68
+ {showPlaceholder && <span className={styles.placeholder}>{placeholder}</span>}
69
+ {filled && <div className={styles.value}>{value}</div>}
70
+ </div>
71
+ </FormControlMobile>
72
+ </div>
73
+ );
74
+ };