react-core-ts 2.0.11 → 2.0.13

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.
@@ -0,0 +1,969 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { FieldErrors } from "react-hook-form";
3
+
4
+ import CustomIcons from "./components/customIcons";
5
+ import Spinner from "./components/loader/Spinner";
6
+ import Portal from "./components/portal";
7
+ import { debounce } from "./utilities/debounce";
8
+ import { applyPositionClass } from "./utilities/getPosition";
9
+ import { DropArrow } from "./utilities/icons";
10
+
11
+ type valueProps = {
12
+ id?: string | number;
13
+ name: string;
14
+ label?: string;
15
+ param1?: string | number | null;
16
+ param2?: string | number | null;
17
+ param3?: string | number | null;
18
+ param4?: string | number | null;
19
+ from?: number;
20
+ };
21
+ interface AutoSuggestionInputProps {
22
+ id?: string;
23
+ label?: string;
24
+ fullWidth?: boolean;
25
+ className?: string;
26
+ autoFocus?: boolean;
27
+ required?: boolean;
28
+ hideClear?: boolean;
29
+ value?: valueProps;
30
+ onChange: (value?: valueProps) => void;
31
+ data?: any[];
32
+ type?: "custom_select" | "auto_complete" | "custom_search_select";
33
+ placeholder?: string;
34
+ getData?: ((key?: string) => Promise<any>) | ((key?: string) => any);
35
+ errors?: FieldErrors;
36
+ name: string;
37
+ readOnly?: boolean;
38
+ disabled?: boolean;
39
+ autoFilter?: boolean;
40
+
41
+ insideOpen?: boolean;
42
+ isClose?: boolean;
43
+ noLocalFilter?: boolean;
44
+ isStaticList?: boolean;
45
+ isCustomPlaceholder?: boolean;
46
+ checkParams?: string[];
47
+ fromPrefix?: boolean;
48
+ hasParentError?: boolean;
49
+ width?: number;
50
+ labelTitle?: string;
51
+ isModern?: boolean;
52
+ size?: "xs" | "sm" | "md" | "lg" | "xxs";
53
+ tableView?: boolean;
54
+ dropdownMinWidth?: number; // Minimum additional width in pixels to add to dropdown
55
+ additionalColumns?: Array<{
56
+ key: string; // Field name in the suggestion object
57
+ width?: number; // Optional width in pixels (if not set, uses flex-1 for equal width)
58
+ order?: number; // Optional order/position (lower numbers appear first, default maintains array order)
59
+ }>; // Additional columns to display
60
+ }
61
+
62
+ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
63
+ label,
64
+ onChange,
65
+
66
+ getData,
67
+ data,
68
+ errors,
69
+ required = false,
70
+ autoFocus,
71
+ name,
72
+ fullWidth,
73
+ placeholder,
74
+ id,
75
+ className: propsClassName,
76
+ type = "custom_select",
77
+ readOnly,
78
+ disabled = false,
79
+ value,
80
+ autoFilter = false,
81
+ insideOpen = false,
82
+ isClose = true,
83
+ noLocalFilter = false,
84
+ isStaticList = false,
85
+ isCustomPlaceholder = false,
86
+ checkParams,
87
+ width,
88
+ hasParentError,
89
+ fromPrefix,
90
+ labelTitle,
91
+ isModern = true,
92
+ size = "md",
93
+ dropdownMinWidth = 200,
94
+ tableView = false,
95
+ additionalColumns = [],
96
+ }) => {
97
+ const [inputValue, setInputValue] = useState<any>(value?.name ?? "");
98
+ const [isHovered, setIsHovered] = useState<boolean>(false);
99
+ const [isLoading, setIsLoading] = useState<boolean>(false);
100
+ const [dropOpen, setDropOpen] = useState<boolean>(false);
101
+
102
+ const [isDisabled, setIsDisabled] = useState<boolean>(disabled);
103
+ const [showClose, setShowClose] = useState(false);
104
+ const [showToolTip, setShowTooltip] = useState(false);
105
+ const [tooltipIsHovered, setTooltipIsHovered] = useState(false);
106
+ const [suggestions, setSuggestions] = useState<any>([]);
107
+ const inputRef = useRef<HTMLInputElement>(null);
108
+ const adorementRef = useRef<HTMLDivElement>(null);
109
+ const dropdownref = useRef<HTMLDivElement>(null);
110
+ const dropBtnRef = useRef<HTMLButtonElement>(null);
111
+ const timerRef = useRef<number>(0); // To fix the no results found issue on second type
112
+ const [selectedIndex, setSelectedIndex] = useState(0);
113
+ const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
114
+ const scrollContainerRef = useRef<HTMLUListElement | null>(null);
115
+ const [dropPosition, setDropPosition] = useState<any>({
116
+ top: 0,
117
+ left: 0,
118
+ bottom: 0,
119
+ right: 0,
120
+ });
121
+ const checkIncludes = (
122
+ mainString: string,
123
+ subString: string,
124
+ param1?: string | number,
125
+ param2?: string | number,
126
+ param3?: string | number,
127
+ param4?: string | number
128
+ ): boolean => {
129
+ const checkIncludesIfExists = (
130
+ mainString?: string | number,
131
+ substring?: string
132
+ ) => {
133
+ return substring &&
134
+ mainString &&
135
+ typeof mainString === "string" &&
136
+ typeof subString === "string"
137
+ ? mainString
138
+ .toString()
139
+ .toLowerCase()
140
+ .startsWith(substring.toString().toLowerCase())
141
+ : false;
142
+ };
143
+ const checkArrayContains = (
144
+ strArray: string[] | undefined,
145
+ param: string
146
+ ): boolean => strArray?.includes(param) ?? false;
147
+
148
+ return (
149
+ checkIncludesIfExists(mainString, subString) ||
150
+ (!!checkParams &&
151
+ ((checkArrayContains(checkParams, "param1") &&
152
+ checkIncludesIfExists(param1, subString)) ||
153
+ (checkArrayContains(checkParams, "param2") &&
154
+ checkIncludesIfExists(param2, subString)) ||
155
+ (checkArrayContains(checkParams, "param3") &&
156
+ checkIncludesIfExists(param3, subString)) ||
157
+ (checkArrayContains(checkParams, "param4") &&
158
+ checkIncludesIfExists(param4, subString))))
159
+ );
160
+ };
161
+
162
+ const getHeight = () => {
163
+ switch (size) {
164
+ case "sm":
165
+ return " h-[34px] ";
166
+ default:
167
+ return " ";
168
+ }
169
+ };
170
+
171
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
172
+ const { value } = event.target;
173
+ timerRef.current = 0;
174
+
175
+ setInputValue(value);
176
+ handleValChange(value);
177
+ if (!value) {
178
+ setInputValue("");
179
+ onChange({ id: undefined, name: "", from: 2 });
180
+ }
181
+ };
182
+
183
+ const handleValChange = useCallback(
184
+ debounce(
185
+ (value: string) => {
186
+ setDropOpen(true);
187
+ onChange({ id: undefined, name: "", from: 1 });
188
+ if (value.trim() === "" && type === "auto_complete") {
189
+ setSuggestions([]);
190
+ if (autoFilter) {
191
+ handleDropData("*");
192
+ } else {
193
+ setDropOpen(false);
194
+ }
195
+ } else {
196
+ handleDropData(value);
197
+ }
198
+ setTimeout(() => {
199
+ timerRef.current = 1;
200
+ }, 200);
201
+ },
202
+ isStaticList ? 0 : 1000
203
+ ),
204
+ []
205
+ );
206
+ const handleDropData = (value?: string) => {
207
+ if (type === "auto_complete") {
208
+ if (isStaticList) {
209
+ loadStaticData();
210
+ const filteredData =
211
+ value && value !== "*"
212
+ ? data?.filter((item) =>
213
+ checkIncludes(
214
+ item.name,
215
+ value,
216
+ item.param1,
217
+ item.param2,
218
+ item.param3
219
+ )
220
+ )
221
+ : data;
222
+ setSuggestions(filteredData);
223
+ } else handlePickSuggestions(value);
224
+ } else if (
225
+ (type === "custom_select" || type === "custom_search_select") &&
226
+ (!data || data?.length === 0)
227
+ ) {
228
+ handlePickSuggestions();
229
+ } else if (type === "custom_search_select" && data && data?.length > 0) {
230
+ const filteredData =
231
+ value && value !== "*"
232
+ ? data.filter((item) =>
233
+ checkIncludes(
234
+ item.name,
235
+ value,
236
+ item.param1,
237
+ item.param2,
238
+ item.param3
239
+ )
240
+ )
241
+ : data;
242
+ setSuggestions(filteredData);
243
+ }
244
+ };
245
+ const handlePickSuggestions = async (value?: string) => {
246
+ if (getData) {
247
+ setIsLoading(true);
248
+ try {
249
+ const fetchedSuggestions = await getData?.(value);
250
+ setSuggestions(fetchedSuggestions);
251
+ setIsLoading(false);
252
+ } catch (error) {
253
+ setIsLoading(false);
254
+ }
255
+ }
256
+ };
257
+
258
+ const onLabelClick = () => {
259
+ if (!isDisabled) {
260
+ inputRef?.current?.focus();
261
+ if (autoFilter && inputValue === "") {
262
+ handleValChange("*");
263
+ } else if (
264
+ (type === "custom_select" || type === "custom_search_select") &&
265
+ data &&
266
+ data?.length > 0
267
+ ) {
268
+ handleDropData();
269
+ setDropOpen(true);
270
+ }
271
+ }
272
+ };
273
+ const handleClickOutside = (event: MouseEvent) => {
274
+ if (
275
+ dropdownref.current &&
276
+ !dropdownref.current.contains(event.target as Node)
277
+ ) {
278
+ setDropOpen(false);
279
+ }
280
+ };
281
+ const checkIsEmptyField = (): boolean => {
282
+ if (inputValue === undefined) return true;
283
+ else return (inputValue?.toString().length ?? 0) <= 0 ? true : false;
284
+ };
285
+ const loadStaticData = async () => {
286
+ if (!data) {
287
+ data = [];
288
+ if (getData) {
289
+ try {
290
+ setIsLoading(true);
291
+ handlePickSuggestions("*");
292
+ const fetchedSuggestions = await getData?.("*");
293
+ fetchedSuggestions?.forEach((element: any) => {
294
+ data?.push(element);
295
+ });
296
+ setIsLoading(false);
297
+ } catch (error) {
298
+ setIsLoading(false);
299
+ }
300
+ }
301
+ }
302
+ };
303
+ useEffect(() => {
304
+ document.addEventListener("click", handleClickOutside);
305
+ return () => {
306
+ document.removeEventListener("click", handleClickOutside);
307
+ };
308
+ }, []);
309
+
310
+ useEffect(() => {
311
+ if (autoFocus) {
312
+ onLabelClick();
313
+ }
314
+ }, [autoFocus]);
315
+ const onInputFocus = () => {
316
+ if (!isDisabled) {
317
+ if (autoFilter && inputValue === "") {
318
+ handleValChange("*");
319
+ } else if (
320
+ (type === "custom_select" || type === "custom_search_select") &&
321
+ data &&
322
+ data?.length > 0
323
+ ) {
324
+ handleDropData();
325
+ //if (insideOpen) setDropOpen(true) // commeted this code bcoz added onclick function inside
326
+ }
327
+ }
328
+ };
329
+ useEffect(() => {
330
+ setIsDisabled(disabled);
331
+ }, [disabled]);
332
+ const handleSuggestionClick = (suggestion: valueProps, index: number) => {
333
+ onChange({
334
+ ...suggestion,
335
+ id: suggestion?.id,
336
+ name: suggestion.name,
337
+ ...(suggestion?.label ? { label: suggestion?.label } : {}),
338
+ });
339
+ if (type !== "custom_select") setInputValue(suggestion.name);
340
+ setDropOpen(false);
341
+ setSelectedIndex(index);
342
+ };
343
+ const handleOpen = (e: any) => {
344
+ if (!suggestions || suggestions?.length === 0) {
345
+ handleDropData();
346
+ return;
347
+ }
348
+ if (!isLoading) {
349
+ setDropOpen(!dropOpen);
350
+ if (type !== "custom_select") setInputValue("");
351
+ }
352
+ };
353
+
354
+ const handleScroll = () => {
355
+ setDropOpen(false);
356
+ };
357
+
358
+ useEffect(() => {
359
+ if (!insideOpen) {
360
+ setSelectedIndex(0);
361
+ window.addEventListener("scroll", handleScroll);
362
+
363
+ const mainElement = document.querySelector("main");
364
+ mainElement?.addEventListener("scroll", handleScroll);
365
+
366
+ const gridElements = document.querySelectorAll(
367
+ ".k-grid-content, .overflow-auto, .overflow-y-auto, .overflow-x-auto"
368
+ );
369
+ gridElements.forEach((gridElement: any) => {
370
+ gridElement.addEventListener("scroll", handleScroll);
371
+ });
372
+
373
+ window.addEventListener("resize", getDropPosition);
374
+ getDropPosition();
375
+ return () => {
376
+ window.removeEventListener("scroll", handleScroll);
377
+ const mainElement = document.querySelector("main");
378
+ mainElement?.removeEventListener("scroll", handleScroll);
379
+ window.addEventListener("resize", getDropPosition);
380
+ const gridElements = document.querySelectorAll(".k-grid-content");
381
+ gridElements.forEach((gridElement: any) => {
382
+ gridElement.removeEventListener("scroll", handleScroll);
383
+ });
384
+ };
385
+ }
386
+ }, [dropOpen]);
387
+
388
+ const handleClear = () => {
389
+ setDropOpen(false);
390
+ setInputValue("");
391
+ onChange({ id: undefined, name: "", from: 3 });
392
+ onLabelClick();
393
+ };
394
+
395
+ useEffect(() => {
396
+ setInputValue(value?.name ?? "");
397
+ }, [value?.name]);
398
+
399
+ useEffect(() => {
400
+ setSuggestions(data);
401
+ }, [data]);
402
+
403
+ const getErrors = (err: any) => {
404
+ let errMsg = "";
405
+ if (err.message) {
406
+ errMsg = err?.message;
407
+ } else if (err?.id?.message) {
408
+ errMsg = err?.id?.message;
409
+ } else if (err?.name?.message) {
410
+ errMsg = err?.name?.message;
411
+ }
412
+ return errMsg;
413
+ };
414
+ const generateClassName = (
415
+ type: "input" | "label" | "message" | "adorement"
416
+ ): string => {
417
+ let className = propsClassName;
418
+ switch (type) {
419
+ case "input":
420
+ className += ` block text-common text-input-text font-normal px-3.5 w-full text-sm text-gray-900 bg-transparent border appearance-none peer h-10 rounded-[4px] disabled:text-input-disabled bg-white disabled:bg-disabled ${
421
+ label && isModern
422
+ ? "placeholder-transparent"
423
+ : "focus:placeholder-grey-secondary placeholder-input-label"
424
+ } focus:placeholder-grey-secondary`;
425
+
426
+ if (errors && errors[name]) {
427
+ className +=
428
+ " border-[#FDA29B] focus:border-error-[#FDA29B] focus:ring-[#FDA29B] focus:ring-3 focus:outline-[#FDA29B] input-outline";
429
+ } else {
430
+ className +=
431
+ " text-grey-dark border-input-light focus:border-blue-navy focus:outline-none focus:ring-0";
432
+ }
433
+
434
+ className += getHeight();
435
+
436
+ break;
437
+ case "label":
438
+ className += ` flex modern-input-label-truncate peer-focus:modern-input-peer-focus-label-size
439
+ ${
440
+ isDisabled
441
+ ? "cursor-pointer"
442
+ : "cursor-text peer-focus:cursor-pointer"
443
+ } ${
444
+ isDisabled && !checkIsEmptyField()
445
+ ? "disabled-input-label-bg"
446
+ : !isDisabled || !checkIsEmptyField()
447
+ ? "active-input-label-bg"
448
+ : ""
449
+ } absolute duration-300 transform -translate-y-4 top-2 z-1 origin-[0] px-0 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:-translate-y-4 start-[14px] rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto ${
450
+ isDisabled
451
+ ? "cursor-pointer"
452
+ : "cursor-text peer-focus:cursor-pointer"
453
+ }
454
+ ${
455
+ checkIsEmptyField()
456
+ ? "modern-input-label-size"
457
+ : "modern-input-peer-focus-label-size"
458
+ }`;
459
+ if (errors && errors[name]) {
460
+ className += " text-error-light ";
461
+ } else {
462
+ className += " text-grey-dark peer-focus:text-blue-navy";
463
+ }
464
+ break;
465
+ case "message":
466
+ className = " text-error-icon ";
467
+ break;
468
+ case "adorement":
469
+ className += " absolute right-0 adorement gap-1 flex items-center ";
470
+ break;
471
+ default:
472
+ break;
473
+ }
474
+ return className;
475
+ };
476
+ const handleClose = () => {
477
+ setTimeout(() => {
478
+ setDropOpen(false);
479
+ }, 500);
480
+ };
481
+ const handleClearInputValue = () => {
482
+ // setInputValue(value?.name ?? '')
483
+ };
484
+ const getInnerWidth = () => {
485
+ const innerwidth = adorementRef.current
486
+ ? adorementRef.current.offsetWidth
487
+ : 0;
488
+ return innerwidth;
489
+ };
490
+ // const getPosition = () => {
491
+ // return 'bottom'
492
+ // }
493
+ const filteredData =
494
+ inputValue !== "*" &&
495
+ inputValue !== "" &&
496
+ type !== "custom_select" &&
497
+ !noLocalFilter
498
+ ? suggestions?.filter((item: valueProps) =>
499
+ checkIncludes(
500
+ item.name,
501
+ inputValue,
502
+ item.param1 ?? "",
503
+ item.param2 ?? "",
504
+ item.param3 ?? "",
505
+ item.param4 ?? ""
506
+ )
507
+ )
508
+ : suggestions;
509
+
510
+ const handleError = (data: any) => {
511
+ if (
512
+ getErrors(data[name]) === "required" ||
513
+ getErrors(data[name]) === "Required"
514
+ ) {
515
+ return `${label ?? labelTitle} is ${getErrors(data[name])}`;
516
+ } else {
517
+ return getErrors(data[name]) ?? "";
518
+ }
519
+ };
520
+
521
+ const getDropPosition = (): any => {
522
+ if (!insideOpen && inputRef?.current) {
523
+ const dropdownHeight = 200; // Assume the height of the dropdown is 200px, adjust as needed
524
+
525
+ const inputRect = inputRef.current.getBoundingClientRect();
526
+ const spaceBelow =
527
+ window.innerHeight - (inputRect.bottom + window.scrollY);
528
+ const spaceAbove = inputRect.top + window.scrollY;
529
+
530
+ let top;
531
+ let bottom;
532
+ if (spaceBelow < dropdownHeight && spaceAbove > dropdownHeight) {
533
+ bottom = spaceBelow + 43;
534
+ } else {
535
+ top = inputRect.top + window.scrollY + inputRect.height + 2;
536
+ }
537
+ setDropPosition({
538
+ left: inputRect.left + window.scrollX,
539
+ top,
540
+ bottom,
541
+ width: inputRect.width + dropdownMinWidth,
542
+ });
543
+ }
544
+
545
+ return undefined;
546
+ };
547
+
548
+ const [showNoResults, setShowNoResults] = useState(false);
549
+
550
+ useEffect(() => {
551
+ if (
552
+ inputValue &&
553
+ filteredData?.length === 0 &&
554
+ !isLoading &&
555
+ timerRef.current === 1
556
+ ) {
557
+ const timer = setTimeout(() => {
558
+ setShowNoResults(true);
559
+ }, 500); // Delay in milliseconds
560
+ return () => clearTimeout(timer);
561
+ } else {
562
+ setShowNoResults(false);
563
+ }
564
+
565
+ // Handle keyboard navigation
566
+ const handleKeyDown = (e: KeyboardEvent) => {
567
+ if (!dropOpen) return;
568
+
569
+ const nextIndex =
570
+ e.key === "ArrowDown"
571
+ ? (selectedIndex + 1) % filteredData.length
572
+ : e.key === "ArrowUp"
573
+ ? (selectedIndex - 1 + filteredData.length) % filteredData.length
574
+ : selectedIndex;
575
+
576
+ switch (e.key) {
577
+ case "ArrowDown":
578
+ case "ArrowUp":
579
+ e.preventDefault();
580
+
581
+ // Update index first
582
+ setSelectedIndex(nextIndex);
583
+
584
+ // Wait for DOM update before scrolling
585
+ requestAnimationFrame(() => {
586
+ const el = itemRefs.current[nextIndex];
587
+ const container = scrollContainerRef.current;
588
+
589
+ if (el && container) {
590
+ const elTop = el.offsetTop;
591
+ const elBottom = elTop + el.offsetHeight;
592
+
593
+ const containerTop = container.scrollTop;
594
+ const containerBottom = containerTop + container.clientHeight;
595
+
596
+ if (elBottom > containerBottom) {
597
+ container.scrollTop = elBottom - container.clientHeight;
598
+ } else if (elTop < containerTop) {
599
+ container.scrollTop = elTop;
600
+ }
601
+ }
602
+ });
603
+ break;
604
+
605
+ case "Enter":
606
+ e.preventDefault();
607
+ handleSuggestionClick(filteredData[selectedIndex], selectedIndex);
608
+ break;
609
+
610
+ case "Escape":
611
+ e.preventDefault();
612
+ setDropOpen(false);
613
+ break;
614
+
615
+ default:
616
+ break;
617
+ }
618
+ };
619
+
620
+ if (filteredData?.length > 0 && !isLoading) {
621
+ window.addEventListener("keydown", handleKeyDown);
622
+ return () => {
623
+ window.removeEventListener("keydown", handleKeyDown);
624
+ };
625
+ }
626
+ }, [
627
+ inputValue,
628
+ filteredData,
629
+ isLoading,
630
+ timerRef.current,
631
+ selectedIndex,
632
+ dropOpen,
633
+ ]);
634
+
635
+ const setDropDown = () => {
636
+ return (
637
+ (filteredData?.length > 0 || showNoResults) && (
638
+ <div
639
+ className="autocomplete-suggections absolute bg-white shadow-gray-300 shadow-md border border-grey-light z-50 mt-9"
640
+ style={dropPosition}
641
+ >
642
+ <ul
643
+ className={`h-auto max-h-40 overflow-auto w-full ${tableView ? '' : 'py-1.5'}`}
644
+ ref={scrollContainerRef}
645
+ >
646
+ {filteredData?.length > 0 ? (
647
+ <>
648
+ {filteredData.map((suggestion: any, index: number) => (
649
+ <li
650
+ className={`${
651
+ value?.id === suggestion?.id
652
+ ? "bg-blue-navy text-white"
653
+ : `${
654
+ index === selectedIndex ? "is-selected" : ""
655
+ } hover:bg-table-hover`
656
+ } cursor-pointer text-xxs qbs-autocomplete-suggections-items ${
657
+ tableView
658
+ ? "border-b border-grey-light last:border-b-0"
659
+ : "p-1 ps-3.5 pl-[10px]"
660
+ }`}
661
+ key={suggestion?.id}
662
+ data-testid={suggestion.name}
663
+ onClick={() => handleSuggestionClick(suggestion, index)}
664
+ tabIndex={index}
665
+ ref={(el) => (itemRefs.current[index] = el)}
666
+ >
667
+ <ul className="flex items-stretch w-full list-none p-0 m-0">
668
+ {(() => {
669
+ // Sort columns by order if specified, otherwise maintain array order
670
+ const sortedColumns = additionalColumns
671
+ ? [...additionalColumns].sort((a, b) => {
672
+ const orderA = a.order !== undefined ? a.order : Infinity;
673
+ const orderB = b.order !== undefined ? b.order : Infinity;
674
+ return orderA - orderB;
675
+ })
676
+ : [];
677
+
678
+ // Separate columns before first column (order < 0) and after (order >= 0 or undefined)
679
+ const columnsBefore = sortedColumns.filter(col => col.order !== undefined && col.order < 0);
680
+ const columnsAfter = sortedColumns.filter(col => col.order === undefined || col.order >= 0);
681
+
682
+ // Determine if first column needs a separator
683
+ const hasColumnsAfter = columnsAfter.length > 0;
684
+ const hasColumnsBefore = columnsBefore.length > 0;
685
+
686
+ return (
687
+ <>
688
+ {/* Columns before first column */}
689
+ {columnsBefore.map((column, colIndex) => {
690
+ const hasValue = suggestion?.[column.key];
691
+ const isLastBefore = colIndex === columnsBefore.length - 1 && !hasColumnsAfter;
692
+ return hasValue ? (
693
+ <li
694
+ key={column.key}
695
+ className={`${column.width ? 'flex-shrink-0' : 'flex-1'} min-w-0 px-3 ${tableView ? 'py-2' : ''} ${!isLastBefore ? 'border-r border-grey-light' : ''} break-words flex flex-col`}
696
+ style={column.width ? { width: `${column.width}px` } : undefined}
697
+ >
698
+ <span className="block whitespace-normal">
699
+ {suggestion?.[column.key]}
700
+ </span>
701
+ </li>
702
+ ) : null;
703
+ })}
704
+
705
+ {/* First column (label/name) */}
706
+ <li className={`flex-1 min-w-0 px-3 ${tableView ? 'py-2' : ''} ${hasColumnsAfter ? 'border-r border-grey-light' : ''} break-words flex flex-col`}>
707
+ <span className="block whitespace-normal">
708
+ {suggestion?.label ? suggestion?.label : suggestion.name}
709
+ </span>
710
+ </li>
711
+
712
+ {/* Columns after first column */}
713
+ {columnsAfter.map((column, colIndex) => {
714
+ const hasValue = suggestion?.[column.key];
715
+ const isLastColumn = colIndex === columnsAfter.length - 1;
716
+ return hasValue ? (
717
+ <li
718
+ key={column.key}
719
+ className={`${column.width ? 'flex-shrink-0' : 'flex-1'} min-w-0 px-3 ${tableView ? 'py-2' : ''} ${!isLastColumn ? 'border-r border-grey-light' : ''} break-words flex flex-col`}
720
+ style={column.width ? { width: `${column.width}px` } : undefined}
721
+ >
722
+ <span className="block whitespace-normal">
723
+ {suggestion?.[column.key]}
724
+ </span>
725
+ </li>
726
+ ) : null;
727
+ })}
728
+ </>
729
+ );
730
+ })()}
731
+ </ul>
732
+ </li>
733
+ ))}
734
+ </>
735
+ ) : (
736
+ showNoResults &&
737
+ !isLoading &&
738
+ timerRef.current === 1 && (
739
+ <li
740
+ className={`$ cursor-pointer p-1 rounded-sm text-xxs`}
741
+ onClick={handleClose}
742
+ >
743
+ No Results Found
744
+ </li>
745
+ )
746
+ )}
747
+ </ul>
748
+ </div>
749
+ )
750
+ );
751
+ };
752
+ const getBorderRight = () => {
753
+ if (fromPrefix) {
754
+ if (
755
+ (hasParentError && Boolean(errors && errors[name])) ||
756
+ hasParentError
757
+ ) {
758
+ return 0;
759
+ } else if (errors && errors[name]) {
760
+ return undefined;
761
+ }
762
+ }
763
+ return 0;
764
+ };
765
+
766
+ useEffect(() => {
767
+ applyPositionClass(dropdownref, isHovered);
768
+ setDropOpen(false);
769
+ }, [isHovered]);
770
+
771
+ useEffect(() => {
772
+ const shouldShowClose =
773
+ value?.name &&
774
+ (value?.id || String(value?.id) === "0") &&
775
+ !disabled &&
776
+ !readOnly &&
777
+ isClose;
778
+ if (showClose !== shouldShowClose) {
779
+ setShowClose(shouldShowClose);
780
+ }
781
+ }, [value, disabled, readOnly]);
782
+ useEffect(() => {
783
+ // Check if input content overflows
784
+ const checkOverflow = () => {
785
+ if (inputRef.current) {
786
+ const { scrollWidth, clientWidth, value } = inputRef.current;
787
+ // Check for overflow only when there's content
788
+ if (value.trim() !== "") {
789
+ setShowTooltip(scrollWidth > clientWidth + 2);
790
+ } else {
791
+ setShowTooltip(false); // No tooltip for empty input
792
+ }
793
+ }
794
+ };
795
+
796
+ if (inputRef.current) {
797
+ inputRef.current.addEventListener("input", checkOverflow);
798
+ checkOverflow(); // Initial check
799
+ }
800
+
801
+ return () => {
802
+ if (inputRef.current) {
803
+ inputRef.current.removeEventListener("input", checkOverflow);
804
+ }
805
+ };
806
+ }, [inputValue]);
807
+
808
+ const handleMouseEnter = () => {
809
+ setTooltipIsHovered(true);
810
+ };
811
+
812
+ const handleMouseLeave = () => {
813
+ setTooltipIsHovered(false);
814
+ };
815
+
816
+ return (
817
+ <div
818
+ id={
819
+ id ? `autocomplete-container-${id}` : `autocomplete-container-${name}`
820
+ }
821
+ className={` flex-grow ${fullWidth ? "w-full" : "w-auto"}`}
822
+ ref={dropdownref}
823
+ >
824
+ {label && !isModern && (
825
+ <div className="mb-3">
826
+ <label className={`text-xs font-medium`}>
827
+ {label}
828
+ {required ? <span className="text-error"> *</span> : <></>}
829
+ </label>
830
+ </div>
831
+ )}
832
+ <div className="tooltip-container">
833
+ {isHovered && errors && errors[name] && (
834
+ <span className="tooltip">{handleError(errors)} </span>
835
+ )}
836
+ {tooltipIsHovered && showToolTip && !dropOpen && inputValue && (
837
+ <div className={`tooltip-info`}>{inputValue}</div>
838
+ )}
839
+
840
+ <div
841
+ className={`flex relative ${fullWidth ? "w-full" : "w-auto"}`}
842
+ style={{ width: width }}
843
+ >
844
+ <div
845
+ className="relative w-full"
846
+ onMouseEnter={() => handleMouseEnter()}
847
+ onMouseLeave={() => handleMouseLeave()}
848
+ >
849
+ <input
850
+ type="text"
851
+ readOnly={readOnly ?? type === "custom_select"}
852
+ value={inputValue ? inputValue : ""}
853
+ onBlur={handleClearInputValue}
854
+ autoComplete="off"
855
+ disabled={disabled}
856
+ ref={inputRef}
857
+ id={
858
+ id ? `custom-autocomplete-${id}` : `custom-autocomplete-${name}`
859
+ }
860
+ data-testid="custom-autocomplete"
861
+ // onBlur={() => {
862
+ // setInputValue("")
863
+ // }}
864
+ aria-describedby={id}
865
+ style={{
866
+ paddingRight: getInnerWidth(),
867
+ ...(fromPrefix
868
+ ? {
869
+ borderRight: getBorderRight(),
870
+ borderTopRightRadius: 0,
871
+ borderBottomRightRadius: 0,
872
+ }
873
+ : {}),
874
+ }}
875
+ className={` ${generateClassName("input")}`}
876
+ onChange={handleChange}
877
+ placeholder={
878
+ type === "auto_complete"
879
+ ? placeholder && isCustomPlaceholder
880
+ ? placeholder
881
+ : "Type to search"
882
+ : placeholder ?? "--Select--"
883
+ }
884
+ onFocus={onInputFocus}
885
+ onClick={(e) => {
886
+ if (type === "custom_select") {
887
+ setDropOpen(!dropOpen);
888
+ handleOpen(e);
889
+ } else {
890
+ setDropOpen(!dropOpen);
891
+ }
892
+ }}
893
+ />
894
+ {isModern && (
895
+ <label
896
+ htmlFor={id}
897
+ onClick={() => onLabelClick()}
898
+ className={generateClassName("label")}
899
+ >
900
+ {label ? <span className="truncate">{label}</span> : ""}
901
+ {required ? <span className="text-error"> *</span> : <></>}
902
+ </label>
903
+ )}
904
+ </div>
905
+ <div className="flex items-center justify-center ">
906
+ <div
907
+ ref={adorementRef}
908
+ className={`${generateClassName(
909
+ "adorement"
910
+ )} qbs-autocomplete-adorement custom-dorpdown-adorement mr-[1px] ${
911
+ isLoading ? "bg-white" : ""
912
+ }`}
913
+ >
914
+ {showClose && (
915
+ <button
916
+ onClick={() => handleClear()}
917
+ className=" text-table-bodyColor text-[#667085]"
918
+ aria-label="close"
919
+ type="button"
920
+ id="autocomplete-close-icon"
921
+ >
922
+ <CustomIcons name="close" type="large-m" />
923
+ </button>
924
+ )}
925
+ {isLoading && <Spinner />}
926
+ {type !== "auto_complete" && !disabled && !readOnly && (
927
+ <button
928
+ disabled={disabled ?? readOnly}
929
+ onClick={(e) => handleOpen(e)}
930
+ onBlur={handleClose}
931
+ className=" text-[#667085] focus-visible:outline-slate-100"
932
+ data-testid="drop-arrow"
933
+ type="button"
934
+ id="autocomplete-drop-icon"
935
+ ref={dropBtnRef}
936
+ >
937
+ {!dropOpen ? (
938
+ <DropArrow uniqueDropArrowId="drop-arrow-icon" />
939
+ ) : (
940
+ <DropArrow
941
+ className="rotate-180"
942
+ uniqueDropArrowId="drop-arrow-icon"
943
+ />
944
+ )}
945
+ </button>
946
+ )}
947
+ {errors && errors[name] && (
948
+ <div
949
+ className={` text-error-label relative cursor-pointer ${generateClassName(
950
+ "message"
951
+ )}`}
952
+ onMouseEnter={() => setIsHovered(true)}
953
+ onMouseLeave={() => setIsHovered(false)}
954
+ >
955
+ <CustomIcons name="alert" type="medium" />
956
+ </div>
957
+ )}
958
+ </div>
959
+ </div>
960
+ {dropOpen && (!isLoading || filteredData?.length > 0) && (
961
+ <>{insideOpen ? setDropDown() : <Portal>{setDropDown()}</Portal>}</>
962
+ )}
963
+ </div>
964
+ </div>
965
+ </div>
966
+ );
967
+ };
968
+
969
+ export default ReactAutoCompleteTableView;