react-core-ts 2.1.30 → 2.1.32
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 +59 -0
- package/dist/ReactAutoCompleteTableView.d.ts +23 -0
- package/dist/ReactAutoCompleteTableView.js +184 -71
- package/dist/ReactAutoCompleteTableView.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/styles/components/autocomplete.css +69 -0
- package/dist/styles/components/tooltip.css +4 -6
- package/dist/styles/styles/components/autocomplete.css +69 -0
- package/dist/styles/styles/components/tooltip.css +4 -6
- package/docs/App.tsx +61 -0
- package/package.json +1 -1
- package/src/ReactAutoCompleteTableView.tsx +322 -94
- package/src/index.tsx +5 -0
- package/src/styles/components/autocomplete.css +69 -0
- package/src/styles/components/tooltip.css +4 -6
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { FieldErrors } from 'react-hook-form';
|
|
3
9
|
|
|
4
10
|
import CustomIcons from './components/customIcons';
|
|
@@ -18,6 +24,26 @@ type valueProps = {
|
|
|
18
24
|
param4?: string | number | null;
|
|
19
25
|
from?: number;
|
|
20
26
|
};
|
|
27
|
+
|
|
28
|
+
/** Add new theme ids here when introducing layouts */
|
|
29
|
+
export type DropdownThemeId = 'default' | 'splitDetail';
|
|
30
|
+
|
|
31
|
+
/** Options for `dropdownTheme: 'splitDetail'` (title + subtitle + optional badge pill) */
|
|
32
|
+
export type SplitDetailThemeConfig = {
|
|
33
|
+
subtitleKey?: string;
|
|
34
|
+
badgeKey?: string;
|
|
35
|
+
/** Shown before badge value, e.g. "Acc. No.: " */
|
|
36
|
+
badgePrefix?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Per-theme configuration. Extend this type with new keys when adding themes
|
|
41
|
+
* (e.g. `compact?: CompactThemeConfig`).
|
|
42
|
+
*/
|
|
43
|
+
export type DropdownThemeConfig = {
|
|
44
|
+
splitDetail?: SplitDetailThemeConfig;
|
|
45
|
+
};
|
|
46
|
+
|
|
21
47
|
interface AutoSuggestionInputProps {
|
|
22
48
|
id?: string;
|
|
23
49
|
label?: string;
|
|
@@ -62,6 +88,13 @@ interface AutoSuggestionInputProps {
|
|
|
62
88
|
key: string; // Field name in the suggestion object
|
|
63
89
|
label: string; // Label for the column
|
|
64
90
|
}>; // Column header to display
|
|
91
|
+
dropdownTheme?: DropdownThemeId;
|
|
92
|
+
/** Theme-specific field mapping and options; see `DropdownThemeConfig` */
|
|
93
|
+
dropdownThemeConfig?: DropdownThemeConfig;
|
|
94
|
+
/**
|
|
95
|
+
* When true, typing updates the input only; fetching/opening the list runs from the search control (and Enter), not while typing.
|
|
96
|
+
*/
|
|
97
|
+
searchOnButtonOnly?: boolean;
|
|
65
98
|
}
|
|
66
99
|
|
|
67
100
|
const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
@@ -100,7 +133,16 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
100
133
|
tableView = false,
|
|
101
134
|
additionalColumns = [],
|
|
102
135
|
columnHeader = [],
|
|
136
|
+
dropdownTheme = 'default',
|
|
137
|
+
dropdownThemeConfig,
|
|
138
|
+
searchOnButtonOnly = false,
|
|
103
139
|
}) => {
|
|
140
|
+
const splitDetailOptions = dropdownThemeConfig?.splitDetail;
|
|
141
|
+
const showSearchOnButtonChrome =
|
|
142
|
+
searchOnButtonOnly &&
|
|
143
|
+
(type === 'auto_complete' || type === 'custom_search_select') &&
|
|
144
|
+
!disabled &&
|
|
145
|
+
!readOnly;
|
|
104
146
|
const [inputValue, setInputValue] = useState<any>(value?.name ?? "");
|
|
105
147
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
|
106
148
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
@@ -193,13 +235,52 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
193
235
|
timerRef.current = 0;
|
|
194
236
|
|
|
195
237
|
setInputValue(value);
|
|
196
|
-
|
|
238
|
+
if (!searchOnButtonOnly) {
|
|
239
|
+
handleValChange(value);
|
|
240
|
+
}
|
|
197
241
|
if (!value) {
|
|
198
242
|
setInputValue('');
|
|
199
243
|
onChange({ id: undefined, name: '', from: 2 });
|
|
200
244
|
}
|
|
201
245
|
};
|
|
202
246
|
|
|
247
|
+
const runSearchFromInput = (value: string) => {
|
|
248
|
+
setDropOpen(true);
|
|
249
|
+
onChange({ id: undefined, name: '', from: 1 });
|
|
250
|
+
if (value.trim() === '' && type === 'auto_complete') {
|
|
251
|
+
setSuggestions([]);
|
|
252
|
+
if (autoFilter) {
|
|
253
|
+
handleDropData('*');
|
|
254
|
+
} else {
|
|
255
|
+
setDropOpen(false);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
handleDropData(value);
|
|
259
|
+
}
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
timerRef.current = 1;
|
|
262
|
+
}, 200);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const handleSearchButtonClick = (e: React.MouseEvent) => {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
if (disabled || readOnly) return;
|
|
269
|
+
timerRef.current = 0;
|
|
270
|
+
const q = inputRef.current?.value ?? inputValue ?? '';
|
|
271
|
+
runSearchFromInput(q);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const handleSearchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
275
|
+
if (!searchOnButtonOnly || disabled || readOnly) return;
|
|
276
|
+
if (e.key === 'Enter') {
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
timerRef.current = 0;
|
|
279
|
+
const q = inputRef.current?.value ?? inputValue ?? '';
|
|
280
|
+
runSearchFromInput(q);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
203
284
|
const handleValChange = useCallback(
|
|
204
285
|
debounce(
|
|
205
286
|
(value: string) => {
|
|
@@ -283,6 +364,9 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
283
364
|
const onLabelClick = () => {
|
|
284
365
|
if (!isDisabled) {
|
|
285
366
|
inputRef?.current?.focus();
|
|
367
|
+
if (searchOnButtonOnly) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
286
370
|
if (autoFilter && inputValue === '') {
|
|
287
371
|
handleValChange('*');
|
|
288
372
|
} else if (
|
|
@@ -344,6 +428,9 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
344
428
|
}, [autoFocus]);
|
|
345
429
|
const onInputFocus = () => {
|
|
346
430
|
if (!isDisabled) {
|
|
431
|
+
if (searchOnButtonOnly) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
347
434
|
if (autoFilter && inputValue === '') {
|
|
348
435
|
handleValChange('*');
|
|
349
436
|
} else if (
|
|
@@ -379,6 +466,12 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
379
466
|
};
|
|
380
467
|
|
|
381
468
|
const handleOpenDropdown = (e: any) => {
|
|
469
|
+
if (searchOnButtonOnly) {
|
|
470
|
+
if (suggestions && suggestions.length > 0 && !isLoading) {
|
|
471
|
+
setDropOpen((open) => !open);
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
382
475
|
if (!suggestions || suggestions?.length === 0 || refetchData) {
|
|
383
476
|
if (autoDropdown && (inputValue === '' || inputValue.trim() === '')) {
|
|
384
477
|
setSuggestions([]);
|
|
@@ -554,21 +647,50 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
554
647
|
// const getPosition = () => {
|
|
555
648
|
// return 'bottom'
|
|
556
649
|
// }
|
|
650
|
+
const itemMatchesLocalFilter = (item: any): boolean => {
|
|
651
|
+
const baseMatch = checkIncludes(
|
|
652
|
+
item.name,
|
|
653
|
+
inputValue,
|
|
654
|
+
item.param1 ?? '',
|
|
655
|
+
item.param2 ?? '',
|
|
656
|
+
item.param3 ?? '',
|
|
657
|
+
item.param4 ?? ''
|
|
658
|
+
);
|
|
659
|
+
if (
|
|
660
|
+
dropdownTheme !== 'splitDetail' ||
|
|
661
|
+
!splitDetailOptions ||
|
|
662
|
+
!inputValue
|
|
663
|
+
) {
|
|
664
|
+
return baseMatch;
|
|
665
|
+
}
|
|
666
|
+
const q = inputValue.trim().toLowerCase();
|
|
667
|
+
if (!q) return baseMatch;
|
|
668
|
+
|
|
669
|
+
const hay = (v: unknown) =>
|
|
670
|
+
v != null && String(v).toLowerCase().includes(q);
|
|
671
|
+
|
|
672
|
+
if (item.label != null && hay(item.label)) return true;
|
|
673
|
+
if (
|
|
674
|
+
splitDetailOptions.subtitleKey &&
|
|
675
|
+
hay(item[splitDetailOptions.subtitleKey])
|
|
676
|
+
) {
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
if (
|
|
680
|
+
splitDetailOptions.badgeKey &&
|
|
681
|
+
hay(item[splitDetailOptions.badgeKey])
|
|
682
|
+
) {
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
return baseMatch;
|
|
686
|
+
};
|
|
687
|
+
|
|
557
688
|
const filteredData =
|
|
558
689
|
inputValue !== '*' &&
|
|
559
690
|
inputValue !== '' &&
|
|
560
691
|
type !== 'custom_select' &&
|
|
561
692
|
!noLocalFilter
|
|
562
|
-
? suggestions?.filter((item: valueProps) =>
|
|
563
|
-
checkIncludes(
|
|
564
|
-
item.name,
|
|
565
|
-
inputValue,
|
|
566
|
-
item.param1 ?? '',
|
|
567
|
-
item.param2 ?? '',
|
|
568
|
-
item.param3 ?? '',
|
|
569
|
-
item.param4 ?? ''
|
|
570
|
-
)
|
|
571
|
-
)
|
|
693
|
+
? suggestions?.filter((item: valueProps) => itemMatchesLocalFilter(item))
|
|
572
694
|
: suggestions;
|
|
573
695
|
|
|
574
696
|
const handleError = (data: any) => {
|
|
@@ -764,7 +886,11 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
764
886
|
<div
|
|
765
887
|
ref={dropdownContentRef}
|
|
766
888
|
style={{ ...dropPosition, overflow: 'hidden' }}
|
|
767
|
-
className=
|
|
889
|
+
className={`autocomplete-suggections autocomplete-suggections-tableview absolute bg-white shadow-gray-300 shadow-md border border-grey-light z-50 mt-9${
|
|
890
|
+
dropdownTheme === 'splitDetail'
|
|
891
|
+
? ' autocomplete-suggections--split-detail'
|
|
892
|
+
: ''
|
|
893
|
+
}`}
|
|
768
894
|
>
|
|
769
895
|
<ul
|
|
770
896
|
className={`h-auto overflow-auto w-full ${tableView ? '' : 'py-1.5'}`}
|
|
@@ -773,7 +899,9 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
773
899
|
>
|
|
774
900
|
{filteredData?.length > 0 ? (
|
|
775
901
|
<>
|
|
776
|
-
{
|
|
902
|
+
{dropdownTheme === 'default' &&
|
|
903
|
+
columnHeader &&
|
|
904
|
+
columnHeader.length > 0 && (
|
|
777
905
|
<li className="sticky top-0 auto-suggections-tableview-header z-10 bg-gray-100 border-b border-grey-light">
|
|
778
906
|
<ul className="flex items-stretch w-full list-none p-0 m-0">
|
|
779
907
|
{(() => {
|
|
@@ -855,9 +983,9 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
855
983
|
index === selectedIndex ? 'is-selected' : ''
|
|
856
984
|
} hover:bg-table-hover`
|
|
857
985
|
} cursor-pointer text-xxs qbs-autocomplete-suggections-items ${
|
|
858
|
-
tableView
|
|
859
|
-
?
|
|
860
|
-
:
|
|
986
|
+
dropdownTheme === 'splitDetail' || tableView
|
|
987
|
+
? 'border-b border-grey-light last:border-b-0'
|
|
988
|
+
: 'p-1 ps-3.5 pl-[10px]'
|
|
861
989
|
}`}
|
|
862
990
|
key={suggestion?.id}
|
|
863
991
|
data-testid={suggestion.name}
|
|
@@ -865,70 +993,144 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
865
993
|
tabIndex={index}
|
|
866
994
|
ref={(el) => (itemRefs.current[index] = el)}
|
|
867
995
|
>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
key={column.key}
|
|
895
|
-
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`}
|
|
896
|
-
style={column.width ? { width: `${column.width}px` } : undefined}
|
|
897
|
-
>
|
|
898
|
-
<span className="block whitespace-normal">
|
|
899
|
-
{suggestion?.[column.key]}
|
|
900
|
-
</span>
|
|
901
|
-
</li>
|
|
902
|
-
) : null;
|
|
903
|
-
})}
|
|
904
|
-
|
|
905
|
-
{/* First column (label/name) */}
|
|
906
|
-
<li className={`flex-1 min-w-0 px-3 ${tableView ? 'py-2' : ''} ${hasColumnsAfter ? 'border-r border-grey-light' : ''} break-words flex flex-col`}>
|
|
907
|
-
<span className="block whitespace-normal">
|
|
908
|
-
{suggestion?.label ? suggestion?.label : suggestion.name}
|
|
996
|
+
{dropdownTheme === 'splitDetail' ? (
|
|
997
|
+
<div className="qbs-split-detail-row">
|
|
998
|
+
<div className="qbs-split-detail-main">
|
|
999
|
+
<div className="qbs-split-detail-title">
|
|
1000
|
+
{suggestion?.label ? suggestion?.label : suggestion.name}
|
|
1001
|
+
</div>
|
|
1002
|
+
{splitDetailOptions?.subtitleKey &&
|
|
1003
|
+
suggestion?.[splitDetailOptions.subtitleKey] != null &&
|
|
1004
|
+
String(
|
|
1005
|
+
suggestion[splitDetailOptions.subtitleKey]
|
|
1006
|
+
).trim() !== '' && (
|
|
1007
|
+
<div className="qbs-split-detail-subtitle">
|
|
1008
|
+
{String(
|
|
1009
|
+
suggestion[splitDetailOptions.subtitleKey]
|
|
1010
|
+
)}
|
|
1011
|
+
</div>
|
|
1012
|
+
)}
|
|
1013
|
+
</div>
|
|
1014
|
+
{splitDetailOptions?.badgeKey &&
|
|
1015
|
+
suggestion?.[splitDetailOptions.badgeKey] != null &&
|
|
1016
|
+
String(suggestion[splitDetailOptions.badgeKey]).trim() !==
|
|
1017
|
+
'' && (
|
|
1018
|
+
<div className="qbs-split-detail-badge-wrap">
|
|
1019
|
+
<span className="qbs-split-detail-badge">
|
|
1020
|
+
{splitDetailOptions.badgePrefix ?? ''}
|
|
1021
|
+
{String(suggestion[splitDetailOptions.badgeKey])}
|
|
909
1022
|
</span>
|
|
910
|
-
</
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1023
|
+
</div>
|
|
1024
|
+
)}
|
|
1025
|
+
</div>
|
|
1026
|
+
) : (
|
|
1027
|
+
<ul className="flex items-stretch w-full list-none p-0 m-0">
|
|
1028
|
+
{(() => {
|
|
1029
|
+
// Sort columns by order if specified, otherwise maintain array order
|
|
1030
|
+
const sortedColumns = additionalColumns
|
|
1031
|
+
? [...additionalColumns].sort((a, b) => {
|
|
1032
|
+
const orderA =
|
|
1033
|
+
a.order !== undefined ? a.order : Infinity;
|
|
1034
|
+
const orderB =
|
|
1035
|
+
b.order !== undefined ? b.order : Infinity;
|
|
1036
|
+
return orderA - orderB;
|
|
1037
|
+
})
|
|
1038
|
+
: [];
|
|
1039
|
+
|
|
1040
|
+
// Separate columns before first column (order < 0) and after (order >= 0 or undefined)
|
|
1041
|
+
const columnsBefore = sortedColumns.filter(
|
|
1042
|
+
(col) => col.order !== undefined && col.order < 0
|
|
1043
|
+
);
|
|
1044
|
+
const columnsAfter = sortedColumns.filter(
|
|
1045
|
+
(col) =>
|
|
1046
|
+
col.order === undefined || col.order >= 0
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
// Determine if first column needs a separator
|
|
1050
|
+
const hasColumnsAfter = columnsAfter.length > 0;
|
|
1051
|
+
|
|
1052
|
+
return (
|
|
1053
|
+
<>
|
|
1054
|
+
{/* Columns before first column */}
|
|
1055
|
+
{columnsBefore.map((column, colIndex) => {
|
|
1056
|
+
const hasValue = suggestion?.[column.key];
|
|
1057
|
+
const isLastBefore = false;
|
|
1058
|
+
return hasValue ? (
|
|
1059
|
+
<li
|
|
1060
|
+
key={column.key}
|
|
1061
|
+
className={`${
|
|
1062
|
+
column.width ? 'flex-shrink-0' : 'flex-1'
|
|
1063
|
+
} min-w-0 px-3 ${
|
|
1064
|
+
tableView ? 'py-2' : ''
|
|
1065
|
+
} ${
|
|
1066
|
+
!isLastBefore
|
|
1067
|
+
? 'border-r border-grey-light'
|
|
1068
|
+
: ''
|
|
1069
|
+
} break-words flex flex-col`}
|
|
1070
|
+
style={
|
|
1071
|
+
column.width
|
|
1072
|
+
? { width: `${column.width}px` }
|
|
1073
|
+
: undefined
|
|
1074
|
+
}
|
|
1075
|
+
>
|
|
1076
|
+
<span className="block whitespace-normal">
|
|
1077
|
+
{suggestion?.[column.key]}
|
|
1078
|
+
</span>
|
|
1079
|
+
</li>
|
|
1080
|
+
) : null;
|
|
1081
|
+
})}
|
|
1082
|
+
|
|
1083
|
+
{/* First column (label/name) */}
|
|
1084
|
+
<li
|
|
1085
|
+
className={`flex-1 min-w-0 px-3 ${
|
|
1086
|
+
tableView ? 'py-2' : ''
|
|
1087
|
+
} ${
|
|
1088
|
+
hasColumnsAfter
|
|
1089
|
+
? 'border-r border-grey-light'
|
|
1090
|
+
: ''
|
|
1091
|
+
} break-words flex flex-col`}
|
|
1092
|
+
>
|
|
1093
|
+
<span className="block whitespace-normal">
|
|
1094
|
+
{suggestion?.label
|
|
1095
|
+
? suggestion?.label
|
|
1096
|
+
: suggestion.name}
|
|
1097
|
+
</span>
|
|
1098
|
+
</li>
|
|
1099
|
+
|
|
1100
|
+
{/* Columns after first column */}
|
|
1101
|
+
{columnsAfter.map((column, colIndex) => {
|
|
1102
|
+
const hasValue = suggestion?.[column.key];
|
|
1103
|
+
const isLastColumn =
|
|
1104
|
+
colIndex === columnsAfter.length - 1;
|
|
1105
|
+
return hasValue ? (
|
|
1106
|
+
<li
|
|
1107
|
+
key={column.key}
|
|
1108
|
+
className={`${
|
|
1109
|
+
column.width ? 'flex-shrink-0' : 'flex-1'
|
|
1110
|
+
} min-w-0 px-3 ${
|
|
1111
|
+
tableView ? 'py-2' : ''
|
|
1112
|
+
} ${
|
|
1113
|
+
!isLastColumn
|
|
1114
|
+
? 'border-r border-grey-light'
|
|
1115
|
+
: ''
|
|
1116
|
+
} break-words flex flex-col`}
|
|
1117
|
+
style={
|
|
1118
|
+
column.width
|
|
1119
|
+
? { width: `${column.width}px` }
|
|
1120
|
+
: undefined
|
|
1121
|
+
}
|
|
1122
|
+
>
|
|
1123
|
+
<span className="block whitespace-normal">
|
|
1124
|
+
{suggestion?.[column.key]}
|
|
1125
|
+
</span>
|
|
1126
|
+
</li>
|
|
1127
|
+
) : null;
|
|
1128
|
+
})}
|
|
1129
|
+
</>
|
|
1130
|
+
);
|
|
1131
|
+
})()}
|
|
1132
|
+
</ul>
|
|
1133
|
+
)}
|
|
932
1134
|
</li>
|
|
933
1135
|
))}
|
|
934
1136
|
</>
|
|
@@ -1034,8 +1236,11 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1034
1236
|
});
|
|
1035
1237
|
}, []);
|
|
1036
1238
|
|
|
1037
|
-
|
|
1038
|
-
if (!(tooltipIsHovered && showToolTip && !dropOpen && inputValue))
|
|
1239
|
+
useLayoutEffect(() => {
|
|
1240
|
+
if (!(tooltipIsHovered && showToolTip && !dropOpen && inputValue)) {
|
|
1241
|
+
setTooltipPosition(null);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1039
1244
|
|
|
1040
1245
|
updateTooltipPosition();
|
|
1041
1246
|
window.addEventListener('scroll', updateTooltipPosition, true);
|
|
@@ -1062,8 +1267,11 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1062
1267
|
});
|
|
1063
1268
|
}, []);
|
|
1064
1269
|
|
|
1065
|
-
|
|
1066
|
-
if (!(isHovered && errors && errors[name]))
|
|
1270
|
+
useLayoutEffect(() => {
|
|
1271
|
+
if (!(isHovered && errors && errors[name])) {
|
|
1272
|
+
setErrorTooltipPosition(null);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1067
1275
|
updateErrorTooltipPosition();
|
|
1068
1276
|
window.addEventListener('scroll', updateErrorTooltipPosition, true);
|
|
1069
1277
|
window.addEventListener('resize', updateErrorTooltipPosition);
|
|
@@ -1090,17 +1298,16 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1090
1298
|
</div>
|
|
1091
1299
|
)}
|
|
1092
1300
|
<div className="tooltip-container">
|
|
1093
|
-
{isHovered && errors && errors[name] && (
|
|
1301
|
+
{isHovered && errors && errors[name] && errorTooltipPosition && (
|
|
1094
1302
|
<Portal>
|
|
1095
1303
|
<span
|
|
1096
1304
|
className="tooltip tooltip-portal-error"
|
|
1097
1305
|
style={{
|
|
1098
1306
|
position: 'fixed',
|
|
1099
|
-
top: errorTooltipPosition
|
|
1100
|
-
left: errorTooltipPosition
|
|
1307
|
+
top: errorTooltipPosition.top,
|
|
1308
|
+
left: errorTooltipPosition.left,
|
|
1101
1309
|
transform: 'translateX(-100%)',
|
|
1102
1310
|
zIndex: 9999,
|
|
1103
|
-
visibility: errorTooltipPosition ? 'visible' : 'hidden',
|
|
1104
1311
|
}}
|
|
1105
1312
|
>
|
|
1106
1313
|
{handleError(errors)}
|
|
@@ -1111,18 +1318,17 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1111
1318
|
showToolTip &&
|
|
1112
1319
|
!dropOpen &&
|
|
1113
1320
|
inputValue &&
|
|
1114
|
-
(
|
|
1321
|
+
tooltipPosition && (
|
|
1115
1322
|
<Portal>
|
|
1116
1323
|
<div
|
|
1117
1324
|
className="tooltip-info tooltip-info-portal"
|
|
1118
1325
|
style={{
|
|
1119
1326
|
position: 'fixed',
|
|
1120
|
-
top: tooltipPosition
|
|
1121
|
-
left: tooltipPosition
|
|
1122
|
-
maxWidth: tooltipPosition
|
|
1327
|
+
top: tooltipPosition.top,
|
|
1328
|
+
left: tooltipPosition.left,
|
|
1329
|
+
maxWidth: tooltipPosition.width,
|
|
1123
1330
|
transform: 'translateX(-100%)',
|
|
1124
1331
|
zIndex: 9999,
|
|
1125
|
-
visibility: tooltipPosition ? 'visible' : 'hidden',
|
|
1126
1332
|
}}
|
|
1127
1333
|
>
|
|
1128
1334
|
{inputValue}
|
|
@@ -1175,10 +1381,13 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1175
1381
|
: placeholder ?? '--Select--'
|
|
1176
1382
|
}
|
|
1177
1383
|
onFocus={onInputFocus}
|
|
1384
|
+
onKeyDown={handleSearchInputKeyDown}
|
|
1178
1385
|
onClick={(e) => {
|
|
1179
1386
|
if (type === 'custom_select') {
|
|
1180
1387
|
setDropOpen(!dropOpen);
|
|
1181
1388
|
handleOpen(e);
|
|
1389
|
+
} else if (searchOnButtonOnly) {
|
|
1390
|
+
return;
|
|
1182
1391
|
} else {
|
|
1183
1392
|
if (dropOpen || filteredData?.length > 0)
|
|
1184
1393
|
setDropOpen(!dropOpen);
|
|
@@ -1216,7 +1425,26 @@ const ReactAutoCompleteTableView: React.FC<AutoSuggestionInputProps> = ({
|
|
|
1216
1425
|
<CustomIcons name="close" type="large-m" />
|
|
1217
1426
|
</button>
|
|
1218
1427
|
)}
|
|
1219
|
-
{
|
|
1428
|
+
{showSearchOnButtonChrome && (
|
|
1429
|
+
<button
|
|
1430
|
+
type="button"
|
|
1431
|
+
aria-label={isLoading ? 'Loading' : 'Search'}
|
|
1432
|
+
aria-busy={isLoading}
|
|
1433
|
+
data-testid="autocomplete-search-button"
|
|
1434
|
+
onClick={handleSearchButtonClick}
|
|
1435
|
+
disabled={isLoading}
|
|
1436
|
+
className="text-table-bodyColor text-[#667085] focus-visible:outline-slate-100 p-0.5 inline-flex items-center justify-center min-w-[28px] min-h-[28px] disabled:opacity-70"
|
|
1437
|
+
>
|
|
1438
|
+
{isLoading ? (
|
|
1439
|
+
<span className="inline-flex items-center justify-center [&_svg]:mr-0 [&_svg]:h-5 [&_svg]:w-5">
|
|
1440
|
+
<Spinner />
|
|
1441
|
+
</span>
|
|
1442
|
+
) : (
|
|
1443
|
+
<CustomIcons name="search" type="medium" />
|
|
1444
|
+
)}
|
|
1445
|
+
</button>
|
|
1446
|
+
)}
|
|
1447
|
+
{isLoading && !showSearchOnButtonChrome && <Spinner />}
|
|
1220
1448
|
{(type !== 'auto_complete' || autoDropdown) &&
|
|
1221
1449
|
!disabled &&
|
|
1222
1450
|
!readOnly && (
|
package/src/index.tsx
CHANGED
|
@@ -21,3 +21,8 @@ export { default as ExpandableToolTip } from './utilities/expandableTootltip';
|
|
|
21
21
|
export { default as ToolTip } from './utilities/tooltip';
|
|
22
22
|
export { default as ModernAutoCompleteDropdown } from './ReactAutoCompleteDropdown';
|
|
23
23
|
export { default as ModernAutoCompleteTableView } from './ReactAutoCompleteTableView';
|
|
24
|
+
export type {
|
|
25
|
+
DropdownThemeConfig,
|
|
26
|
+
DropdownThemeId,
|
|
27
|
+
SplitDetailThemeConfig,
|
|
28
|
+
} from './ReactAutoCompleteTableView';
|
|
@@ -748,6 +748,75 @@ span.dropdown-search-icon {
|
|
|
748
748
|
flex: 0 1 auto;
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
+
/* Split-detail dropdown theme (bank-style row: title + subtitle | badge) */
|
|
752
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-row {
|
|
753
|
+
display: flex;
|
|
754
|
+
align-items: center;
|
|
755
|
+
gap: 12px;
|
|
756
|
+
width: 100%;
|
|
757
|
+
padding: 10px 12px;
|
|
758
|
+
box-sizing: border-box;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-main {
|
|
762
|
+
flex: 1;
|
|
763
|
+
min-width: 0;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-title {
|
|
767
|
+
font-size: 14px;
|
|
768
|
+
font-weight: 600;
|
|
769
|
+
color: #101828;
|
|
770
|
+
line-height: 1.35;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-subtitle {
|
|
774
|
+
margin-top: 4px;
|
|
775
|
+
font-size: 12px;
|
|
776
|
+
font-weight: 400;
|
|
777
|
+
color: #667085;
|
|
778
|
+
line-height: 1.45;
|
|
779
|
+
white-space: normal;
|
|
780
|
+
word-break: break-word;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-badge-wrap {
|
|
784
|
+
flex-shrink: 0;
|
|
785
|
+
align-self: center;
|
|
786
|
+
max-width: 45%;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.autocomplete-suggections--split-detail .qbs-split-detail-badge {
|
|
790
|
+
display: inline-block;
|
|
791
|
+
padding: 6px 10px;
|
|
792
|
+
font-size: 12px;
|
|
793
|
+
font-weight: 600;
|
|
794
|
+
line-height: 1.3;
|
|
795
|
+
color: #194185;
|
|
796
|
+
background: #d1e9ff;
|
|
797
|
+
border: 1px solid #b2ddff;
|
|
798
|
+
border-radius: 6px;
|
|
799
|
+
white-space: normal;
|
|
800
|
+
word-break: break-word;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.autocomplete-suggections--split-detail
|
|
804
|
+
.qbs-autocomplete-suggections-items.bg-blue-navy
|
|
805
|
+
.qbs-split-detail-title,
|
|
806
|
+
.autocomplete-suggections--split-detail
|
|
807
|
+
.qbs-autocomplete-suggections-items.bg-blue-navy
|
|
808
|
+
.qbs-split-detail-subtitle {
|
|
809
|
+
color: #fff;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.autocomplete-suggections--split-detail
|
|
813
|
+
.qbs-autocomplete-suggections-items.bg-blue-navy
|
|
814
|
+
.qbs-split-detail-badge {
|
|
815
|
+
color: #194185;
|
|
816
|
+
background: #eff8ff;
|
|
817
|
+
border-color: #b2ddff;
|
|
818
|
+
}
|
|
819
|
+
|
|
751
820
|
/* TextField Inner Search Button */
|
|
752
821
|
.inner-search-button {
|
|
753
822
|
background: #f8f8f8;
|