@xqmsg/ui-core 0.23.2 → 0.24.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 (196) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +40 -40
  3. package/dist/{78c9d6fd7766410f.svg → 89793640b494d7ea.svg} +9 -9
  4. package/dist/components/input/InputTypes.d.ts +1 -1
  5. package/dist/components/input/index.d.ts +3 -3
  6. package/dist/components/select/index.d.ts +6 -5
  7. package/dist/components/text/index.d.ts +2 -2
  8. package/dist/ui-core.cjs.development.js +12 -19
  9. package/dist/ui-core.cjs.development.js.map +1 -1
  10. package/dist/ui-core.cjs.production.min.js +1 -1
  11. package/dist/ui-core.cjs.production.min.js.map +1 -1
  12. package/dist/ui-core.esm.js +12 -19
  13. package/dist/ui-core.esm.js.map +1 -1
  14. package/package.json +118 -118
  15. package/src/components/banner/Banner.stories.tsx +100 -100
  16. package/src/components/banner/index.tsx +75 -73
  17. package/src/components/breadcrumbs/Breadcrumbs.stories.tsx +66 -66
  18. package/src/components/breadcrumbs/components/icon/index.tsx +38 -38
  19. package/src/components/breadcrumbs/components/label/index.tsx +20 -20
  20. package/src/components/breadcrumbs/index.tsx +48 -48
  21. package/src/components/button/Button.stories.tsx +140 -140
  22. package/src/components/button/google/GoogleButton.stories.tsx +23 -23
  23. package/src/components/button/google/index.tsx +29 -29
  24. package/src/components/button/index.tsx +51 -51
  25. package/src/components/button/microsoft/MicrosoftButton.stories.tsx +25 -25
  26. package/src/components/button/microsoft/index.tsx +29 -29
  27. package/src/components/button/spinner/SpinnerButton.stories.tsx +60 -60
  28. package/src/components/button/spinner/index.tsx +36 -36
  29. package/src/components/card/Card.stories.tsx +56 -56
  30. package/src/components/card/index.tsx +78 -78
  31. package/src/components/form/Form.stories.tsx +62 -62
  32. package/src/components/form/FormTypes.ts +20 -20
  33. package/src/components/form/hooks/useFormHandler.tsx +74 -74
  34. package/src/components/form/index.tsx +25 -25
  35. package/src/components/form/section/FormSection.stories.tsx +109 -109
  36. package/src/components/form/section/index.tsx +87 -87
  37. package/src/components/form/utils/formErrors.ts +34 -34
  38. package/src/components/icons/checkmark/checkmark.svg +3 -3
  39. package/src/components/icons/checkmark/index.tsx +13 -13
  40. package/src/components/icons/chevron/down/chevron-down.svg +3 -3
  41. package/src/components/icons/chevron/down/index.tsx +14 -14
  42. package/src/components/icons/chevron/right/chevron-right.svg +3 -3
  43. package/src/components/icons/chevron/right/index.tsx +13 -13
  44. package/src/components/icons/clock/clock.svg +3 -3
  45. package/src/components/icons/clock/index.tsx +13 -13
  46. package/src/components/icons/close/close.svg +3 -3
  47. package/src/components/icons/close/index.tsx +21 -21
  48. package/src/components/icons/dropdown/dropdown.svg +3 -3
  49. package/src/components/icons/dropdown/index.tsx +16 -16
  50. package/src/components/icons/error/error.svg +3 -3
  51. package/src/components/icons/error/index.tsx +13 -13
  52. package/src/components/icons/file/fill/file-fill.svg +4 -4
  53. package/src/components/icons/file/fill/index.tsx +13 -13
  54. package/src/components/icons/file/outline/file-outline.svg +3 -3
  55. package/src/components/icons/file/outline/index.tsx +13 -13
  56. package/src/components/icons/folder/add/fill/folder-add-fill.svg +3 -3
  57. package/src/components/icons/folder/add/fill/index.tsx +13 -13
  58. package/src/components/icons/folder/add/outline/folder-add-outline.svg +3 -3
  59. package/src/components/icons/folder/add/outline/index.tsx +15 -15
  60. package/src/components/icons/folder/fill/folder-fill-gradient.svg +33 -33
  61. package/src/components/icons/folder/fill/folder-fill.svg +4 -4
  62. package/src/components/icons/folder/fill/index.tsx +21 -21
  63. package/src/components/icons/folder/outline/folder-outline.svg +3 -3
  64. package/src/components/icons/folder/outline/index.tsx +13 -13
  65. package/src/components/icons/gear/GearIcon.tsx +36 -36
  66. package/src/components/icons/google/drive/index.tsx +13 -13
  67. package/src/components/icons/google/google.svg +13 -13
  68. package/src/components/icons/google/index.tsx +13 -13
  69. package/src/components/icons/group/group.svg +3 -3
  70. package/src/components/icons/group/index.tsx +13 -13
  71. package/src/components/icons/home/home.svg +3 -3
  72. package/src/components/icons/home/index.tsx +13 -13
  73. package/src/components/icons/image/image.svg +3 -3
  74. package/src/components/icons/image/index.tsx +13 -13
  75. package/src/components/icons/index.tsx +101 -101
  76. package/src/components/icons/link/index.tsx +13 -13
  77. package/src/components/icons/link/link.svg +4 -4
  78. package/src/components/icons/menu/index.tsx +13 -13
  79. package/src/components/icons/menu/menu.svg +3 -3
  80. package/src/components/icons/microsoft/index.tsx +13 -13
  81. package/src/components/icons/microsoft/microsoft.svg +9 -9
  82. package/src/components/icons/microsoft/onedrive/index.tsx +16 -16
  83. package/src/components/icons/neutral/index.tsx +14 -14
  84. package/src/components/icons/neutral/neutral.svg +3 -3
  85. package/src/components/icons/page/index.tsx +13 -13
  86. package/src/components/icons/page/page.svg +3 -3
  87. package/src/components/icons/positive/index.tsx +13 -13
  88. package/src/components/icons/positive/positive.svg +3 -3
  89. package/src/components/icons/question/index.tsx +13 -13
  90. package/src/components/icons/question/question.svg +3 -3
  91. package/src/components/icons/search/index.tsx +13 -13
  92. package/src/components/icons/search/search.svg +3 -3
  93. package/src/components/icons/services/index.tsx +13 -13
  94. package/src/components/icons/services/services.svg +3 -3
  95. package/src/components/icons/settings/index.tsx +14 -14
  96. package/src/components/icons/settings/settings.svg +6 -6
  97. package/src/components/icons/table/fill/index.tsx +13 -13
  98. package/src/components/icons/table/fill/table-fill.svg +3 -3
  99. package/src/components/icons/table/outline/index.tsx +13 -13
  100. package/src/components/icons/table/outline/table-outline.svg +3 -3
  101. package/src/components/icons/task/index.tsx +10 -10
  102. package/src/components/icons/task/task.svg +11 -11
  103. package/src/components/icons/trash/index.tsx +13 -13
  104. package/src/components/icons/trash/trash.svg +3 -3
  105. package/src/components/icons/vault/index.tsx +14 -14
  106. package/src/components/icons/video/index.tsx +13 -13
  107. package/src/components/icons/video/video.svg +3 -3
  108. package/src/components/icons/warning/index.tsx +13 -13
  109. package/src/components/icons/warning/warning.svg +3 -3
  110. package/src/components/icons/workspace/index.tsx +14 -14
  111. package/src/components/input/Input.stories.tsx +287 -287
  112. package/src/components/input/InputTypes.ts +83 -77
  113. package/src/components/input/StackedCheckbox/StackedCheckbox.tsx +44 -44
  114. package/src/components/input/StackedInput/StackedInput.tsx +60 -60
  115. package/src/components/input/StackedMultiSelect/index.tsx +349 -349
  116. package/src/components/input/StackedPilledInput/index.tsx +386 -386
  117. package/src/components/input/StackedRadio/StackedRadioGroup.tsx +38 -38
  118. package/src/components/input/StackedSelect/index.tsx +232 -232
  119. package/src/components/input/StackedSwitch/index.tsx +33 -33
  120. package/src/components/input/StackedTextarea/StackedTextarea.tsx +55 -55
  121. package/src/components/input/components/dropdown/index.tsx +111 -111
  122. package/src/components/input/components/label/index.tsx +35 -35
  123. package/src/components/input/components/token/Token.stories.tsx +25 -25
  124. package/src/components/input/components/token/index.tsx +45 -45
  125. package/src/components/input/index.tsx +299 -298
  126. package/src/components/layout/BorderedBox/index.tsx +30 -30
  127. package/src/components/layout/Layout.stories.tsx +40 -40
  128. package/src/components/layout/index.tsx +100 -100
  129. package/src/components/link/Link.stories.tsx +23 -23
  130. package/src/components/link/index.tsx +34 -34
  131. package/src/components/loading/LoadingIndicator.stories.tsx +45 -45
  132. package/src/components/loading/index.tsx +45 -45
  133. package/src/components/modal/Modal.stories.tsx +36 -36
  134. package/src/components/modal/components/action/index.tsx +37 -37
  135. package/src/components/modal/index.tsx +41 -41
  136. package/src/components/navigation/NavigationMenu.stories.tsx +85 -85
  137. package/src/components/navigation/components/header/index.tsx +27 -27
  138. package/src/components/navigation/components/items/index.tsx +76 -76
  139. package/src/components/navigation/index.tsx +87 -87
  140. package/src/components/select/index.tsx +139 -140
  141. package/src/components/table/Table.stories.tsx +63 -63
  142. package/src/components/table/TableTypes.ts +15 -15
  143. package/src/components/table/components/loading/index.tsx +45 -45
  144. package/src/components/table/components/text/index.tsx +23 -23
  145. package/src/components/table/empty/index.tsx +47 -47
  146. package/src/components/table/index.tsx +84 -84
  147. package/src/components/table/utils/generateTableColumns.ts +9 -9
  148. package/src/components/tabs/TabsWrapper.stories.tsx +85 -85
  149. package/src/components/tabs/index.tsx +39 -39
  150. package/src/components/text/Text.stories.tsx +59 -59
  151. package/src/components/text/index.tsx +16 -16
  152. package/src/components/toast/Toast.stories.tsx +52 -52
  153. package/src/components/toast/index.tsx +78 -78
  154. package/src/components/toolbar/Toolbar.stories.tsx +59 -59
  155. package/src/components/toolbar/components/actions/add/index.tsx +18 -18
  156. package/src/components/toolbar/components/actions/search/index.tsx +38 -38
  157. package/src/components/toolbar/components/actions/sort/index.tsx +49 -49
  158. package/src/components/toolbar/components/breadcrumbs/index.tsx +63 -63
  159. package/src/components/toolbar/components/breadcrumbs/item/index.tsx +72 -72
  160. package/src/components/toolbar/components/dropdown/index.tsx +107 -107
  161. package/src/components/toolbar/components/navigation/components/button/left/index.tsx +28 -28
  162. package/src/components/toolbar/components/navigation/components/button/left/left-arrow.svg +3 -3
  163. package/src/components/toolbar/components/navigation/components/button/right/index.tsx +27 -27
  164. package/src/components/toolbar/components/navigation/components/button/right/right-arrow.svg +3 -3
  165. package/src/components/toolbar/components/navigation/index.tsx +36 -36
  166. package/src/components/toolbar/index.tsx +55 -55
  167. package/src/hooks/useDeepEffect.tsx +22 -22
  168. package/src/hooks/useDidMountEffect.tsx +13 -13
  169. package/src/hooks/useOnOutsideClick.tsx +31 -31
  170. package/src/hooks/useToast.tsx +16 -16
  171. package/src/index.tsx +78 -78
  172. package/src/theme/components/alert.ts +60 -60
  173. package/src/theme/components/badge.ts +59 -59
  174. package/src/theme/components/button.ts +163 -163
  175. package/src/theme/components/checkbox.ts +28 -28
  176. package/src/theme/components/code.ts +16 -16
  177. package/src/theme/components/form-error.ts +31 -31
  178. package/src/theme/components/form-label.ts +17 -17
  179. package/src/theme/components/form.ts +29 -29
  180. package/src/theme/components/input.ts +65 -65
  181. package/src/theme/components/link.ts +118 -118
  182. package/src/theme/components/modal.ts +45 -45
  183. package/src/theme/components/select.ts +36 -36
  184. package/src/theme/components/switch.ts +89 -89
  185. package/src/theme/components/table.ts +42 -42
  186. package/src/theme/components/tabs.ts +255 -255
  187. package/src/theme/components/text.ts +93 -93
  188. package/src/theme/components/textarea.ts +42 -42
  189. package/src/theme/customXQChakraTheme.ts +54 -54
  190. package/src/theme/foundations/breakpoints.ts +18 -18
  191. package/src/theme/foundations/colors.ts +165 -165
  192. package/src/theme/foundations/shadows.ts +23 -23
  193. package/src/theme/foundations/typography.ts +62 -62
  194. package/src/theme/provider/index.tsx +21 -21
  195. package/src/theme/styles.ts +19 -19
  196. package/CHANGELOG.md +0 -468
@@ -1,386 +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
- 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
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;