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

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