react-core-ts 1.0.26 → 1.0.28
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.
- package/dist/components/TreeNode.js +1 -1
- package/dist/components/TreeNode.js.map +1 -1
- package/package.json +2 -2
- package/src/AutoComplete.tsx +489 -0
- package/src/AutoCompleteWithTreeStructure.tsx +611 -0
- package/src/ExpandableAutoComplete.tsx +545 -0
- package/src/commontypes.ts +45 -0
- package/src/components/DropdownList.tsx +70 -0
- package/src/components/Icons.tsx +38 -0
- package/src/components/InputActions.tsx +63 -0
- package/src/components/TreeNode.tsx +87 -0
- package/src/index.html +1 -0
- package/src/index.tsx +12 -0
- package/src/utilities/autosuggestions.ts +83 -0
- package/src/utilities/convertFlatArraytoTreeStructure.ts +27 -0
- package/src/utilities/debounce.ts +15 -0
- package/src/utilities/deepEqual.ts +54 -0
- package/src/utilities/expandableTootltip.tsx +147 -0
- package/src/utilities/filterSuggestions.ts +48 -0
- package/src/utilities/icons.tsx +86 -0
- package/src/utilities/tooltip.tsx +52 -0
- package/tailwind.config.js +1 -0
- /package/{dist/styles → src}/styles/components/autocomplete.css +0 -0
- /package/{dist/styles → src}/styles/global.css +0 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import ReactDOM from "react-dom";
|
|
10
|
+
|
|
11
|
+
import { AutoSuggestionInputProps } from "./commontypes";
|
|
12
|
+
import DropdownList from "./components/DropdownList";
|
|
13
|
+
import InputActions from "./components/InputActions";
|
|
14
|
+
import { useSuggestions } from "./utilities/autosuggestions";
|
|
15
|
+
import { debounce } from "./utilities/debounce";
|
|
16
|
+
import { deepEqual } from "./utilities/deepEqual";
|
|
17
|
+
import { default as Tooltip } from "./utilities/expandableTootltip";
|
|
18
|
+
import { filterSuggestions } from "./utilities/filterSuggestions";
|
|
19
|
+
import { Search, Spinner } from "./utilities/icons";
|
|
20
|
+
|
|
21
|
+
type ValueProps = {
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
};
|
|
24
|
+
const ExpandableAutoComplete = forwardRef<
|
|
25
|
+
HTMLInputElement,
|
|
26
|
+
AutoSuggestionInputProps
|
|
27
|
+
>(
|
|
28
|
+
(
|
|
29
|
+
{
|
|
30
|
+
label,
|
|
31
|
+
onChange,
|
|
32
|
+
getData = async () => [],
|
|
33
|
+
data = [],
|
|
34
|
+
errors,
|
|
35
|
+
required = false,
|
|
36
|
+
name,
|
|
37
|
+
fullWidth = false,
|
|
38
|
+
placeholder,
|
|
39
|
+
id,
|
|
40
|
+
type = "custom_select",
|
|
41
|
+
selectedItems: propsSeelctedItems = [],
|
|
42
|
+
readOnly = false,
|
|
43
|
+
disabled = false,
|
|
44
|
+
value,
|
|
45
|
+
isMultiple = false,
|
|
46
|
+
desc = "name",
|
|
47
|
+
descId = "id",
|
|
48
|
+
singleSelect,
|
|
49
|
+
className,
|
|
50
|
+
async = false,
|
|
51
|
+
paginationEnabled,
|
|
52
|
+
initialLoad,
|
|
53
|
+
actionLabel,
|
|
54
|
+
handleAction,
|
|
55
|
+
nextBlock,
|
|
56
|
+
notDataMessage,
|
|
57
|
+
onFocus,
|
|
58
|
+
expandable = false,
|
|
59
|
+
textCount = 10,
|
|
60
|
+
itemCount = 1,
|
|
61
|
+
scrollRef,
|
|
62
|
+
isTreeDropdown = false,
|
|
63
|
+
},
|
|
64
|
+
ref
|
|
65
|
+
) => {
|
|
66
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
67
|
+
// State Hooks Section
|
|
68
|
+
const [isInitialRender, setIsInitialRender] = useState(true);
|
|
69
|
+
|
|
70
|
+
const [inputValue, setInputValue] = useState<string>(value);
|
|
71
|
+
const [searchValue, setSearchValue] = useState<string>("");
|
|
72
|
+
const [nextPage, setNextPage] = useState<number | undefined>(1);
|
|
73
|
+
const [dropOpen, setDropOpen] = useState<boolean>(false);
|
|
74
|
+
const [selectedItems, setSelectedItems] = useState<ValueProps[]>([]);
|
|
75
|
+
// API call for suggestions through a custom hook
|
|
76
|
+
const inputRef = useRef(null);
|
|
77
|
+
const dropRef = useRef(null);
|
|
78
|
+
useImperativeHandle(ref, () => inputRef.current);
|
|
79
|
+
const [dropdownStyle, setDropdownStyle] = useState({
|
|
80
|
+
top: 0,
|
|
81
|
+
left: 0,
|
|
82
|
+
width: 200,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const adjustDropdownPosition = () => {
|
|
86
|
+
if (dropdownRef.current) {
|
|
87
|
+
const inputRect = dropdownRef.current.getBoundingClientRect();
|
|
88
|
+
const dropdownPosition = {
|
|
89
|
+
left: inputRect.left + window.scrollX,
|
|
90
|
+
width: inputRect.width,
|
|
91
|
+
top: 0,
|
|
92
|
+
};
|
|
93
|
+
// Check if there's enough space below the input for the dropdown
|
|
94
|
+
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
95
|
+
const spaceAbove = inputRect.top + window.scrollY;
|
|
96
|
+
|
|
97
|
+
const dropdownHeight = 275; // Assume a fixed height or calculate based on content
|
|
98
|
+
if (spaceBelow >= dropdownHeight) {
|
|
99
|
+
dropdownPosition.top =
|
|
100
|
+
inputRect.top + window.scrollY + inputRect.height;
|
|
101
|
+
} else {
|
|
102
|
+
dropdownPosition.top =
|
|
103
|
+
inputRect.top + window.scrollY - dropdownHeight + 73;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setDropdownStyle({
|
|
107
|
+
...dropdownPosition,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
window.addEventListener("resize", adjustDropdownPosition);
|
|
114
|
+
adjustDropdownPosition();
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
window.removeEventListener("resize", adjustDropdownPosition);
|
|
118
|
+
};
|
|
119
|
+
}, [dropOpen, selectedItems]);
|
|
120
|
+
|
|
121
|
+
const { suggestions, isLoading, handlePickSuggestions } = useSuggestions(
|
|
122
|
+
getData,
|
|
123
|
+
data,
|
|
124
|
+
dropOpen,
|
|
125
|
+
async,
|
|
126
|
+
paginationEnabled,
|
|
127
|
+
initialLoad,
|
|
128
|
+
inputValue,
|
|
129
|
+
isMultiple,
|
|
130
|
+
setNextPage,
|
|
131
|
+
selectedItems
|
|
132
|
+
// nextBlock
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Handling the selection of a suggestion
|
|
136
|
+
const handleSuggestionClick = useCallback((suggestion: ValueProps) => {
|
|
137
|
+
if (isMultiple) {
|
|
138
|
+
setSelectedItems((prev) => [...prev, suggestion]);
|
|
139
|
+
} else {
|
|
140
|
+
setInputValue(suggestion[desc]);
|
|
141
|
+
}
|
|
142
|
+
setSearchValue("");
|
|
143
|
+
setInputValue(suggestion[desc]);
|
|
144
|
+
onChange(suggestion);
|
|
145
|
+
setDropOpen(false);
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
// Adding debounce to avoid making API calls on every keystroke
|
|
149
|
+
const handleChangeWithDebounce = debounce((value) => {
|
|
150
|
+
if ((type === "auto_complete" || type === "auto_suggestion") && async) {
|
|
151
|
+
handlePickSuggestions(value, 1);
|
|
152
|
+
}
|
|
153
|
+
}, 1000);
|
|
154
|
+
|
|
155
|
+
const handleMultiSelect = (e: any, suggestion: ValueProps) => {
|
|
156
|
+
const { checked } = e.target;
|
|
157
|
+
if (isMultiple) {
|
|
158
|
+
if (checked) {
|
|
159
|
+
setSelectedItems((prev) => [...prev, suggestion]);
|
|
160
|
+
} else {
|
|
161
|
+
setSelectedItems((prev) => {
|
|
162
|
+
return prev.filter(
|
|
163
|
+
(item, i) => item[descId] !== suggestion[descId]
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
if (checked) {
|
|
169
|
+
setInputValue(suggestion[desc]);
|
|
170
|
+
onChange(suggestion);
|
|
171
|
+
} else {
|
|
172
|
+
setInputValue("");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!deepEqual(selectedItems, propsSeelctedItems))
|
|
178
|
+
setSelectedItems(propsSeelctedItems);
|
|
179
|
+
}, [propsSeelctedItems]);
|
|
180
|
+
// Effect to set the input value whenever `value` prop changes
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
setInputValue(value ?? "");
|
|
183
|
+
}, [value]);
|
|
184
|
+
|
|
185
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
186
|
+
const { value } = e.target;
|
|
187
|
+
setDropOpen(true);
|
|
188
|
+
setSearchValue(value);
|
|
189
|
+
handleChangeWithDebounce(value);
|
|
190
|
+
if (!value) {
|
|
191
|
+
setInputValue("");
|
|
192
|
+
// onChange({ [descId]: '', [desc]: '' });
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const handleSuggestionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
196
|
+
const { value } = e.target;
|
|
197
|
+
setDropOpen(true);
|
|
198
|
+
setSearchValue(value);
|
|
199
|
+
handleChangeWithDebounce(value);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleBlur = () => {
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
setDropOpen(false);
|
|
205
|
+
}, 200);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const handleClear = () => {
|
|
209
|
+
if (searchValue) {
|
|
210
|
+
setSearchValue("");
|
|
211
|
+
setDropOpen(false);
|
|
212
|
+
} else {
|
|
213
|
+
setInputValue("");
|
|
214
|
+
onChange({ [descId]: "", [desc]: "" });
|
|
215
|
+
setDropOpen(false);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const generateClassName = useCallback(() => {
|
|
220
|
+
return `qbs-textfield-default ${className} ${
|
|
221
|
+
errors && errors?.message ? "textfield-error" : "textfield"
|
|
222
|
+
} ${expandable ? "expandable" : ""}`;
|
|
223
|
+
}, [errors, name]);
|
|
224
|
+
const handleRemoveSelectedItem = (index: number) => {
|
|
225
|
+
setSelectedItems((prev) => {
|
|
226
|
+
return prev.filter((_, i) => i !== index);
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (isInitialRender) {
|
|
232
|
+
setIsInitialRender(false);
|
|
233
|
+
} else {
|
|
234
|
+
onChange(selectedItems);
|
|
235
|
+
}
|
|
236
|
+
}, [selectedItems]);
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
const handleClickOutside = (event: any) => {
|
|
240
|
+
if (
|
|
241
|
+
dropRef.current &&
|
|
242
|
+
event.target instanceof Node &&
|
|
243
|
+
!dropRef.current.contains(event.target) &&
|
|
244
|
+
event.target.nodeName !== "svg"
|
|
245
|
+
) {
|
|
246
|
+
setDropOpen(false);
|
|
247
|
+
setSearchValue("");
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
document.addEventListener("mousedown", handleClickOutside as any);
|
|
251
|
+
window.addEventListener("scroll", handleClickOutside as any);
|
|
252
|
+
|
|
253
|
+
const scrollableDivs = document.querySelectorAll(
|
|
254
|
+
'div[style*="overflow"]'
|
|
255
|
+
);
|
|
256
|
+
scrollableDivs.forEach((div) =>
|
|
257
|
+
div.addEventListener("scroll", handleClickOutside as any)
|
|
258
|
+
);
|
|
259
|
+
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
260
|
+
scrollRef.current.addEventListener("scroll", handleClickOutside as any);
|
|
261
|
+
}
|
|
262
|
+
return () => {
|
|
263
|
+
document.removeEventListener("mousedown", handleClickOutside as any);
|
|
264
|
+
window.removeEventListener("scroll", handleClickOutside as any);
|
|
265
|
+
scrollableDivs.forEach((div) =>
|
|
266
|
+
div.removeEventListener("scroll", handleClickOutside as any)
|
|
267
|
+
);
|
|
268
|
+
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
269
|
+
scrollRef.current.removeEventListener(
|
|
270
|
+
"scroll",
|
|
271
|
+
handleClickOutside as any
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
277
|
+
// Filtering suggestions based on type and search value
|
|
278
|
+
const selected: any = isMultiple ? selectedItems : inputValue;
|
|
279
|
+
const filteredData: ValueProps[] = filterSuggestions(
|
|
280
|
+
suggestions,
|
|
281
|
+
searchValue,
|
|
282
|
+
type,
|
|
283
|
+
desc,
|
|
284
|
+
selected,
|
|
285
|
+
async,
|
|
286
|
+
isTreeDropdown
|
|
287
|
+
);
|
|
288
|
+
const isSelected = (
|
|
289
|
+
item: ValueProps,
|
|
290
|
+
selectedItems: ValueProps[] | string
|
|
291
|
+
): boolean => {
|
|
292
|
+
if (Array.isArray(selectedItems)) {
|
|
293
|
+
return selectedItems.some(
|
|
294
|
+
(selectedItem) => selectedItem[desc] === item[desc]
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
return item[desc] === selectedItems;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const handleLoadMore = () => {
|
|
302
|
+
if (paginationEnabled) {
|
|
303
|
+
handlePickSuggestions(searchValue, nextPage + 1, true);
|
|
304
|
+
setNextPage(nextPage + 1);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const handleOnClick = () => {
|
|
309
|
+
!disabled && !readOnly ? setDropOpen(true) : "";
|
|
310
|
+
};
|
|
311
|
+
const onInputFocus = () => {
|
|
312
|
+
if (inputRef.current) inputRef.current.focus();
|
|
313
|
+
handleOnClick();
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const tooltipContent =
|
|
317
|
+
selectedItems?.length > itemCount
|
|
318
|
+
? selectedItems
|
|
319
|
+
?.slice(itemCount)
|
|
320
|
+
.map((item) => item[desc])
|
|
321
|
+
.join(", ")
|
|
322
|
+
: "";
|
|
323
|
+
|
|
324
|
+
const handleDropOpen = (e: any) => {
|
|
325
|
+
if (!dropOpen) setDropOpen(true);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const handleDropClose = (e: any) => {
|
|
329
|
+
if (dropOpen) setDropOpen(false);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<div className={fullWidth ? "fullWidth" : "autoWidth"} ref={dropdownRef}>
|
|
334
|
+
{label && (
|
|
335
|
+
<div
|
|
336
|
+
style={{
|
|
337
|
+
marginBottom: 5,
|
|
338
|
+
display: "flex",
|
|
339
|
+
justifyContent: "space-between",
|
|
340
|
+
}}
|
|
341
|
+
>
|
|
342
|
+
<label className={`labels label-text`}>
|
|
343
|
+
{label}
|
|
344
|
+
{required && <span className="text-error"> *</span>}
|
|
345
|
+
</label>
|
|
346
|
+
<span onClick={() => handleAction?.()} className={`action_label`}>
|
|
347
|
+
{actionLabel}
|
|
348
|
+
</span>
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
{/* Displaying selected items for multi-select */}
|
|
352
|
+
|
|
353
|
+
<div
|
|
354
|
+
className={`${
|
|
355
|
+
expandable ? "qbs-expandable-container" : "qbs-container"
|
|
356
|
+
}`}
|
|
357
|
+
style={{ position: "relative" }}
|
|
358
|
+
>
|
|
359
|
+
{selectedItems?.length > 0 && (
|
|
360
|
+
<>
|
|
361
|
+
{selectedItems
|
|
362
|
+
.slice(
|
|
363
|
+
0,
|
|
364
|
+
selectedItems?.length > itemCount
|
|
365
|
+
? itemCount
|
|
366
|
+
: selectedItems?.length
|
|
367
|
+
)
|
|
368
|
+
.map((item, index) => (
|
|
369
|
+
<div key={index} className="selected-items-container">
|
|
370
|
+
<Tooltip
|
|
371
|
+
title={item?.[desc]}
|
|
372
|
+
enabled={item?.[desc]?.length > textCount}
|
|
373
|
+
>
|
|
374
|
+
<div key={item.id} className="selected-item">
|
|
375
|
+
{item?.[desc]?.length > textCount
|
|
376
|
+
? `${item?.[desc].substring(0, textCount)}...`
|
|
377
|
+
: item?.[desc]}
|
|
378
|
+
|
|
379
|
+
<button
|
|
380
|
+
onClick={() => handleRemoveSelectedItem(index)}
|
|
381
|
+
className="remove-item-btn"
|
|
382
|
+
aria-label={`Remove ${item?.[desc]}`}
|
|
383
|
+
>
|
|
384
|
+
X
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
</Tooltip>
|
|
388
|
+
</div>
|
|
389
|
+
))}
|
|
390
|
+
{selectedItems?.length > itemCount && (
|
|
391
|
+
<div className="selected-items-container">
|
|
392
|
+
<Tooltip title={tooltipContent} enabled={true}>
|
|
393
|
+
<div className="selected-item-more">
|
|
394
|
+
+{selectedItems?.length - itemCount} more
|
|
395
|
+
</div>
|
|
396
|
+
</Tooltip>
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
399
|
+
</>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
<div
|
|
403
|
+
className={`qbs-textfield-expandable ${
|
|
404
|
+
!expandable ? "qbs-normal" : ""
|
|
405
|
+
}`}
|
|
406
|
+
data-value={
|
|
407
|
+
selectedItems?.length > 0 || searchValue
|
|
408
|
+
? searchValue
|
|
409
|
+
: placeholder ?? ""
|
|
410
|
+
}
|
|
411
|
+
onClick={() => onInputFocus()}
|
|
412
|
+
>
|
|
413
|
+
<input
|
|
414
|
+
id={id}
|
|
415
|
+
ref={inputRef}
|
|
416
|
+
type="text"
|
|
417
|
+
value={
|
|
418
|
+
type === "auto_suggestion" && !expandable
|
|
419
|
+
? inputValue
|
|
420
|
+
: searchValue || inputValue
|
|
421
|
+
}
|
|
422
|
+
onChange={handleChange}
|
|
423
|
+
// onBlur={handleBlur}
|
|
424
|
+
onFocus={onFocus}
|
|
425
|
+
onClick={() => handleOnClick()}
|
|
426
|
+
className={generateClassName()}
|
|
427
|
+
placeholder={selectedItems?.length > 0 ? "" : placeholder ?? ""}
|
|
428
|
+
readOnly={
|
|
429
|
+
readOnly ||
|
|
430
|
+
type === "custom_select" ||
|
|
431
|
+
(type == "auto_suggestion" && !expandable)
|
|
432
|
+
}
|
|
433
|
+
disabled={disabled}
|
|
434
|
+
data-testid="custom-autocomplete"
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
{/* Icons for Clearing Input or Toggling Dropdown */}
|
|
439
|
+
|
|
440
|
+
<InputActions
|
|
441
|
+
inputValue={inputValue}
|
|
442
|
+
searchValue={searchValue}
|
|
443
|
+
dropOpen={dropOpen}
|
|
444
|
+
handleDropOpen={handleDropOpen}
|
|
445
|
+
handleDropClose={handleDropClose}
|
|
446
|
+
disabled={disabled}
|
|
447
|
+
readOnly={readOnly}
|
|
448
|
+
expandable={expandable}
|
|
449
|
+
handleClear={handleClear}
|
|
450
|
+
/>
|
|
451
|
+
{/* Displaying Loading Spinner */}
|
|
452
|
+
|
|
453
|
+
{/* Suggestions Dropdown */}
|
|
454
|
+
|
|
455
|
+
{dropOpen &&
|
|
456
|
+
ReactDOM.createPortal(
|
|
457
|
+
<ul
|
|
458
|
+
ref={dropRef}
|
|
459
|
+
style={{ ...dropdownStyle, minHeight: 192 }}
|
|
460
|
+
className={`qbs-autocomplete-suggestions`}
|
|
461
|
+
>
|
|
462
|
+
{type == "auto_suggestion" && !expandable && (
|
|
463
|
+
<div
|
|
464
|
+
style={{ position: "relative" }}
|
|
465
|
+
className="react-core-ts-search-container"
|
|
466
|
+
>
|
|
467
|
+
<span className="dropdown-search-icon">
|
|
468
|
+
<Search />
|
|
469
|
+
</span>
|
|
470
|
+
<input
|
|
471
|
+
className="dropdown-search-input"
|
|
472
|
+
onChange={handleSuggestionChange}
|
|
473
|
+
value={searchValue}
|
|
474
|
+
placeholder="Search"
|
|
475
|
+
/>
|
|
476
|
+
</div>
|
|
477
|
+
)}
|
|
478
|
+
|
|
479
|
+
<div className={`qbs-autocomplete-suggestions-sub `}>
|
|
480
|
+
{filteredData?.length > 0 ? (
|
|
481
|
+
filteredData.map((suggestion: ValueProps, idx: number) => (
|
|
482
|
+
<DropdownList
|
|
483
|
+
idx={idx}
|
|
484
|
+
suggestion={suggestion}
|
|
485
|
+
isSelected={isSelected}
|
|
486
|
+
handleSuggestionClick={handleSuggestionClick}
|
|
487
|
+
handleMultiSelect={handleMultiSelect}
|
|
488
|
+
selected={selected}
|
|
489
|
+
isMultiple={isMultiple}
|
|
490
|
+
singleSelect={singleSelect}
|
|
491
|
+
desc={desc}
|
|
492
|
+
/>
|
|
493
|
+
))
|
|
494
|
+
) : (
|
|
495
|
+
<>
|
|
496
|
+
{isLoading ? (
|
|
497
|
+
<div
|
|
498
|
+
style={{ display: "flex", justifyContent: "center" }}
|
|
499
|
+
>
|
|
500
|
+
<span>
|
|
501
|
+
<Spinner />
|
|
502
|
+
</span>
|
|
503
|
+
</div>
|
|
504
|
+
) : (
|
|
505
|
+
<li
|
|
506
|
+
className="qbs-autocomplete-notfound"
|
|
507
|
+
onClick={handleBlur}
|
|
508
|
+
>
|
|
509
|
+
{notDataMessage ?? "No Results Found"}
|
|
510
|
+
</li>
|
|
511
|
+
)}
|
|
512
|
+
</>
|
|
513
|
+
)}
|
|
514
|
+
{paginationEnabled &&
|
|
515
|
+
nextBlock !== 0 &&
|
|
516
|
+
nextBlock !== undefined &&
|
|
517
|
+
filteredData?.length > 0 && (
|
|
518
|
+
<div
|
|
519
|
+
className="loadMoreSection"
|
|
520
|
+
onClick={() => handleLoadMore()}
|
|
521
|
+
>
|
|
522
|
+
<p style={{ margin: 2 }}>Load More</p>
|
|
523
|
+
</div>
|
|
524
|
+
)}
|
|
525
|
+
</div>
|
|
526
|
+
</ul>,
|
|
527
|
+
document.body
|
|
528
|
+
)}
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
{/* Displaying Validation Error */}
|
|
532
|
+
{errors && (
|
|
533
|
+
<div
|
|
534
|
+
className="text-error text-error-label mt-[1px]"
|
|
535
|
+
data-testid="autocomplete-error"
|
|
536
|
+
>
|
|
537
|
+
{errors.message}
|
|
538
|
+
</div>
|
|
539
|
+
)}
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
export default React.memo(ExpandableAutoComplete);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type ValueProps = {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
};
|
|
4
|
+
export interface AutoSuggestionInputProps {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
fullWidth?: boolean;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
value?: string;
|
|
10
|
+
onChange: (value?: ValueProps | ValueProps[]) => void;
|
|
11
|
+
data?: any[];
|
|
12
|
+
type?:
|
|
13
|
+
| 'custom_select'
|
|
14
|
+
| 'auto_complete'
|
|
15
|
+
| 'custom_search_select'
|
|
16
|
+
| 'auto_suggestion';
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
getData?: (key?: string, nextBlock?: number) => any;
|
|
19
|
+
errors?: any;
|
|
20
|
+
name: string;
|
|
21
|
+
readOnly?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
isMultiple?: boolean;
|
|
24
|
+
desc?: string;
|
|
25
|
+
descId?: string;
|
|
26
|
+
singleSelect?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
async?: boolean;
|
|
29
|
+
scrollRef?: any;
|
|
30
|
+
paginationEnabled?: boolean;
|
|
31
|
+
nextBlock?: number;
|
|
32
|
+
initialLoad?: boolean;
|
|
33
|
+
handleAction?: () => void;
|
|
34
|
+
actionLabel?: string;
|
|
35
|
+
notDataMessage?: string;
|
|
36
|
+
selectedItems?: any[];
|
|
37
|
+
onFocus?: (e: React.FocusEvent<HTMLInputElement, Element>) => void;
|
|
38
|
+
dopDownRef?: HTMLInputElement;
|
|
39
|
+
expandable?: boolean;
|
|
40
|
+
textCount?: number;
|
|
41
|
+
itemCount?: number;
|
|
42
|
+
isTreeDropdown?: boolean;
|
|
43
|
+
flatArray?: boolean;
|
|
44
|
+
parentField?: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type ValueProps = {
|
|
4
|
+
idx: number;
|
|
5
|
+
suggestion: any;
|
|
6
|
+
isSelected: (item: any, selectedItems: any[] | string) => boolean;
|
|
7
|
+
handleSuggestionClick: (suggestion: any) => void;
|
|
8
|
+
handleMultiSelect: (e: any, suggestion: any) => void;
|
|
9
|
+
selected: any;
|
|
10
|
+
isMultiple?: boolean;
|
|
11
|
+
singleSelect?: boolean;
|
|
12
|
+
desc: string;
|
|
13
|
+
};
|
|
14
|
+
const DropdownList: React.FC<ValueProps> = ({
|
|
15
|
+
idx,
|
|
16
|
+
suggestion,
|
|
17
|
+
isSelected,
|
|
18
|
+
handleSuggestionClick,
|
|
19
|
+
handleMultiSelect,
|
|
20
|
+
selected,
|
|
21
|
+
isMultiple,
|
|
22
|
+
singleSelect,
|
|
23
|
+
desc,
|
|
24
|
+
}) => {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
key={idx.toString()}
|
|
28
|
+
className={`qbs-autocomplete-listitem-container ${
|
|
29
|
+
(isMultiple || singleSelect) && 'qbs-autocomplete-checkbox-container'
|
|
30
|
+
} ${isSelected(suggestion, selected) ? 'is-selected' : ''}`}
|
|
31
|
+
>
|
|
32
|
+
{(isMultiple || singleSelect) && (
|
|
33
|
+
<div className="qbs-autocomplete-checkbox">
|
|
34
|
+
<input
|
|
35
|
+
onChange={(e) => handleMultiSelect(e, suggestion)}
|
|
36
|
+
type="checkbox"
|
|
37
|
+
checked={isSelected(suggestion, selected)}
|
|
38
|
+
id={`qbs-checkbox-${idx.toString()}`}
|
|
39
|
+
/>
|
|
40
|
+
<label htmlFor={`qbs-checkbox-${idx.toString()}`}>
|
|
41
|
+
<svg
|
|
42
|
+
width="8"
|
|
43
|
+
height="6"
|
|
44
|
+
viewBox="0 0 8 6"
|
|
45
|
+
fill="none"
|
|
46
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
47
|
+
>
|
|
48
|
+
<path
|
|
49
|
+
d="M0 3.21739L2.89883 6L8 1.06994L6.89494 0L2.89883 3.86768L1.09728 2.14745L0 3.21739Z"
|
|
50
|
+
fill="white"
|
|
51
|
+
/>
|
|
52
|
+
</svg>
|
|
53
|
+
</label>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
<li
|
|
57
|
+
key={idx}
|
|
58
|
+
className={`qbs-autocomplete-suggestions-item ${
|
|
59
|
+
isSelected(suggestion, selected) ? 'is-selected' : ''
|
|
60
|
+
}`}
|
|
61
|
+
onClick={() => handleSuggestionClick(suggestion)}
|
|
62
|
+
data-testid={suggestion[desc]}
|
|
63
|
+
>
|
|
64
|
+
{suggestion[desc]}
|
|
65
|
+
</li>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default DropdownList;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export const DropdownIcon: React.FC<any> = (className) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="14"
|
|
7
|
+
height="12"
|
|
8
|
+
className={className}
|
|
9
|
+
viewBox="0 0 14 12"
|
|
10
|
+
fill="none"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
d="M13.2996 0.759638L0.699221 0.759638C0.571648 0.760037 0.446601 0.795222 0.337538 0.861405C0.228475 0.927588 0.139526 1.02226 0.080267 1.13523C0.0210075 1.24821 -0.00631905 1.37521 0.00122929 1.50256C0.00877762 1.62991 0.0509148 1.75279 0.123105 1.85797L6.42328 10.9582C6.68439 11.3355 7.31301 11.3355 7.57482 10.9582L13.875 1.85797C13.9479 1.75301 13.9907 1.63006 13.9986 1.5025C14.0066 1.37494 13.9794 1.24764 13.9201 1.13442C13.8608 1.02121 13.7716 0.926414 13.6622 0.860339C13.5528 0.794264 13.4274 0.759437 13.2996 0.759638Z"
|
|
15
|
+
fill="#003C71"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const DropSideIcon: React.FC<any> = (className) => {
|
|
22
|
+
return (
|
|
23
|
+
|
|
24
|
+
<svg
|
|
25
|
+
width="12"
|
|
26
|
+
height="14"
|
|
27
|
+
className={className}
|
|
28
|
+
viewBox="0 0 12 14"
|
|
29
|
+
fill="none"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
>
|
|
32
|
+
<path
|
|
33
|
+
d="M0.884639 0.700422V13.3008C0.885038 13.4284 0.920223 13.5534 0.986406 13.6625C1.05259 13.7715 1.14726 13.8605 1.26024 13.9197C1.37321 13.979 1.50021 14.0063 1.62756 13.9988C1.75491 13.9912 1.87779 13.9491 1.98297 13.8769L11.0832 7.57672C11.4605 7.31561 11.4605 6.68699 11.0832 6.42518L1.98297 0.125005C1.87801 0.0520805 1.75506 0.00931579 1.6275 0.00135729C1.49994 -0.00660121 1.37264 0.0205509 1.25942 0.0798637C1.14621 0.139176 1.05142 0.228381 0.98534 0.337786C0.919266 0.447191 0.884438 0.572612 0.884639 0.700422Z"
|
|
34
|
+
fill="#003C71"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
};
|