react-core-ts 1.0.24 → 1.0.26

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.
@@ -1,545 +0,0 @@
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);
@@ -1,43 +0,0 @@
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
- }
@@ -1,70 +0,0 @@
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;
@@ -1,38 +0,0 @@
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
- };