@xqmsg/ui-core 0.23.1-rc.1 → 0.23.1-rc.3

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