@xqmsg/ui-core 0.23.1-rc.0 → 0.23.1-rc.2

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 (200) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/LICENSE +20 -20
  3. package/README.md +40 -40
  4. package/dist/{89793640b494d7ea.svg → 78c9d6fd7766410f.svg} +9 -9
  5. package/dist/components/input/StackedInput/StackedInput.d.ts +1 -0
  6. package/dist/components/input/StackedPilledInput/index.d.ts +1 -0
  7. package/dist/components/input/StackedTextarea/StackedTextarea.d.ts +1 -0
  8. package/dist/theme/components/button.d.ts +0 -3
  9. package/dist/theme/components/checkbox.d.ts +5 -4
  10. package/dist/theme/components/input.d.ts +12 -1
  11. package/dist/theme/components/select.d.ts +12 -1
  12. package/dist/theme/components/textarea.d.ts +15 -3
  13. package/dist/ui-core.cjs.development.js +85 -78
  14. package/dist/ui-core.cjs.development.js.map +1 -1
  15. package/dist/ui-core.cjs.production.min.js +1 -1
  16. package/dist/ui-core.cjs.production.min.js.map +1 -1
  17. package/dist/ui-core.esm.js +85 -78
  18. package/dist/ui-core.esm.js.map +1 -1
  19. package/package.json +118 -118
  20. package/src/components/banner/Banner.stories.tsx +100 -100
  21. package/src/components/banner/index.tsx +73 -73
  22. package/src/components/breadcrumbs/Breadcrumbs.stories.tsx +66 -66
  23. package/src/components/breadcrumbs/components/icon/index.tsx +38 -38
  24. package/src/components/breadcrumbs/components/label/index.tsx +20 -20
  25. package/src/components/breadcrumbs/index.tsx +48 -48
  26. package/src/components/button/Button.stories.tsx +140 -140
  27. package/src/components/button/google/GoogleButton.stories.tsx +23 -23
  28. package/src/components/button/google/index.tsx +29 -29
  29. package/src/components/button/index.tsx +51 -51
  30. package/src/components/button/microsoft/MicrosoftButton.stories.tsx +25 -25
  31. package/src/components/button/microsoft/index.tsx +29 -29
  32. package/src/components/button/spinner/SpinnerButton.stories.tsx +60 -60
  33. package/src/components/button/spinner/index.tsx +36 -36
  34. package/src/components/card/Card.stories.tsx +56 -56
  35. package/src/components/card/index.tsx +78 -78
  36. package/src/components/form/Form.stories.tsx +62 -62
  37. package/src/components/form/FormTypes.ts +20 -20
  38. package/src/components/form/hooks/useFormHandler.tsx +74 -74
  39. package/src/components/form/index.tsx +25 -25
  40. package/src/components/form/section/FormSection.stories.tsx +109 -109
  41. package/src/components/form/section/index.tsx +87 -87
  42. package/src/components/form/utils/formErrors.ts +34 -34
  43. package/src/components/icons/checkmark/checkmark.svg +3 -3
  44. package/src/components/icons/checkmark/index.tsx +13 -13
  45. package/src/components/icons/chevron/down/chevron-down.svg +3 -3
  46. package/src/components/icons/chevron/down/index.tsx +14 -14
  47. package/src/components/icons/chevron/right/chevron-right.svg +3 -3
  48. package/src/components/icons/chevron/right/index.tsx +13 -13
  49. package/src/components/icons/clock/clock.svg +3 -3
  50. package/src/components/icons/clock/index.tsx +13 -13
  51. package/src/components/icons/close/close.svg +3 -3
  52. package/src/components/icons/close/index.tsx +21 -21
  53. package/src/components/icons/dropdown/dropdown.svg +3 -3
  54. package/src/components/icons/dropdown/index.tsx +16 -16
  55. package/src/components/icons/error/error.svg +3 -3
  56. package/src/components/icons/error/index.tsx +13 -13
  57. package/src/components/icons/file/fill/file-fill.svg +4 -4
  58. package/src/components/icons/file/fill/index.tsx +13 -13
  59. package/src/components/icons/file/outline/file-outline.svg +3 -3
  60. package/src/components/icons/file/outline/index.tsx +13 -13
  61. package/src/components/icons/folder/add/fill/folder-add-fill.svg +3 -3
  62. package/src/components/icons/folder/add/fill/index.tsx +13 -13
  63. package/src/components/icons/folder/add/outline/folder-add-outline.svg +3 -3
  64. package/src/components/icons/folder/add/outline/index.tsx +15 -15
  65. package/src/components/icons/folder/fill/folder-fill-gradient.svg +33 -33
  66. package/src/components/icons/folder/fill/folder-fill.svg +4 -4
  67. package/src/components/icons/folder/fill/index.tsx +21 -21
  68. package/src/components/icons/folder/outline/folder-outline.svg +3 -3
  69. package/src/components/icons/folder/outline/index.tsx +13 -13
  70. package/src/components/icons/gear/GearIcon.tsx +36 -36
  71. package/src/components/icons/google/drive/index.tsx +13 -13
  72. package/src/components/icons/google/google.svg +13 -13
  73. package/src/components/icons/google/index.tsx +13 -13
  74. package/src/components/icons/group/group.svg +3 -3
  75. package/src/components/icons/group/index.tsx +13 -13
  76. package/src/components/icons/home/home.svg +3 -3
  77. package/src/components/icons/home/index.tsx +13 -13
  78. package/src/components/icons/image/image.svg +3 -3
  79. package/src/components/icons/image/index.tsx +13 -13
  80. package/src/components/icons/index.tsx +101 -101
  81. package/src/components/icons/link/index.tsx +13 -13
  82. package/src/components/icons/link/link.svg +4 -4
  83. package/src/components/icons/menu/index.tsx +13 -13
  84. package/src/components/icons/menu/menu.svg +3 -3
  85. package/src/components/icons/microsoft/index.tsx +13 -13
  86. package/src/components/icons/microsoft/microsoft.svg +9 -9
  87. package/src/components/icons/microsoft/onedrive/index.tsx +16 -16
  88. package/src/components/icons/neutral/index.tsx +14 -14
  89. package/src/components/icons/neutral/neutral.svg +3 -3
  90. package/src/components/icons/page/index.tsx +13 -13
  91. package/src/components/icons/page/page.svg +3 -3
  92. package/src/components/icons/positive/index.tsx +13 -13
  93. package/src/components/icons/positive/positive.svg +3 -3
  94. package/src/components/icons/question/index.tsx +13 -13
  95. package/src/components/icons/question/question.svg +3 -3
  96. package/src/components/icons/search/index.tsx +13 -13
  97. package/src/components/icons/search/search.svg +3 -3
  98. package/src/components/icons/services/index.tsx +13 -13
  99. package/src/components/icons/services/services.svg +3 -3
  100. package/src/components/icons/settings/index.tsx +14 -14
  101. package/src/components/icons/settings/settings.svg +6 -6
  102. package/src/components/icons/table/fill/index.tsx +13 -13
  103. package/src/components/icons/table/fill/table-fill.svg +3 -3
  104. package/src/components/icons/table/outline/index.tsx +13 -13
  105. package/src/components/icons/table/outline/table-outline.svg +3 -3
  106. package/src/components/icons/task/index.tsx +10 -10
  107. package/src/components/icons/task/task.svg +11 -11
  108. package/src/components/icons/trash/index.tsx +13 -13
  109. package/src/components/icons/trash/trash.svg +3 -3
  110. package/src/components/icons/vault/index.tsx +14 -14
  111. package/src/components/icons/video/index.tsx +13 -13
  112. package/src/components/icons/video/video.svg +3 -3
  113. package/src/components/icons/warning/index.tsx +13 -13
  114. package/src/components/icons/warning/warning.svg +3 -3
  115. package/src/components/icons/workspace/index.tsx +14 -14
  116. package/src/components/input/Input.stories.tsx +287 -286
  117. package/src/components/input/InputTypes.ts +77 -77
  118. package/src/components/input/StackedCheckbox/StackedCheckbox.tsx +44 -44
  119. package/src/components/input/StackedInput/StackedInput.tsx +60 -55
  120. package/src/components/input/StackedMultiSelect/index.tsx +349 -349
  121. package/src/components/input/StackedPilledInput/index.tsx +386 -362
  122. package/src/components/input/StackedRadio/StackedRadioGroup.tsx +38 -38
  123. package/src/components/input/StackedSelect/index.tsx +232 -232
  124. package/src/components/input/StackedSwitch/index.tsx +33 -33
  125. package/src/components/input/StackedTextarea/StackedTextarea.tsx +55 -53
  126. package/src/components/input/components/dropdown/index.tsx +111 -111
  127. package/src/components/input/components/label/index.tsx +35 -35
  128. package/src/components/input/components/token/Token.stories.tsx +25 -25
  129. package/src/components/input/components/token/index.tsx +45 -45
  130. package/src/components/input/index.tsx +298 -295
  131. package/src/components/layout/BorderedBox/index.tsx +30 -30
  132. package/src/components/layout/Layout.stories.tsx +40 -40
  133. package/src/components/layout/index.tsx +100 -100
  134. package/src/components/link/Link.stories.tsx +23 -23
  135. package/src/components/link/index.tsx +34 -34
  136. package/src/components/loading/LoadingIndicator.stories.tsx +45 -45
  137. package/src/components/loading/index.tsx +45 -45
  138. package/src/components/modal/Modal.stories.tsx +36 -36
  139. package/src/components/modal/components/action/index.tsx +37 -37
  140. package/src/components/modal/index.tsx +41 -41
  141. package/src/components/navigation/NavigationMenu.stories.tsx +85 -85
  142. package/src/components/navigation/components/header/index.tsx +27 -27
  143. package/src/components/navigation/components/items/index.tsx +76 -76
  144. package/src/components/navigation/index.tsx +87 -87
  145. package/src/components/select/index.tsx +140 -184
  146. package/src/components/table/Table.stories.tsx +63 -63
  147. package/src/components/table/TableTypes.ts +15 -15
  148. package/src/components/table/components/loading/index.tsx +45 -45
  149. package/src/components/table/components/text/index.tsx +23 -23
  150. package/src/components/table/empty/index.tsx +47 -47
  151. package/src/components/table/index.tsx +84 -84
  152. package/src/components/table/utils/generateTableColumns.ts +9 -9
  153. package/src/components/tabs/TabsWrapper.stories.tsx +85 -85
  154. package/src/components/tabs/index.tsx +39 -39
  155. package/src/components/text/Text.stories.tsx +59 -59
  156. package/src/components/text/index.tsx +16 -16
  157. package/src/components/toast/Toast.stories.tsx +52 -52
  158. package/src/components/toast/index.tsx +78 -78
  159. package/src/components/toolbar/Toolbar.stories.tsx +59 -59
  160. package/src/components/toolbar/components/actions/add/index.tsx +18 -18
  161. package/src/components/toolbar/components/actions/search/index.tsx +38 -38
  162. package/src/components/toolbar/components/actions/sort/index.tsx +49 -49
  163. package/src/components/toolbar/components/breadcrumbs/index.tsx +63 -63
  164. package/src/components/toolbar/components/breadcrumbs/item/index.tsx +72 -72
  165. package/src/components/toolbar/components/dropdown/index.tsx +107 -107
  166. package/src/components/toolbar/components/navigation/components/button/left/index.tsx +28 -28
  167. package/src/components/toolbar/components/navigation/components/button/left/left-arrow.svg +3 -3
  168. package/src/components/toolbar/components/navigation/components/button/right/index.tsx +27 -27
  169. package/src/components/toolbar/components/navigation/components/button/right/right-arrow.svg +3 -3
  170. package/src/components/toolbar/components/navigation/index.tsx +36 -36
  171. package/src/components/toolbar/index.tsx +55 -55
  172. package/src/hooks/useDeepEffect.tsx +22 -22
  173. package/src/hooks/useDidMountEffect.tsx +13 -13
  174. package/src/hooks/useOnOutsideClick.tsx +31 -31
  175. package/src/hooks/useToast.tsx +16 -16
  176. package/src/index.tsx +78 -78
  177. package/src/theme/components/alert.ts +60 -60
  178. package/src/theme/components/badge.ts +59 -59
  179. package/src/theme/components/button.ts +163 -163
  180. package/src/theme/components/checkbox.ts +28 -25
  181. package/src/theme/components/code.ts +16 -16
  182. package/src/theme/components/form-error.ts +31 -31
  183. package/src/theme/components/form-label.ts +17 -17
  184. package/src/theme/components/form.ts +29 -29
  185. package/src/theme/components/input.ts +65 -57
  186. package/src/theme/components/link.ts +118 -118
  187. package/src/theme/components/modal.ts +45 -45
  188. package/src/theme/components/select.ts +36 -36
  189. package/src/theme/components/switch.ts +89 -89
  190. package/src/theme/components/table.ts +42 -42
  191. package/src/theme/components/tabs.ts +255 -255
  192. package/src/theme/components/text.ts +93 -93
  193. package/src/theme/components/textarea.ts +42 -41
  194. package/src/theme/customXQChakraTheme.ts +54 -54
  195. package/src/theme/foundations/breakpoints.ts +18 -18
  196. package/src/theme/foundations/colors.ts +165 -165
  197. package/src/theme/foundations/shadows.ts +23 -23
  198. package/src/theme/foundations/typography.ts +62 -62
  199. package/src/theme/provider/index.tsx +21 -21
  200. package/src/theme/styles.ts +19 -19
@@ -1,362 +1,386 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { Box, Flex, Input, Text, useOutsideClick } from '@chakra-ui/react';
3
- import { InputFieldProps } from '../InputTypes';
4
- import {
5
- Control,
6
- FieldValues,
7
- UseFormClearErrors,
8
- UseFormSetError,
9
- UseFormSetValue,
10
- useWatch,
11
- } from 'react-hook-form';
12
- import colors from '../../../theme/foundations/colors';
13
- import Token from '../components/token';
14
-
15
- export interface StackedPilledInputProps extends InputFieldProps {
16
- setValue: UseFormSetValue<FieldValues>;
17
- setError: UseFormSetError<FieldValues>;
18
- clearErrors: UseFormClearErrors<FieldValues>;
19
- control: Control<FieldValues, any>;
20
- separators?: string[];
21
- variant?: string;
22
- }
23
-
24
- /**
25
- * A functional React component utilized to render the `StackedPilledInput` component.
26
- */
27
- const StackedPilledInput = React.forwardRef<
28
- HTMLInputElement,
29
- StackedPilledInputProps
30
- >(
31
- (
32
- {
33
- name,
34
- setValue,
35
- control,
36
- placeholder,
37
- disabled,
38
- separators = ['Enter', ' ', ',', ';', 'Tab'],
39
- variant,
40
- },
41
- _ref
42
- ) => {
43
- const watchedValue = useWatch({ control, name: name as string });
44
- const [lastestFormValueToArray, setLatestFormValueToArray] = useState<
45
- string[]
46
- >([]);
47
-
48
- const inputRef = useRef<HTMLInputElement>(null);
49
- const inputWrapperRef = useRef(null);
50
- const scrollRef = useRef<HTMLDivElement>(null);
51
-
52
- const [tokenIndex, setTokenIndex] = useState<number | null>(null);
53
- const [isFocussed, setIsFocussed] = useState(false);
54
- const [localValue, setLocalValue] = useState('');
55
-
56
- const latestTokenElement = document.getElementById(
57
- `${name}_token_${lastestFormValueToArray.length - 1}`
58
- );
59
-
60
- // gets latest watched form value (common delimited) from RHF state and creates a list
61
- useEffect(() => {
62
- if (watchedValue !== undefined && !watchedValue.length) {
63
- setLatestFormValueToArray([]);
64
- }
65
-
66
- if (watchedValue !== undefined && watchedValue?.length) {
67
- setLatestFormValueToArray(
68
- watchedValue
69
- .split(';')
70
- .join(',')
71
- .split(',')
72
- .filter(Boolean)
73
- );
74
-
75
- if (latestTokenElement) {
76
- latestTokenElement.scrollIntoView({
77
- block: 'end',
78
- inline: 'center',
79
- behavior: 'smooth',
80
- });
81
- }
82
- }
83
- }, [latestTokenElement, watchedValue]);
84
-
85
- const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
86
- if (tokenIndex === null) {
87
- setLocalValue(
88
- e.target.value
89
- .trim()
90
- .replace(',', '')
91
- .replace(';', '').length
92
- ? e.target.value
93
- : ''
94
- );
95
- }
96
- };
97
-
98
- const onHandleKeyDown = (e: React.KeyboardEvent) => {
99
- if (e.key === 'Enter') {
100
- e.stopPropagation();
101
- e.preventDefault();
102
- }
103
-
104
- if (separators.includes(e.key)) {
105
- if (
106
- e.key === 'Enter' &&
107
- !localValue.trim().length &&
108
- tokenIndex !== null
109
- ) {
110
- setLocalValue(lastestFormValueToArray[tokenIndex]);
111
-
112
- const filteredUniqueValues = Array.from(
113
- new Set(
114
- lastestFormValueToArray.filter(
115
- value => value !== lastestFormValueToArray[tokenIndex]
116
- )
117
- )
118
- );
119
-
120
- setValue(name as string, filteredUniqueValues.toString(), {
121
- shouldValidate: true,
122
- shouldDirty: true,
123
- });
124
-
125
- return setTokenIndex(null);
126
- }
127
-
128
- const filteredUniqueValues = Array.from(
129
- new Set([
130
- ...lastestFormValueToArray,
131
- ...localValue
132
- .trim()
133
- .split(';')
134
- .join(',')
135
- .split(','),
136
- ])
137
- );
138
-
139
- setLocalValue('');
140
-
141
- return setValue(name as string, filteredUniqueValues.toString(), {
142
- shouldValidate: true,
143
- shouldDirty: true,
144
- });
145
- }
146
-
147
- if (!localValue.trim().length && lastestFormValueToArray.length) {
148
- if (e.key === 'Backspace' && tokenIndex !== null) {
149
- setLocalValue(
150
- lastestFormValueToArray[tokenIndex].substring(
151
- 0,
152
- lastestFormValueToArray[tokenIndex].length
153
- )
154
- );
155
-
156
- const filteredUniqueValues = Array.from(
157
- new Set(
158
- [...lastestFormValueToArray].filter(
159
- value => value !== lastestFormValueToArray[tokenIndex]
160
- )
161
- )
162
- );
163
-
164
- setValue(name as string, filteredUniqueValues.toString(), {
165
- shouldValidate: true,
166
- shouldDirty: true,
167
- });
168
-
169
- return setTokenIndex(null);
170
- }
171
-
172
- if (e.key === 'ArrowLeft') {
173
- if (tokenIndex === 0) return;
174
-
175
- if (!tokenIndex) {
176
- return setTokenIndex(lastestFormValueToArray.length - 1);
177
- }
178
-
179
- setTokenIndex(prevTokenIndex => (prevTokenIndex as number) - 1);
180
-
181
- const tokenElement = document.getElementById(
182
- `${name}_token_${tokenIndex}`
183
- );
184
-
185
- if (!tokenElement || !scrollRef.current) return;
186
-
187
- return scrollRef.current.scrollBy({
188
- left: -1 * tokenElement.getBoundingClientRect().width,
189
- behavior: 'smooth',
190
- });
191
- }
192
-
193
- if (e.key === 'ArrowRight') {
194
- if (tokenIndex === null) return;
195
-
196
- if (tokenIndex === lastestFormValueToArray.length - 1) {
197
- return setTokenIndex(null);
198
- }
199
- setTokenIndex(prevTokenIndex => (prevTokenIndex as number) + 1);
200
-
201
- const tokenElement = document.getElementById(
202
- `${name}_token_${tokenIndex}`
203
- );
204
-
205
- if (!tokenElement || !scrollRef.current) return;
206
-
207
- return scrollRef.current.scrollBy({
208
- left: tokenElement.getBoundingClientRect().width,
209
- behavior: 'smooth',
210
- });
211
- }
212
- }
213
- };
214
-
215
- const onRemoveTag = (index: number) => {
216
- const filteredUniqueValues = lastestFormValueToArray.filter(
217
- (_, i) => i !== index
218
- );
219
-
220
- setLatestFormValueToArray(filteredUniqueValues);
221
-
222
- setValue(name as string, filteredUniqueValues.toString(), {
223
- shouldValidate: true,
224
- shouldDirty: true,
225
- });
226
- };
227
-
228
- const onBlur = () => {
229
- if (localValue.trim().length) {
230
- const filteredUniqueValues = Array.from(
231
- new Set([...lastestFormValueToArray, ...localValue.trim().split(',')])
232
- );
233
-
234
- setValue(name as string, filteredUniqueValues.toString(), {
235
- shouldValidate: true,
236
- shouldDirty: true,
237
- });
238
- setLocalValue('');
239
- }
240
- setIsFocussed(false);
241
- };
242
-
243
- useOutsideClick({
244
- ref: inputWrapperRef,
245
- handler: () => {
246
- onBlur();
247
- },
248
- });
249
- const isMobile = variant === 'mobile';
250
-
251
- return (
252
- <Box position="relative">
253
- <Flex
254
- fontSize={isMobile ? '17px' : '13px'}
255
- border={isFocussed ? '2px solid' : '.5px solid'}
256
- borderColor={isFocussed ? colors.border.focus : colors.border.default}
257
- pl="8px"
258
- borderRadius={isMobile ? '0' : '4px'}
259
- alignItems="center"
260
- justifyContent="space-between"
261
- onClick={() => {
262
- if (isFocussed && tokenIndex !== null) {
263
- setTokenIndex(null);
264
- }
265
-
266
- if (!disabled) {
267
- inputRef.current?.focus();
268
- }
269
- }}
270
- bg={disabled ? colors.fill.light.quaternary : '#ffffff'}
271
- cursor={disabled ? 'not-allowed' : 'pointer'}
272
- ref={inputWrapperRef}
273
- h={isMobile ? '48px' : '26px'}
274
- >
275
- <Flex
276
- h="100%"
277
- alignItems="center"
278
- overflowX="scroll"
279
- overflowY="hidden"
280
- maxWidth={isFocussed ? '80%' : '100%'}
281
- style={{
282
- scrollbarWidth: 'none' /* Firefox */,
283
- msOverflowStyle: 'none',
284
- }}
285
- sx={{
286
- '::-webkit-scrollbar': {
287
- display: 'none',
288
- },
289
- }}
290
- ref={scrollRef}
291
- zIndex={100}
292
- onKeyDown={onHandleKeyDown}
293
- >
294
- {lastestFormValueToArray.length
295
- ? lastestFormValueToArray.map((label, index) => (
296
- <Box
297
- key={index}
298
- mr="4px"
299
- border={
300
- tokenIndex === index
301
- ? `1px solid ${colors.border.focus}`
302
- : 'none'
303
- }
304
- borderRadius="full"
305
- onClick={() => setTokenIndex(index)}
306
- width="100%"
307
- id={`${name}_token_${index}`}
308
- >
309
- <Token
310
- label={label}
311
- onDelete={(e: any) => {
312
- e.stopPropagation();
313
- e.preventDefault();
314
- onRemoveTag(index);
315
- }}
316
- isMobile={isMobile}
317
- />
318
- </Box>
319
- ))
320
- : null}
321
- {!lastestFormValueToArray.length && !isFocussed ? (
322
- <Text
323
- color={colors.label.secondary.light}
324
- fontSize={isMobile ? '17px' : '13px'}
325
- >
326
- {placeholder}
327
- </Text>
328
- ) : null}
329
- </Flex>
330
- {!disabled && (
331
- <Flex flex={1} minWidth={isFocussed && !tokenIndex ? '20%' : 0}>
332
- <Input
333
- onKeyDown={onHandleKeyDown}
334
- type="text"
335
- padding={0}
336
- alignContent="flex-start"
337
- float="right"
338
- border="none"
339
- height="auto"
340
- color={
341
- tokenIndex !== null ? 'transparent' : colors.label.primary
342
- }
343
- _focus={{ boxShadow: 'none !important' }}
344
- value={localValue}
345
- onChange={handleOnChange}
346
- ref={inputRef}
347
- onFocus={() => setIsFocussed(true)}
348
- onBlur={() => {
349
- setIsFocussed(false);
350
- return setTokenIndex(null);
351
- }}
352
- variant={variant}
353
- />
354
- </Flex>
355
- )}
356
- </Flex>
357
- </Box>
358
- );
359
- }
360
- );
361
-
362
- export default StackedPilledInput;
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { Box, Flex, Input, Text, useOutsideClick } from '@chakra-ui/react';
3
+ import { InputFieldProps } from '../InputTypes';
4
+ import {
5
+ Control,
6
+ FieldValues,
7
+ UseFormClearErrors,
8
+ UseFormSetError,
9
+ UseFormSetValue,
10
+ useWatch,
11
+ } from 'react-hook-form';
12
+ import colors from '../../../theme/foundations/colors';
13
+ import Token from '../components/token';
14
+
15
+ export interface StackedPilledInputProps extends InputFieldProps {
16
+ setValue: UseFormSetValue<FieldValues>;
17
+ setError: UseFormSetError<FieldValues>;
18
+ clearErrors: UseFormClearErrors<FieldValues>;
19
+ control: Control<FieldValues, any>;
20
+ separators?: string[];
21
+ variant?: string;
22
+ label?: string;
23
+ }
24
+
25
+ /**
26
+ * A functional React component utilized to render the `StackedPilledInput` component.
27
+ */
28
+ const StackedPilledInput = React.forwardRef<
29
+ HTMLInputElement,
30
+ StackedPilledInputProps
31
+ >(
32
+ (
33
+ {
34
+ name,
35
+ setValue,
36
+ control,
37
+ placeholder,
38
+ disabled,
39
+ separators = ['Enter', ' ', ',', ';', 'Tab'],
40
+ variant,
41
+ label,
42
+ },
43
+ _ref
44
+ ) => {
45
+ const watchedValue = useWatch({ control, name: name as string });
46
+ const [lastestFormValueToArray, setLatestFormValueToArray] = useState<
47
+ string[]
48
+ >([]);
49
+
50
+ const inputRef = useRef<HTMLInputElement>(null);
51
+ const inputWrapperRef = useRef(null);
52
+ const scrollRef = useRef<HTMLDivElement>(null);
53
+
54
+ const [tokenIndex, setTokenIndex] = useState<number | null>(null);
55
+ const [isFocussed, setIsFocussed] = useState(false);
56
+ const [localValue, setLocalValue] = useState('');
57
+
58
+ const latestTokenElement = document.getElementById(
59
+ `${name}_token_${lastestFormValueToArray.length - 1}`
60
+ );
61
+
62
+ // gets latest watched form value (common delimited) from RHF state and creates a list
63
+ useEffect(() => {
64
+ if (watchedValue !== undefined && !watchedValue.length) {
65
+ setLatestFormValueToArray([]);
66
+ }
67
+
68
+ if (watchedValue !== undefined && watchedValue?.length) {
69
+ setLatestFormValueToArray(
70
+ watchedValue
71
+ .split(';')
72
+ .join(',')
73
+ .split(',')
74
+ .filter(Boolean)
75
+ );
76
+
77
+ if (latestTokenElement) {
78
+ latestTokenElement.scrollIntoView({
79
+ block: 'end',
80
+ inline: 'center',
81
+ behavior: 'smooth',
82
+ });
83
+ }
84
+ }
85
+ }, [latestTokenElement, watchedValue]);
86
+
87
+ const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
88
+ if (tokenIndex === null) {
89
+ setLocalValue(
90
+ e.target.value
91
+ .trim()
92
+ .replace(',', '')
93
+ .replace(';', '').length
94
+ ? e.target.value
95
+ : ''
96
+ );
97
+ }
98
+ };
99
+
100
+ const onHandleKeyDown = (e: React.KeyboardEvent) => {
101
+ if (e.key === 'Enter') {
102
+ e.stopPropagation();
103
+ e.preventDefault();
104
+ }
105
+
106
+ if (separators.includes(e.key)) {
107
+ if (
108
+ e.key === 'Enter' &&
109
+ !localValue.trim().length &&
110
+ tokenIndex !== null
111
+ ) {
112
+ setLocalValue(lastestFormValueToArray[tokenIndex]);
113
+
114
+ const filteredUniqueValues = Array.from(
115
+ new Set(
116
+ lastestFormValueToArray.filter(
117
+ value => value !== lastestFormValueToArray[tokenIndex]
118
+ )
119
+ )
120
+ );
121
+
122
+ setValue(name as string, filteredUniqueValues.toString(), {
123
+ shouldValidate: true,
124
+ shouldDirty: true,
125
+ });
126
+
127
+ return setTokenIndex(null);
128
+ }
129
+
130
+ const filteredUniqueValues = Array.from(
131
+ new Set([
132
+ ...lastestFormValueToArray,
133
+ ...localValue
134
+ .trim()
135
+ .split(';')
136
+ .join(',')
137
+ .split(','),
138
+ ])
139
+ );
140
+
141
+ setLocalValue('');
142
+
143
+ return setValue(name as string, filteredUniqueValues.toString(), {
144
+ shouldValidate: true,
145
+ shouldDirty: true,
146
+ });
147
+ }
148
+
149
+ if (!localValue.trim().length && lastestFormValueToArray.length) {
150
+ if (e.key === 'Backspace' && tokenIndex !== null) {
151
+ setLocalValue(
152
+ lastestFormValueToArray[tokenIndex].substring(
153
+ 0,
154
+ lastestFormValueToArray[tokenIndex].length
155
+ )
156
+ );
157
+
158
+ const filteredUniqueValues = Array.from(
159
+ new Set(
160
+ [...lastestFormValueToArray].filter(
161
+ value => value !== lastestFormValueToArray[tokenIndex]
162
+ )
163
+ )
164
+ );
165
+
166
+ setValue(name as string, filteredUniqueValues.toString(), {
167
+ shouldValidate: true,
168
+ shouldDirty: true,
169
+ });
170
+
171
+ return setTokenIndex(null);
172
+ }
173
+
174
+ if (e.key === 'ArrowLeft') {
175
+ if (tokenIndex === 0) return;
176
+
177
+ if (!tokenIndex) {
178
+ return setTokenIndex(lastestFormValueToArray.length - 1);
179
+ }
180
+
181
+ setTokenIndex(prevTokenIndex => (prevTokenIndex as number) - 1);
182
+
183
+ const tokenElement = document.getElementById(
184
+ `${name}_token_${tokenIndex}`
185
+ );
186
+
187
+ if (!tokenElement || !scrollRef.current) return;
188
+
189
+ return scrollRef.current.scrollBy({
190
+ left: -1 * tokenElement.getBoundingClientRect().width,
191
+ behavior: 'smooth',
192
+ });
193
+ }
194
+
195
+ if (e.key === 'ArrowRight') {
196
+ if (tokenIndex === null) return;
197
+
198
+ if (tokenIndex === lastestFormValueToArray.length - 1) {
199
+ return setTokenIndex(null);
200
+ }
201
+ setTokenIndex(prevTokenIndex => (prevTokenIndex as number) + 1);
202
+
203
+ const tokenElement = document.getElementById(
204
+ `${name}_token_${tokenIndex}`
205
+ );
206
+
207
+ if (!tokenElement || !scrollRef.current) return;
208
+
209
+ return scrollRef.current.scrollBy({
210
+ left: tokenElement.getBoundingClientRect().width,
211
+ behavior: 'smooth',
212
+ });
213
+ }
214
+ }
215
+ };
216
+
217
+ const onRemoveTag = (index: number) => {
218
+ const filteredUniqueValues = lastestFormValueToArray.filter(
219
+ (_, i) => i !== index
220
+ );
221
+
222
+ setLatestFormValueToArray(filteredUniqueValues);
223
+
224
+ setValue(name as string, filteredUniqueValues.toString(), {
225
+ shouldValidate: true,
226
+ shouldDirty: true,
227
+ });
228
+ };
229
+
230
+ const onBlur = () => {
231
+ if (localValue.trim().length) {
232
+ const filteredUniqueValues = Array.from(
233
+ new Set([...lastestFormValueToArray, ...localValue.trim().split(',')])
234
+ );
235
+
236
+ setValue(name as string, filteredUniqueValues.toString(), {
237
+ shouldValidate: true,
238
+ shouldDirty: true,
239
+ });
240
+ setLocalValue('');
241
+ }
242
+ setIsFocussed(false);
243
+ };
244
+
245
+ useOutsideClick({
246
+ ref: inputWrapperRef,
247
+ handler: () => {
248
+ onBlur();
249
+ },
250
+ });
251
+ const isMobile = variant === 'mobile';
252
+
253
+ return (
254
+ <Box position="relative">
255
+ <Flex
256
+ fontSize={isMobile ? '17px' : '13px'}
257
+ border={isFocussed ? '2px solid' : '.5px solid'}
258
+ borderColor={isFocussed ? colors.border.focus : colors.border.default}
259
+ pl="8px"
260
+ borderRadius={isMobile ? '0' : '4px'}
261
+ alignItems="center"
262
+ justifyContent="space-between"
263
+ style={
264
+ isMobile
265
+ ? {
266
+ cursor: 'pointer',
267
+ height: '48px',
268
+ fontSize: '17px',
269
+ lineHeight: '20px',
270
+ fontWeight: 400,
271
+ padding: '12px 16px 12px 0px',
272
+ borderRadius: 0,
273
+ border: '0.5px solid rgba(153, 153, 153, 0.1)',
274
+ borderLeft: 'none',
275
+ borderRight: 'none',
276
+ }
277
+ : undefined
278
+ }
279
+ onClick={() => {
280
+ if (isFocussed && tokenIndex !== null) {
281
+ setTokenIndex(null);
282
+ }
283
+
284
+ if (!disabled) {
285
+ inputRef.current?.focus();
286
+ }
287
+ }}
288
+ bg={disabled ? colors.fill.light.quaternary : '#ffffff'}
289
+ cursor={disabled ? 'not-allowed' : 'pointer'}
290
+ ref={inputWrapperRef}
291
+ h={isMobile ? '48px' : '26px'}
292
+ >
293
+ <Flex
294
+ h="100%"
295
+ alignItems="center"
296
+ overflowX="scroll"
297
+ overflowY="hidden"
298
+ maxWidth={isFocussed ? '80%' : '100%'}
299
+ style={{
300
+ scrollbarWidth: 'none' /* Firefox */,
301
+ msOverflowStyle: 'none',
302
+ }}
303
+ sx={{
304
+ '::-webkit-scrollbar': {
305
+ display: 'none',
306
+ },
307
+ }}
308
+ ref={scrollRef}
309
+ zIndex={100}
310
+ onKeyDown={onHandleKeyDown}
311
+ >
312
+ {lastestFormValueToArray.length
313
+ ? lastestFormValueToArray.map((label, index) => (
314
+ <Box
315
+ key={index}
316
+ mr="4px"
317
+ border={
318
+ tokenIndex === index
319
+ ? `1px solid ${colors.border.focus}`
320
+ : 'none'
321
+ }
322
+ borderRadius="full"
323
+ onClick={() => setTokenIndex(index)}
324
+ width="100%"
325
+ id={`${name}_token_${index}`}
326
+ >
327
+ <Token
328
+ label={label}
329
+ onDelete={(e: any) => {
330
+ e.stopPropagation();
331
+ e.preventDefault();
332
+ onRemoveTag(index);
333
+ }}
334
+ isMobile={isMobile}
335
+ />
336
+ </Box>
337
+ ))
338
+ : null}
339
+ {!lastestFormValueToArray.length && !isFocussed ? (
340
+ <Text
341
+ color={colors.label.secondary.light}
342
+ fontSize={isMobile ? '17px' : '13px'}
343
+ >
344
+ {placeholder}
345
+ </Text>
346
+ ) : null}
347
+ </Flex>
348
+ {!disabled && (
349
+ <Flex flex={1} minWidth={isFocussed && !tokenIndex ? '20%' : 0}>
350
+ <Input
351
+ onKeyDown={onHandleKeyDown}
352
+ type="text"
353
+ padding={0}
354
+ alignContent="flex-start"
355
+ float="right"
356
+ border="none"
357
+ height="auto"
358
+ color={
359
+ tokenIndex !== null ? 'transparent' : colors.label.primary
360
+ }
361
+ _focus={{ boxShadow: 'none !important' }}
362
+ value={localValue}
363
+ onChange={handleOnChange}
364
+ ref={inputRef}
365
+ onFocus={() => setIsFocussed(true)}
366
+ onBlur={() => {
367
+ setIsFocussed(false);
368
+ return setTokenIndex(null);
369
+ }}
370
+ placeholder={
371
+ isMobile && label && lastestFormValueToArray.length === 0
372
+ ? (label as string)
373
+ : ''
374
+ }
375
+ variant={variant}
376
+ style={isMobile ? { border: 'none' } : undefined}
377
+ />
378
+ </Flex>
379
+ )}
380
+ </Flex>
381
+ </Box>
382
+ );
383
+ }
384
+ );
385
+
386
+ export default StackedPilledInput;