react-core-ts 1.0.42 → 1.0.44
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/README.md +50 -0
- package/dist/AutoComplete.d.ts +2 -2
- package/dist/AutoComplete.js +44 -37
- package/dist/AutoComplete.js.map +1 -1
- package/dist/AutoCompleteWithSelectedList.d.ts +4 -0
- package/dist/AutoCompleteWithSelectedList.js +446 -0
- package/dist/AutoCompleteWithSelectedList.js.map +1 -0
- package/dist/ExpandableAutoComplete.js +12 -5
- package/dist/ExpandableAutoComplete.js.map +1 -1
- package/dist/commontypes.d.ts +11 -4
- package/dist/components/DropdownList.js +2 -2
- package/dist/components/DropdownList.js.map +1 -1
- package/dist/components/InputActions.d.ts +1 -0
- package/dist/components/InputActions.js +2 -2
- package/dist/components/InputActions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/styles/components/autocomplete.css +18 -1
- package/dist/styles/components/selectedautocomplete.css +121 -0
- package/dist/styles/global.css +2 -1
- package/dist/utilities/autosuggestions.d.ts +2 -2
- package/dist/utilities/autosuggestions.js +10 -7
- package/dist/utilities/autosuggestions.js.map +1 -1
- package/dist/utilities/filterSuggestions.d.ts +1 -1
- package/dist/utilities/filterSuggestions.js +10 -5
- package/dist/utilities/filterSuggestions.js.map +1 -1
- package/docs/App.tsx +96 -4
- package/docs/index.html +1 -1
- package/package.json +1 -1
- package/src/AutoComplete.tsx +66 -51
- package/src/AutoCompleteWithSelectedList.tsx +677 -0
- package/src/ExpandableAutoComplete.tsx +13 -5
- package/src/commontypes.ts +14 -6
- package/src/components/DropdownList.tsx +3 -3
- package/src/components/InputActions.tsx +3 -1
- package/src/index.html +1 -1
- package/src/index.tsx +1 -0
- package/src/styles/components/autocomplete.css +18 -1
- package/src/styles/components/selectedautocomplete.css +121 -0
- package/src/styles/global.css +2 -1
- package/src/utilities/autosuggestions.ts +18 -12
- package/src/utilities/filterSuggestions.ts +10 -7
|
@@ -0,0 +1,677 @@
|
|
|
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, TabPops, ValueProps } 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 { DropArrow, Search, Spinner } from "./utilities/icons";
|
|
20
|
+
|
|
21
|
+
const AutoCompleteWithSelectedList = forwardRef<
|
|
22
|
+
HTMLInputElement,
|
|
23
|
+
AutoSuggestionInputProps
|
|
24
|
+
>(
|
|
25
|
+
(
|
|
26
|
+
{
|
|
27
|
+
label,
|
|
28
|
+
onChange,
|
|
29
|
+
getData = async () => [],
|
|
30
|
+
data = [],
|
|
31
|
+
errors,
|
|
32
|
+
required = false,
|
|
33
|
+
name,
|
|
34
|
+
fullWidth = false,
|
|
35
|
+
placeholder,
|
|
36
|
+
id,
|
|
37
|
+
type = "custom_select",
|
|
38
|
+
selectedItems: propsSeelctedItems = [],
|
|
39
|
+
readOnly = false,
|
|
40
|
+
disabled = false,
|
|
41
|
+
value,
|
|
42
|
+
isMultiple = false,
|
|
43
|
+
desc = "name",
|
|
44
|
+
descId = "id",
|
|
45
|
+
singleSelect,
|
|
46
|
+
className,
|
|
47
|
+
async = false,
|
|
48
|
+
paginationEnabled,
|
|
49
|
+
initialLoad = false,
|
|
50
|
+
actionLabel,
|
|
51
|
+
handleAction,
|
|
52
|
+
nextBlock,
|
|
53
|
+
notDataMessage,
|
|
54
|
+
initialDataMessage,
|
|
55
|
+
onFocus,
|
|
56
|
+
expandable = false,
|
|
57
|
+
textCount = 10,
|
|
58
|
+
itemCount = 1,
|
|
59
|
+
scrollRef,
|
|
60
|
+
countOnly = true,
|
|
61
|
+
typeOnlyFetch = false,
|
|
62
|
+
tab = [],
|
|
63
|
+
},
|
|
64
|
+
ref
|
|
65
|
+
) => {
|
|
66
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
67
|
+
const dropdownSelectedRef = useRef<HTMLDivElement>(null);
|
|
68
|
+
const tabRef = useRef<HTMLUListElement>(null);
|
|
69
|
+
// State Hooks Section
|
|
70
|
+
const [isInitialRender, setIsInitialRender] = useState(true);
|
|
71
|
+
|
|
72
|
+
const [inputValue, setInputValue] = useState<string>(value);
|
|
73
|
+
const [searchValue, setSearchValue] = useState<string>("");
|
|
74
|
+
const [searchOldValue, setSearchOldValue] = useState<string>("");
|
|
75
|
+
const [nextPage, setNextPage] = useState<number | undefined>(1);
|
|
76
|
+
const [dropOpen, setDropOpen] = useState<boolean>(false);
|
|
77
|
+
const [selectedItems, setSelectedItems] = useState<ValueProps[]>([]);
|
|
78
|
+
const [showAllSelected, setShowAllSelected] = useState<boolean>(false);
|
|
79
|
+
const [activeTab, setActiveTab] = useState<number>(0);
|
|
80
|
+
// API call for suggestions through a custom hook
|
|
81
|
+
const inputRef = useRef(null);
|
|
82
|
+
const dropRef = useRef(null);
|
|
83
|
+
useImperativeHandle(ref, () => inputRef.current);
|
|
84
|
+
const [dropdownStyle, setDropdownStyle] = useState({
|
|
85
|
+
top: 0,
|
|
86
|
+
left: 0,
|
|
87
|
+
width: 200,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const adjustDropdownPosition = () => {
|
|
91
|
+
if (dropdownRef.current) {
|
|
92
|
+
const inputRect = dropdownRef.current.getBoundingClientRect();
|
|
93
|
+
const dropdownPosition = {
|
|
94
|
+
left: inputRect.left + window.scrollX,
|
|
95
|
+
width: inputRect.width,
|
|
96
|
+
top: 0,
|
|
97
|
+
};
|
|
98
|
+
// Check if there's enough space below the input for the dropdown
|
|
99
|
+
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
100
|
+
const spaceAbove = inputRect.top + window.scrollY;
|
|
101
|
+
|
|
102
|
+
let dropdownHeight = 275; // Assume a fixed height or calculate based on content
|
|
103
|
+
if (countOnly) {
|
|
104
|
+
if (dropdownSelectedRef?.current)
|
|
105
|
+
dropdownHeight += dropdownSelectedRef?.current?.clientHeight + 3;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if(tab.length > 0) {
|
|
109
|
+
if (tabRef?.current)
|
|
110
|
+
dropdownHeight += tabRef?.current?.clientHeight + 20;
|
|
111
|
+
}
|
|
112
|
+
if (spaceBelow >= dropdownHeight) {
|
|
113
|
+
dropdownPosition.top =
|
|
114
|
+
inputRect.top + window.scrollY + inputRect.height;
|
|
115
|
+
} else {
|
|
116
|
+
dropdownPosition.top =
|
|
117
|
+
inputRect.top + window.scrollY - dropdownHeight + 73;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setDropdownStyle({
|
|
121
|
+
...dropdownPosition,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
window.addEventListener("resize", adjustDropdownPosition);
|
|
128
|
+
adjustDropdownPosition();
|
|
129
|
+
|
|
130
|
+
return () => {
|
|
131
|
+
window.removeEventListener("resize", adjustDropdownPosition);
|
|
132
|
+
};
|
|
133
|
+
}, [dropOpen, selectedItems]);
|
|
134
|
+
|
|
135
|
+
const { suggestions, isLoading, handlePickSuggestions } = useSuggestions(
|
|
136
|
+
getData,
|
|
137
|
+
data,
|
|
138
|
+
dropOpen,
|
|
139
|
+
async,
|
|
140
|
+
paginationEnabled,
|
|
141
|
+
initialLoad,
|
|
142
|
+
inputValue,
|
|
143
|
+
isMultiple,
|
|
144
|
+
setNextPage,
|
|
145
|
+
selectedItems,
|
|
146
|
+
typeOnlyFetch
|
|
147
|
+
// nextBlock
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Handling the selection of a suggestion
|
|
151
|
+
const handleSuggestionClick = useCallback((suggestion: ValueProps) => {
|
|
152
|
+
if (isMultiple) {
|
|
153
|
+
setSelectedItems((prev) => {
|
|
154
|
+
const isAdded = prev.some(
|
|
155
|
+
(item) => item[descId] === suggestion[descId]
|
|
156
|
+
);
|
|
157
|
+
if (isAdded) {
|
|
158
|
+
return prev.filter((item) => item[descId] !== suggestion[descId]);
|
|
159
|
+
} else {
|
|
160
|
+
return [...prev, suggestion];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
setInputValue(suggestion[desc]);
|
|
165
|
+
setSearchValue("");
|
|
166
|
+
onChange(suggestion);
|
|
167
|
+
setDropOpen(false);
|
|
168
|
+
}
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
// Adding debounce to avoid making API calls on every keystroke
|
|
172
|
+
const debouncedUpdate = useCallback(
|
|
173
|
+
debounce((value: string, tabVal?: number | string) => {
|
|
174
|
+
setSearchOldValue(value);
|
|
175
|
+
handlePickSuggestions(value, 1, false, tabVal);
|
|
176
|
+
}, 500),
|
|
177
|
+
[]
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const handleChangeWithDebounce = (value: string) => {
|
|
181
|
+
if ((type === "auto_complete" || type === "auto_suggestion") && async) {
|
|
182
|
+
const activeTabVal = tab.length > 0 ? tab?.[activeTab].id : undefined
|
|
183
|
+
debouncedUpdate(value, activeTabVal);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleMultiSelect = (e: any, suggestion: ValueProps) => {
|
|
188
|
+
const { checked } = e.target;
|
|
189
|
+
if (isMultiple) {
|
|
190
|
+
if (checked) {
|
|
191
|
+
setSelectedItems((prev) => [...prev, suggestion]);
|
|
192
|
+
} else {
|
|
193
|
+
setSelectedItems((prev) => {
|
|
194
|
+
return prev.filter(
|
|
195
|
+
(item, i) => item[descId] !== suggestion[descId]
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (checked) {
|
|
201
|
+
setInputValue(suggestion[desc]);
|
|
202
|
+
onChange(suggestion);
|
|
203
|
+
} else {
|
|
204
|
+
setInputValue("");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (!deepEqual(selectedItems, propsSeelctedItems))
|
|
210
|
+
setSelectedItems(propsSeelctedItems);
|
|
211
|
+
}, [propsSeelctedItems]);
|
|
212
|
+
// Effect to set the input value whenever `value` prop changes
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
setInputValue(value ?? "");
|
|
215
|
+
}, [value]);
|
|
216
|
+
|
|
217
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
218
|
+
const { value } = e.target;
|
|
219
|
+
setDropOpen(true);
|
|
220
|
+
setSearchValue(value);
|
|
221
|
+
handleChangeWithDebounce(value);
|
|
222
|
+
if (!value) {
|
|
223
|
+
setInputValue("");
|
|
224
|
+
// onChange({ [descId]: '', [desc]: '' });
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const handleSuggestionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
228
|
+
const { value } = e.target;
|
|
229
|
+
setDropOpen(true);
|
|
230
|
+
setSearchValue(value);
|
|
231
|
+
handleChangeWithDebounce(value);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const handleClear = () => {
|
|
235
|
+
if (searchValue) {
|
|
236
|
+
setSearchValue("");
|
|
237
|
+
setDropOpen(false);
|
|
238
|
+
} else {
|
|
239
|
+
setInputValue("");
|
|
240
|
+
onChange({ [descId]: "", [desc]: "" });
|
|
241
|
+
setDropOpen(false);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const handleClearSelected = () => {
|
|
246
|
+
setSelectedItems([]);
|
|
247
|
+
onChange({ [descId]: "", [desc]: "" });
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const generateClassName = useCallback(() => {
|
|
251
|
+
return `qbs-textfield-default ${className} ${
|
|
252
|
+
errors && errors?.message ? "textfield-error" : "textfield"
|
|
253
|
+
} ${expandable ? "expandable" : ""}`;
|
|
254
|
+
}, [errors, name]);
|
|
255
|
+
const handleRemoveSelectedItem = (index: number) => {
|
|
256
|
+
setSelectedItems((prev) => {
|
|
257
|
+
return prev.filter((_, i) => i !== index);
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
if (isInitialRender) {
|
|
263
|
+
setIsInitialRender(false);
|
|
264
|
+
} else {
|
|
265
|
+
onChange(selectedItems);
|
|
266
|
+
}
|
|
267
|
+
}, [selectedItems]);
|
|
268
|
+
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
const handleClickOutside = (event: any) => {
|
|
271
|
+
if (
|
|
272
|
+
dropRef.current &&
|
|
273
|
+
event.target instanceof Node &&
|
|
274
|
+
!dropRef.current.contains(event.target) &&
|
|
275
|
+
event.target.nodeName !== "svg"
|
|
276
|
+
) {
|
|
277
|
+
setDropOpen(false);
|
|
278
|
+
//setSearchValue("");
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
document.addEventListener("mousedown", handleClickOutside as any);
|
|
282
|
+
window.addEventListener("scroll", handleClickOutside as any);
|
|
283
|
+
|
|
284
|
+
const scrollableDivs = document.querySelectorAll(
|
|
285
|
+
'div[style*="overflow"]'
|
|
286
|
+
);
|
|
287
|
+
scrollableDivs.forEach((div) =>
|
|
288
|
+
div.addEventListener("scroll", handleClickOutside as any)
|
|
289
|
+
);
|
|
290
|
+
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
291
|
+
scrollRef.current.addEventListener("scroll", handleClickOutside as any);
|
|
292
|
+
}
|
|
293
|
+
return () => {
|
|
294
|
+
document.removeEventListener("mousedown", handleClickOutside as any);
|
|
295
|
+
window.removeEventListener("scroll", handleClickOutside as any);
|
|
296
|
+
scrollableDivs.forEach((div) =>
|
|
297
|
+
div.removeEventListener("scroll", handleClickOutside as any)
|
|
298
|
+
);
|
|
299
|
+
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
300
|
+
scrollRef.current.removeEventListener(
|
|
301
|
+
"scroll",
|
|
302
|
+
handleClickOutside as any
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}, []);
|
|
307
|
+
|
|
308
|
+
// Filtering suggestions based on type and search value
|
|
309
|
+
const selected: any = isMultiple ? selectedItems : inputValue;
|
|
310
|
+
const filteredData: ValueProps[] = filterSuggestions(
|
|
311
|
+
suggestions,
|
|
312
|
+
searchValue,
|
|
313
|
+
type,
|
|
314
|
+
desc,
|
|
315
|
+
selected,
|
|
316
|
+
async,
|
|
317
|
+
false,
|
|
318
|
+
true
|
|
319
|
+
);
|
|
320
|
+
const isSelected = (
|
|
321
|
+
item: ValueProps,
|
|
322
|
+
selectedItems: ValueProps[] | string
|
|
323
|
+
): boolean => {
|
|
324
|
+
if (Array.isArray(selectedItems)) {
|
|
325
|
+
return selectedItems.some(
|
|
326
|
+
(selectedItem) => selectedItem[desc] === item[desc]
|
|
327
|
+
);
|
|
328
|
+
} else {
|
|
329
|
+
return item[desc] === selectedItems;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const handleLoadMore = () => {
|
|
334
|
+
if (paginationEnabled) {
|
|
335
|
+
handlePickSuggestions(searchValue, nextPage + 1, true);
|
|
336
|
+
setNextPage(nextPage + 1);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const handleOnClick = () => {
|
|
341
|
+
!disabled && !readOnly ? setDropOpen(true) : "";
|
|
342
|
+
};
|
|
343
|
+
const onInputFocus = () => {
|
|
344
|
+
if (inputRef.current) inputRef.current.focus();
|
|
345
|
+
handleOnClick();
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const handleShowAllSelected = (enabled: boolean) => {
|
|
349
|
+
if (enabled) {
|
|
350
|
+
setShowAllSelected(true);
|
|
351
|
+
} else {
|
|
352
|
+
setShowAllSelected(false);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const tooltipContent =
|
|
357
|
+
selectedItems?.length > itemCount
|
|
358
|
+
? selectedItems
|
|
359
|
+
?.slice(itemCount)
|
|
360
|
+
.map((item) => item[desc])
|
|
361
|
+
.join(", ")
|
|
362
|
+
: "";
|
|
363
|
+
|
|
364
|
+
const handleDropOpen = (e: any) => {
|
|
365
|
+
if (!dropOpen) setDropOpen(true);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const handleDropClose = (e: any) => {
|
|
369
|
+
if (dropOpen) setDropOpen(false);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const getSelectedItems = (dropdown: boolean) => {
|
|
373
|
+
return (
|
|
374
|
+
<>
|
|
375
|
+
{selectedItems
|
|
376
|
+
.slice(
|
|
377
|
+
0,
|
|
378
|
+
selectedItems?.length > itemCount
|
|
379
|
+
? showAllSelected
|
|
380
|
+
? selectedItems?.length
|
|
381
|
+
: itemCount
|
|
382
|
+
: selectedItems?.length
|
|
383
|
+
)
|
|
384
|
+
.map((item, index) => (
|
|
385
|
+
<div key={index} className="selected-items-container">
|
|
386
|
+
<Tooltip
|
|
387
|
+
title={item?.[desc]}
|
|
388
|
+
enabled={item?.[desc]?.length > textCount}
|
|
389
|
+
>
|
|
390
|
+
<div key={item.id} className="selected-item">
|
|
391
|
+
{item?.[desc]?.length > textCount
|
|
392
|
+
? `${item?.[desc].substring(0, textCount)}...`
|
|
393
|
+
: item?.[desc]}
|
|
394
|
+
|
|
395
|
+
<button
|
|
396
|
+
onClick={() => handleRemoveSelectedItem(index)}
|
|
397
|
+
className="remove-item-btn"
|
|
398
|
+
aria-label={`Remove ${item?.[desc]}`}
|
|
399
|
+
>
|
|
400
|
+
X
|
|
401
|
+
</button>
|
|
402
|
+
</div>
|
|
403
|
+
</Tooltip>
|
|
404
|
+
</div>
|
|
405
|
+
))}
|
|
406
|
+
{selectedItems?.length > itemCount && !showAllSelected && (
|
|
407
|
+
<div className="selected-items-container">
|
|
408
|
+
<Tooltip title={tooltipContent} enabled={true}>
|
|
409
|
+
<div
|
|
410
|
+
className={`selected-item-more qbs-rounded-full qbs-min-h-6 qbs-min-w-6 qbs-p-1 ${
|
|
411
|
+
dropdown ? "qbs-cursor-pointer" : ""
|
|
412
|
+
}`}
|
|
413
|
+
onClick={() => {
|
|
414
|
+
handleShowAllSelected(dropdown);
|
|
415
|
+
}}
|
|
416
|
+
>
|
|
417
|
+
+{selectedItems?.length - itemCount}{" "}
|
|
418
|
+
<span className="qbs-hidden">more</span>
|
|
419
|
+
</div>
|
|
420
|
+
</Tooltip>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
</>
|
|
424
|
+
);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const getTabItems = () => {
|
|
428
|
+
return (
|
|
429
|
+
<ul className="qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2" ref={tabRef}>
|
|
430
|
+
{tab.map((item: TabPops, idx: number) => (
|
|
431
|
+
<li className="qbs-flex-1 qbs-tab-items" key={`tab-${idx}`}>
|
|
432
|
+
<span
|
|
433
|
+
className={`qbs-inline-block qbs-tab-item qbs-text-sm qbs-cursor-pointer qbs-w-full qbs-text-center qbs-p-1 qbs-border-b-2 ${
|
|
434
|
+
activeTab === idx
|
|
435
|
+
? "qbs-tab-active-item"
|
|
436
|
+
: ""
|
|
437
|
+
}`}
|
|
438
|
+
onClick={() => {
|
|
439
|
+
setActiveTab(idx);
|
|
440
|
+
}}
|
|
441
|
+
>
|
|
442
|
+
{item.label}
|
|
443
|
+
</span>
|
|
444
|
+
</li>
|
|
445
|
+
))}
|
|
446
|
+
</ul>
|
|
447
|
+
);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<div className={fullWidth ? "fullWidth" : "autoWidth"} ref={dropdownRef}>
|
|
452
|
+
{label && (
|
|
453
|
+
<div
|
|
454
|
+
style={{
|
|
455
|
+
marginBottom: 5,
|
|
456
|
+
display: "flex",
|
|
457
|
+
justifyContent: "space-between",
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<label className={`labels label-text`}>
|
|
461
|
+
{label}
|
|
462
|
+
{required && <span className="text-error"> *</span>}
|
|
463
|
+
</label>
|
|
464
|
+
<span onClick={() => handleAction?.()} className={`action_label`}>
|
|
465
|
+
{actionLabel}
|
|
466
|
+
</span>
|
|
467
|
+
</div>
|
|
468
|
+
)}
|
|
469
|
+
{/* Displaying selected items for multi-select */}
|
|
470
|
+
|
|
471
|
+
<div
|
|
472
|
+
className={`qbs-relative qbs-autocomplete-selected-comp ${
|
|
473
|
+
expandable ? "qbs-expandable-container" : "qbs-container"
|
|
474
|
+
}`}
|
|
475
|
+
>
|
|
476
|
+
{selectedItems?.length > 0 && (
|
|
477
|
+
<>
|
|
478
|
+
{!countOnly ? (
|
|
479
|
+
getSelectedItems(false)
|
|
480
|
+
) : (
|
|
481
|
+
<div className="selected-items-container qbs-text-sm qbs-gap-1">
|
|
482
|
+
<span className="badge qbs-rounded-full qbs-text-xs qbs-inline-flex qbs-items-center qbs-justify-center qbs-px-2 qbs-py-1 qbs-leading-none qbs-min-w-6 qbs-min-h-6">
|
|
483
|
+
{selectedItems?.length}
|
|
484
|
+
</span>
|
|
485
|
+
Item{selectedItems?.length > 1 && "s"} selected
|
|
486
|
+
</div>
|
|
487
|
+
)}
|
|
488
|
+
</>
|
|
489
|
+
)}
|
|
490
|
+
|
|
491
|
+
<div
|
|
492
|
+
className={`qbs-textfield-expandable ${
|
|
493
|
+
!expandable ? "qbs-normal" : ""
|
|
494
|
+
}`}
|
|
495
|
+
data-value={
|
|
496
|
+
selectedItems?.length > 0 || searchValue
|
|
497
|
+
? searchValue
|
|
498
|
+
: placeholder ?? ""
|
|
499
|
+
}
|
|
500
|
+
onClick={() => onInputFocus()}
|
|
501
|
+
>
|
|
502
|
+
<input
|
|
503
|
+
id={id}
|
|
504
|
+
ref={inputRef}
|
|
505
|
+
type="text"
|
|
506
|
+
value={
|
|
507
|
+
type === "auto_suggestion" && !expandable
|
|
508
|
+
? inputValue
|
|
509
|
+
: searchValue || inputValue
|
|
510
|
+
}
|
|
511
|
+
onChange={handleChange}
|
|
512
|
+
// onBlur={handleBlur}
|
|
513
|
+
onFocus={onFocus}
|
|
514
|
+
onClick={() => handleOnClick()}
|
|
515
|
+
className={generateClassName()}
|
|
516
|
+
placeholder={selectedItems?.length > 0 ? "" : placeholder ?? ""}
|
|
517
|
+
readOnly={
|
|
518
|
+
readOnly ||
|
|
519
|
+
type === "custom_select" ||
|
|
520
|
+
(type == "auto_suggestion" && !expandable)
|
|
521
|
+
}
|
|
522
|
+
disabled={disabled}
|
|
523
|
+
data-testid="custom-autocomplete"
|
|
524
|
+
/>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
{/* Icons for Clearing Input or Toggling Dropdown */}
|
|
528
|
+
|
|
529
|
+
<InputActions
|
|
530
|
+
inputValue={inputValue}
|
|
531
|
+
searchValue={searchValue}
|
|
532
|
+
dropOpen={dropOpen}
|
|
533
|
+
handleDropOpen={handleDropOpen}
|
|
534
|
+
handleDropClose={handleDropClose}
|
|
535
|
+
disabled={disabled}
|
|
536
|
+
readOnly={readOnly}
|
|
537
|
+
expandable={expandable}
|
|
538
|
+
handleClear={handleClear}
|
|
539
|
+
countOnly={countOnly}
|
|
540
|
+
/>
|
|
541
|
+
{/* Displaying Loading Spinner */}
|
|
542
|
+
|
|
543
|
+
{/* Suggestions Dropdown */}
|
|
544
|
+
|
|
545
|
+
{dropOpen &&
|
|
546
|
+
ReactDOM.createPortal(
|
|
547
|
+
<div
|
|
548
|
+
ref={dropRef}
|
|
549
|
+
style={{ ...dropdownStyle, minHeight: 192 }}
|
|
550
|
+
className={`qbs-autocomplete-suggestions qbs-autocomplete-selected-suggestions`}
|
|
551
|
+
>
|
|
552
|
+
<>{tab.length > 0 && getTabItems()}</>
|
|
553
|
+
{type == "auto_suggestion" && !expandable && (
|
|
554
|
+
<div
|
|
555
|
+
style={{ position: "relative" }}
|
|
556
|
+
className="react-core-ts-search-container"
|
|
557
|
+
>
|
|
558
|
+
<span className="dropdown-search-icon">
|
|
559
|
+
<Search />
|
|
560
|
+
</span>
|
|
561
|
+
<input
|
|
562
|
+
className="dropdown-search-input"
|
|
563
|
+
onChange={handleSuggestionChange}
|
|
564
|
+
value={searchValue}
|
|
565
|
+
placeholder="Search"
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
)}
|
|
569
|
+
|
|
570
|
+
<div className={`qbs-autocomplete-suggestions-sub `}>
|
|
571
|
+
{filteredData?.length > 0 ? (
|
|
572
|
+
filteredData.map((suggestion: ValueProps, idx: number) => (
|
|
573
|
+
<DropdownList
|
|
574
|
+
idx={idx}
|
|
575
|
+
suggestion={suggestion}
|
|
576
|
+
isSelected={isSelected}
|
|
577
|
+
handleSuggestionClick={handleSuggestionClick}
|
|
578
|
+
handleMultiSelect={handleMultiSelect}
|
|
579
|
+
selected={selected}
|
|
580
|
+
isMultiple={isMultiple}
|
|
581
|
+
singleSelect={singleSelect}
|
|
582
|
+
desc={desc}
|
|
583
|
+
key={suggestion[descId]}
|
|
584
|
+
/>
|
|
585
|
+
))
|
|
586
|
+
) : (
|
|
587
|
+
<>
|
|
588
|
+
{isLoading ||
|
|
589
|
+
(searchValue !== searchOldValue &&
|
|
590
|
+
searchValue !== "" &&
|
|
591
|
+
async) ? (
|
|
592
|
+
<div className="qbs-flex qbs-align-middle qbs-justify-center qbs-min-emp-h qbs-py-80">
|
|
593
|
+
<div className="qbs-pt-16">
|
|
594
|
+
<Spinner />
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
) : (
|
|
598
|
+
<div className="qbs-autocomplete-notfound qbs-text-center qbs-justify-center qbs-align-middle qbs-min-emp-h qbs-py-80">
|
|
599
|
+
{searchValue !== ""
|
|
600
|
+
? notDataMessage ?? "No Results Found"
|
|
601
|
+
: initialDataMessage ?? "Type to search"}
|
|
602
|
+
</div>
|
|
603
|
+
)}
|
|
604
|
+
</>
|
|
605
|
+
)}
|
|
606
|
+
{paginationEnabled &&
|
|
607
|
+
nextBlock !== 0 &&
|
|
608
|
+
nextBlock !== undefined &&
|
|
609
|
+
filteredData?.length > 0 && (
|
|
610
|
+
<div
|
|
611
|
+
className="loadMoreSection"
|
|
612
|
+
onClick={() => handleLoadMore()}
|
|
613
|
+
>
|
|
614
|
+
<p style={{ margin: 2 }}>Load More</p>
|
|
615
|
+
</div>
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
618
|
+
<>
|
|
619
|
+
{countOnly && selectedItems?.length > 0 && (
|
|
620
|
+
<>
|
|
621
|
+
<div
|
|
622
|
+
className={`qbs-expandable-container qbs-dropdown-selected-container`}
|
|
623
|
+
ref={dropdownSelectedRef}
|
|
624
|
+
>
|
|
625
|
+
<div className="qbs-flex qbs-w-full qbs-text-xs ">
|
|
626
|
+
<div className="qbs-selected-lbs qbs-flex-grow">
|
|
627
|
+
Selected Items
|
|
628
|
+
</div>
|
|
629
|
+
<div
|
|
630
|
+
className="qbs-clear-link qbs-text-right qbs-cursor-pointer"
|
|
631
|
+
onClick={handleClearSelected}
|
|
632
|
+
>
|
|
633
|
+
Clear
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
{getSelectedItems(true)}
|
|
638
|
+
|
|
639
|
+
<>
|
|
640
|
+
{selectedItems?.length > itemCount &&
|
|
641
|
+
showAllSelected && (
|
|
642
|
+
<div className="qbs-readmore-collapse">
|
|
643
|
+
<div
|
|
644
|
+
className="qbs-more-collapse"
|
|
645
|
+
onClick={() => {
|
|
646
|
+
setShowAllSelected(false);
|
|
647
|
+
}}
|
|
648
|
+
>
|
|
649
|
+
<DropArrow className={`icon-button-rotate`} />
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
)}
|
|
653
|
+
</>
|
|
654
|
+
</div>
|
|
655
|
+
</>
|
|
656
|
+
)}
|
|
657
|
+
</>
|
|
658
|
+
</div>,
|
|
659
|
+
document.body
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
|
|
663
|
+
{/* Displaying Validation Error */}
|
|
664
|
+
{errors && (
|
|
665
|
+
<div
|
|
666
|
+
className="text-error text-error-label mt-[1px]"
|
|
667
|
+
data-testid="autocomplete-error"
|
|
668
|
+
>
|
|
669
|
+
{errors.message}
|
|
670
|
+
</div>
|
|
671
|
+
)}
|
|
672
|
+
</div>
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
export default React.memo(AutoCompleteWithSelectedList);
|
|
@@ -135,14 +135,22 @@ const ExpandableAutoComplete = forwardRef<
|
|
|
135
135
|
// Handling the selection of a suggestion
|
|
136
136
|
const handleSuggestionClick = useCallback((suggestion: ValueProps) => {
|
|
137
137
|
if (isMultiple) {
|
|
138
|
-
setSelectedItems((prev) =>
|
|
138
|
+
setSelectedItems((prev) => {
|
|
139
|
+
const isAdded = prev.some(
|
|
140
|
+
(item) => item[descId] === suggestion[descId]
|
|
141
|
+
);
|
|
142
|
+
if (isAdded) {
|
|
143
|
+
return prev.filter((item) => item[descId] !== suggestion[descId]);
|
|
144
|
+
} else {
|
|
145
|
+
return [...prev, suggestion];
|
|
146
|
+
}
|
|
147
|
+
});
|
|
139
148
|
} else {
|
|
140
149
|
setInputValue(suggestion[desc]);
|
|
150
|
+
setSearchValue("");
|
|
151
|
+
onChange(suggestion);
|
|
152
|
+
setDropOpen(false);
|
|
141
153
|
}
|
|
142
|
-
setSearchValue("");
|
|
143
|
-
setInputValue(suggestion[desc]);
|
|
144
|
-
onChange(suggestion);
|
|
145
|
-
setDropOpen(false);
|
|
146
154
|
}, []);
|
|
147
155
|
|
|
148
156
|
// Adding debounce to avoid making API calls on every keystroke
|