react-core-ts 1.8.8 → 1.9.0

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,4 +1,11 @@
1
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
2
9
  import ReactDOM from 'react-dom';
3
10
 
4
11
  import { AutoSuggestionInputProps } from './commontypes';
@@ -9,7 +16,7 @@ import { debounce } from './utilities/debounce';
9
16
  import { deepEqual } from './utilities/deepEqual';
10
17
  import { default as Tooltip } from './utilities/expandableTootltip';
11
18
  import { filterSuggestions } from './utilities/filterSuggestions';
12
- import { Search, Spinner } from './utilities/icons';
19
+ import { AllDropArrow, Search, Spinner } from './utilities/icons';
13
20
 
14
21
  type ValueProps = {
15
22
  [key: string]: string;
@@ -54,7 +61,9 @@ const ExpandableAutoComplete = forwardRef<
54
61
  scrollRef,
55
62
  isTreeDropdown = false,
56
63
  shortCode = '',
57
- labelCode = ''
64
+ labelCode = '',
65
+ typeOnlyFetch = false,
66
+ autoDropdown = false,
58
67
  },
59
68
  ref
60
69
  ) => {
@@ -70,9 +79,13 @@ const ExpandableAutoComplete = forwardRef<
70
79
  const [focusedIndex, setFocusedIndex] = useState(0);
71
80
  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
72
81
  const itemsRef = useRef<HTMLDivElement>(null);
82
+ const dropBtnRef = useRef<HTMLButtonElement>(null);
83
+ const [refetchData, setRefetchData] = useState(false);
84
+
73
85
  // API call for suggestions through a custom hook
74
86
  const inputRef = useRef(null);
75
87
  const dropRef = useRef(null);
88
+ const typeRef = useRef(0);
76
89
  useImperativeHandle(ref, () => inputRef.current);
77
90
  const [dropdownStyle, setDropdownStyle] = useState({
78
91
  top: 0,
@@ -126,39 +139,51 @@ const ExpandableAutoComplete = forwardRef<
126
139
  inputValue,
127
140
  isMultiple,
128
141
  setNextPage,
129
- selectedItems
142
+ selectedItems,
143
+ typeOnlyFetch
130
144
  // nextBlock
131
145
  );
132
146
 
133
147
  // Handling the selection of a suggestion
134
- const handleSuggestionClick = useCallback((suggestion: ValueProps, idx: number) => {
135
- if (isMultiple) {
136
- setFocusedIndex(idx)
137
- setSelectedItems((prev) => {
138
- const isAdded = prev.some(
139
- (item) => item[descId] === suggestion[descId]
140
- );
141
- if (isAdded) {
142
- return prev.filter((item) => item[descId] !== suggestion[descId]);
143
- } else {
144
- return [...prev, suggestion];
145
- }
146
- });
147
- } else {
148
- setFocusedIndex(idx)
149
- setInputValue(suggestion[desc]);
150
- setSearchValue('');
151
- onChange(suggestion);
152
- setDropOpen(false);
153
- }
154
- }, []);
148
+ const handleSuggestionClick = useCallback(
149
+ (suggestion: ValueProps, idx: number) => {
150
+ if (isMultiple) {
151
+ setFocusedIndex(idx);
152
+ setSelectedItems((prev) => {
153
+ const isAdded = prev.some(
154
+ (item) => item[descId] === suggestion[descId]
155
+ );
156
+ if (isAdded) {
157
+ return prev.filter((item) => item[descId] !== suggestion[descId]);
158
+ } else {
159
+ return [...prev, suggestion];
160
+ }
161
+ });
162
+ } else {
163
+ setFocusedIndex(idx);
164
+ setInputValue(suggestion[desc]);
165
+ setSearchValue('');
166
+ onChange(suggestion);
167
+ setDropOpen(false);
168
+ }
169
+ },
170
+ []
171
+ );
155
172
 
156
173
  // Adding debounce to avoid making API calls on every keystroke
157
- const handleChangeWithDebounce = debounce((value) => {
174
+ const debouncedUpdate = useCallback(
175
+ debounce((value: string) => {
176
+ handlePickSuggestions(value, 1, false);
177
+ typeRef.current = 0;
178
+ }, 500),
179
+ []
180
+ );
181
+
182
+ const handleChangeWithDebounce = (value: string) => {
158
183
  if ((type === 'auto_complete' || type === 'auto_suggestion') && async) {
159
- handlePickSuggestions(value, 1);
160
- }
161
- }, 1000);
184
+ debouncedUpdate(value);
185
+ } else typeRef.current = 0;
186
+ };
162
187
 
163
188
  const handleMultiSelect = (e: any, suggestion: ValueProps) => {
164
189
  const { checked } = e.target;
@@ -194,9 +219,10 @@ const ExpandableAutoComplete = forwardRef<
194
219
  const { value } = e.target;
195
220
  setDropOpen(true);
196
221
  setSearchValue(value);
222
+ typeRef.current = 1;
197
223
  handleChangeWithDebounce(value);
198
224
  if (!value) {
199
- setInputValue('');
225
+ if (!typeOnlyFetch) setInputValue('');
200
226
  // onChange({ [descId]: '', [desc]: '' });
201
227
  }
202
228
  };
@@ -253,7 +279,7 @@ const ExpandableAutoComplete = forwardRef<
253
279
  ) {
254
280
  setTimeout(() => {
255
281
  setDropOpen(false);
256
- setSearchValue('');
282
+ if (!typeOnlyFetch) setSearchValue('');
257
283
  }, 200);
258
284
  }
259
285
  };
@@ -297,7 +323,6 @@ const ExpandableAutoComplete = forwardRef<
297
323
  );
298
324
 
299
325
  useEffect(() => {
300
-
301
326
  // Handle keyboard navigation
302
327
  const handleKeyDown = (e: any) => {
303
328
  if (!dropOpen) return;
@@ -305,49 +330,53 @@ const ExpandableAutoComplete = forwardRef<
305
330
  const atBottom = focusedIndex === filteredData.length - 1;
306
331
  const atTop = focusedIndex === 0;
307
332
  switch (e.key) {
308
- case "ArrowDown":
333
+ case 'ArrowDown':
309
334
  e.preventDefault();
310
335
  if (itemsRef.current) {
311
336
  if (!atBottom) {
312
- itemsRef.current.scrollTop += itemRefs.current[focusedIndex]?.offsetHeight || 50;
337
+ itemsRef.current.scrollTop +=
338
+ itemRefs.current[focusedIndex]?.offsetHeight || 50;
313
339
  } else {
314
- itemsRef.current.scrollTop = 0;
340
+ itemsRef.current.scrollTop = 0;
315
341
  }
316
342
  }
317
343
  setFocusedIndex((prev) => (prev + 1) % filteredData?.length);
318
344
  break;
319
-
320
- case "ArrowUp":
345
+
346
+ case 'ArrowUp':
321
347
  e.preventDefault();
322
348
  if (itemsRef.current) {
323
349
  if (!atTop) {
324
- itemsRef.current.scrollTop -= itemRefs.current[focusedIndex]?.offsetHeight || 50;
350
+ itemsRef.current.scrollTop -=
351
+ itemRefs.current[focusedIndex]?.offsetHeight || 50;
325
352
  } else {
326
- itemsRef.current.scrollTop = 0;
353
+ itemsRef.current.scrollTop = 0;
327
354
  }
328
355
  }
329
- setFocusedIndex((prev) => (prev - 1 + filteredData?.length) % filteredData?.length);
356
+ setFocusedIndex(
357
+ (prev) => (prev - 1 + filteredData?.length) % filteredData?.length
358
+ );
330
359
  break;
331
-
332
- case "Enter":
360
+
361
+ case 'Enter':
333
362
  e.preventDefault();
334
- handleSuggestionClick(filteredData[focusedIndex], focusedIndex)
363
+ handleSuggestionClick(filteredData[focusedIndex], focusedIndex);
335
364
  break;
336
-
337
- case "Escape":
365
+
366
+ case 'Escape':
338
367
  e.preventDefault();
339
368
  setDropOpen(false);
340
369
  break;
341
-
370
+
342
371
  default:
343
372
  break;
344
373
  }
345
374
  };
346
-
347
- window.addEventListener("keydown", handleKeyDown);
348
-
375
+
376
+ window.addEventListener('keydown', handleKeyDown);
377
+
349
378
  return () => {
350
- window.removeEventListener("keydown", handleKeyDown);
379
+ window.removeEventListener('keydown', handleKeyDown);
351
380
  };
352
381
  }, [filteredData, dropOpen, isLoading, focusedIndex]);
353
382
 
@@ -402,6 +431,38 @@ const ExpandableAutoComplete = forwardRef<
402
431
  else onChange({ [descId]: '', [desc]: '' });
403
432
  };
404
433
 
434
+ const handleSelectAll = () => {
435
+ if (isMultiple) {
436
+ filteredData.map((suggestion: ValueProps) =>
437
+ setSelectedItems((prev) => {
438
+ const isAdded = prev.some(
439
+ (item) => item[descId] === suggestion[descId]
440
+ );
441
+ if (isAdded) {
442
+ return prev;
443
+ } else {
444
+ return [...prev, suggestion];
445
+ }
446
+ })
447
+ );
448
+ }
449
+ };
450
+
451
+ const handleOpenDropdown = (e: any) => {
452
+ if (!suggestions || suggestions?.length === 0 || refetchData) {
453
+ if (autoDropdown && (inputValue === '' || inputValue.trim() === '')) {
454
+ handlePickSuggestions('a', 1);
455
+ setRefetchData(false);
456
+ } else if (!refetchData && inputValue !== '') {
457
+ handlePickSuggestions(inputValue, 1);
458
+ setRefetchData(true);
459
+ }
460
+ }
461
+ if (!isLoading) {
462
+ setDropOpen(!dropOpen);
463
+ }
464
+ };
465
+
405
466
  return (
406
467
  <div
407
468
  id={id ? `expandable-container-${id}` : `expandable-container-${name}`}
@@ -513,10 +574,30 @@ const ExpandableAutoComplete = forwardRef<
513
574
  />
514
575
  </div>
515
576
 
577
+ <>
578
+ {autoDropdown && !disabled && !readOnly && (
579
+ <button
580
+ disabled={disabled ?? readOnly}
581
+ onClick={(e) => handleOpenDropdown(e)}
582
+ className=" text-[#667085] focus-visible:outline-slate-100 absolute right-2"
583
+ data-testid="drop-arrow"
584
+ type="button"
585
+ id="autocomplete-drop-icon"
586
+ ref={dropBtnRef}
587
+ >
588
+ <AllDropArrow
589
+ type={!dropOpen ? 'down' : 'up'}
590
+ uniqueId="all-dropdow-arrow-icon"
591
+ className="all-dropdow-arrow-icon"
592
+ />
593
+ </button>
594
+ )}
595
+ </>
596
+
516
597
  {selectedItems?.length > 1 && (
517
598
  <div
518
599
  id="expandable-clear-all"
519
- className={`qbs-clear-link qbs-text-right qbs-cursor-pointer qbs-text-xs absolute right-2 bottom-1`}
600
+ className={`qbs-clear-link qbs-text-right qbs-cursor-pointer qbs-text-xs absolute right-2 qbs-bottom-0`}
520
601
  onClick={handleClearSelected}
521
602
  >
522
603
  Clear all
@@ -536,6 +617,7 @@ const ExpandableAutoComplete = forwardRef<
536
617
  expandable={expandable}
537
618
  handleClear={handleClear}
538
619
  uniqueDropArrowId="drop-arrow-expanded-list-icon"
620
+ autoDropdown={autoDropdown}
539
621
  />
540
622
  {/* Displaying Loading Spinner */}
541
623
 
@@ -571,7 +653,10 @@ const ExpandableAutoComplete = forwardRef<
571
653
  </div>
572
654
  )}
573
655
 
574
- <div className={`qbs-autocomplete-suggestions-sub `} ref={itemsRef}>
656
+ <div
657
+ className={`qbs-autocomplete-suggestions-sub `}
658
+ ref={itemsRef}
659
+ >
575
660
  {filteredData?.length > 0 ? (
576
661
  filteredData.map((suggestion: ValueProps, idx: number) => (
577
662
  <DropdownList
@@ -610,7 +695,10 @@ const ExpandableAutoComplete = forwardRef<
610
695
  className="qbs-autocomplete-notfound"
611
696
  onClick={handleBlur}
612
697
  >
613
- {notDataMessage ?? 'No Results Found'}
698
+ {notDataMessage ??
699
+ (searchValue === '' || typeRef.current === 1)
700
+ ? 'Start typing to see suggestions'
701
+ : 'No Results Found'}
614
702
  </li>
615
703
  )}
616
704
  </>
@@ -627,6 +715,15 @@ const ExpandableAutoComplete = forwardRef<
627
715
  </div>
628
716
  )}
629
717
  </div>
718
+ {filteredData?.length > 0 && (
719
+ <div
720
+ id="select-all"
721
+ className={`qbs-select-all-link qbs-text-right qbs-cursor-pointer qbs-text-xs absolute right-2 bottom-1`}
722
+ onClick={handleSelectAll}
723
+ >
724
+ Select all
725
+ </div>
726
+ )}
630
727
  </ul>,
631
728
  document.body
632
729
  )}
@@ -81,6 +81,7 @@ export interface AutoSuggestionInputProps {
81
81
  labelCode?: string,
82
82
  tabInlineSearch?: boolean // tab dropdown search
83
83
  matchFromStart?: boolean // match data's from the starting position
84
+ autoDropdown?: boolean;
84
85
  }
85
86
  export interface IconsProps {
86
87
  name: any;
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React from 'react';
2
2
 
3
- import { Close, DropArrow } from "../utilities/icons";
3
+ import { Close, DropArrow } from '../utilities/icons';
4
4
 
5
5
  type ValueProps = {
6
6
  inputValue: string;
@@ -16,6 +16,7 @@ type ValueProps = {
16
16
  error?: boolean;
17
17
  viewMode?: boolean;
18
18
  uniqueDropArrowId?: string;
19
+ autoDropdown?: boolean;
19
20
  };
20
21
  const InputActions: React.FC<ValueProps> = ({
21
22
  inputValue,
@@ -31,6 +32,7 @@ const InputActions: React.FC<ValueProps> = ({
31
32
  error,
32
33
  viewMode,
33
34
  uniqueDropArrowId,
35
+ autoDropdown = false,
34
36
  }) => {
35
37
  return (
36
38
  <div
@@ -54,31 +56,32 @@ const InputActions: React.FC<ValueProps> = ({
54
56
  <Close />
55
57
  </button>
56
58
  )}
57
- {dropOpen ? (
58
- <button
59
- type="button"
60
- onClick={(e) => handleDropClose(e)}
61
- className="icon-button text-[#667085] "
62
- aria-label="toggle"
63
- name="toggle"
64
- >
65
- <DropArrow
66
- className={`icon-button-rotate`}
67
- uniqueId={uniqueDropArrowId}
68
- />
69
- </button>
70
- ) : (
71
- <button
72
- disabled={(disabled || readOnly) && !viewMode}
73
- type="button"
74
- className="icon-button text-[#667085] "
75
- onClick={(e) => handleDropOpen(e)}
76
- aria-label="toggle"
77
- name="toggle"
78
- >
79
- <DropArrow uniqueId={uniqueDropArrowId} />
80
- </button>
81
- )}
59
+ {!autoDropdown &&
60
+ (dropOpen ? (
61
+ <button
62
+ type="button"
63
+ onClick={(e) => handleDropClose(e)}
64
+ className="icon-button text-[#667085] "
65
+ aria-label="toggle"
66
+ name="toggle"
67
+ >
68
+ <DropArrow
69
+ className={`icon-button-rotate`}
70
+ uniqueId={uniqueDropArrowId}
71
+ />
72
+ </button>
73
+ ) : (
74
+ <button
75
+ disabled={(disabled || readOnly) && !viewMode}
76
+ type="button"
77
+ className="icon-button text-[#667085] "
78
+ onClick={(e) => handleDropOpen(e)}
79
+ aria-label="toggle"
80
+ name="toggle"
81
+ >
82
+ <DropArrow uniqueId={uniqueDropArrowId} />
83
+ </button>
84
+ ))}
82
85
  </div>
83
86
  );
84
87
  };
@@ -128,3 +128,6 @@
128
128
  transition: height 3s ease-in-out, opacity 3s ease-in-out,
129
129
  padding 1s ease-in-out;
130
130
  }
131
+ .qbs-bottom-0{
132
+ bottom: 0;
133
+ }