react-core-ts 2.0.24 → 2.1.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.
- package/dist/AutoComplete.js +28 -3
- package/dist/AutoComplete.js.map +1 -1
- package/dist/ReactAutoComplete.js +29 -1
- package/dist/ReactAutoComplete.js.map +1 -1
- package/dist/ReactAutoCompleteDropdown.js +29 -1
- package/dist/ReactAutoCompleteDropdown.js.map +1 -1
- package/dist/ReactAutoCompleteSuggections.d.ts +74 -0
- package/dist/ReactAutoCompleteSuggections.js +379 -0
- package/dist/ReactAutoCompleteSuggections.js.map +1 -0
- package/dist/dropdownFilterTabs.js +128 -94
- package/dist/dropdownFilterTabs.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/styles/components/autocomplete.css +744 -0
- package/dist/styles/styles/components/selectedautocomplete.css +221 -0
- package/dist/styles/styles/components/tooltip.css +497 -0
- package/dist/styles/styles/components/treedropdown.css +97 -0
- package/dist/styles/styles/global.css +8 -0
- package/docs/App.tsx +16 -1
- package/package.json +1 -1
- package/src/AutoComplete.tsx +29 -1
- package/src/ReactAutoComplete.tsx +30 -1
- package/src/ReactAutoCompleteDropdown.tsx +30 -1
- package/src/ReactAutoCompleteSuggections.tsx +559 -0
- package/src/dropdownFilterTabs.tsx +73 -35
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
// NOTE: This file was renamed from ReactAutoCompleteNameCount.tsx
|
|
2
|
+
// The implementation is unchanged; only the filename and exported component name differ.
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { FieldErrors } from 'react-hook-form';
|
|
5
|
+
import Spinner from './components/loader/Spinner';
|
|
6
|
+
import Portal from './components/portal';
|
|
7
|
+
import CustomIcons from './components/customIcons';
|
|
8
|
+
|
|
9
|
+
type SuggestionItem = {
|
|
10
|
+
name: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ModernAutoCompleteSuggectionsProps = {
|
|
15
|
+
name: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Controlled value for the input (optional). */
|
|
20
|
+
value?: string;
|
|
21
|
+
/** Called whenever the user types (only the raw text, no selection). */
|
|
22
|
+
onChange?: (value: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Optional static list of items. Each item should have at least `name`.
|
|
25
|
+
*/
|
|
26
|
+
data?: SuggestionItem[];
|
|
27
|
+
/**
|
|
28
|
+
* Async/source function that returns items for a search text.
|
|
29
|
+
*/
|
|
30
|
+
getData?: (key?: string) => Promise<SuggestionItem[]> | SuggestionItem[];
|
|
31
|
+
/**
|
|
32
|
+
* Field that holds an explicit count per item (fallback: duplicates count).
|
|
33
|
+
* Similar to desc, this allows customizing which field contains the count.
|
|
34
|
+
* e.g. "count", "total", "quantity".
|
|
35
|
+
*/
|
|
36
|
+
countField?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the API provides count in the data. If true, uses countField value directly.
|
|
39
|
+
* If false, groups items and counts duplicates.
|
|
40
|
+
*/
|
|
41
|
+
countProvided?: boolean;
|
|
42
|
+
/** Use modern label styling (floating label). */
|
|
43
|
+
isModern?: boolean;
|
|
44
|
+
/** Mark field as required. */
|
|
45
|
+
required?: boolean;
|
|
46
|
+
/** Input size. */
|
|
47
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xxs';
|
|
48
|
+
/** Disable the input. */
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
/** Custom className. */
|
|
51
|
+
className?: string;
|
|
52
|
+
/** Render dropdown inside component container (false = use Portal). */
|
|
53
|
+
insideOpen?: boolean;
|
|
54
|
+
/** Form validation errors. */
|
|
55
|
+
errors?: FieldErrors;
|
|
56
|
+
/** Label title for error messages. */
|
|
57
|
+
labelTitle?: string;
|
|
58
|
+
/** Field name that contains the display text (default: 'name'). */
|
|
59
|
+
desc?: string;
|
|
60
|
+
/** Field name that contains the unique identifier (default: 'id'). */
|
|
61
|
+
descId?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Simple suggestion item:
|
|
66
|
+
* - No tooltip
|
|
67
|
+
* - No truncation
|
|
68
|
+
* - Text wraps to next line when long
|
|
69
|
+
*/
|
|
70
|
+
const SuggestionItem: React.FC<{ name: string; count: number; showCount: boolean }> = ({
|
|
71
|
+
name,
|
|
72
|
+
count,
|
|
73
|
+
showCount,
|
|
74
|
+
}) => {
|
|
75
|
+
return (
|
|
76
|
+
<div className="py-0.5 flex items-center justify-between gap-2 group relative">
|
|
77
|
+
<span className="flex-1 whitespace-normal break-words">
|
|
78
|
+
{name}
|
|
79
|
+
</span>
|
|
80
|
+
{showCount && (
|
|
81
|
+
<span className="ml-2 suggection-count flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-full bg-orange-500 text-white text-[10px] font-medium flex-shrink-0">
|
|
82
|
+
{count}
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read‑only suggestion helper:
|
|
91
|
+
* - User types freely in the input (no item click / selection)
|
|
92
|
+
* - Below the field we show unique names
|
|
93
|
+
* - If a name is duplicated, show: "Name (count)"
|
|
94
|
+
*/
|
|
95
|
+
const ModernAutoCompleteSuggections: React.FC<
|
|
96
|
+
ModernAutoCompleteSuggectionsProps
|
|
97
|
+
> = ({
|
|
98
|
+
name,
|
|
99
|
+
label,
|
|
100
|
+
id,
|
|
101
|
+
placeholder,
|
|
102
|
+
data,
|
|
103
|
+
getData,
|
|
104
|
+
value,
|
|
105
|
+
onChange,
|
|
106
|
+
countField = 'count',
|
|
107
|
+
countProvided = false,
|
|
108
|
+
isModern = true,
|
|
109
|
+
required = false,
|
|
110
|
+
size = 'md',
|
|
111
|
+
disabled = false,
|
|
112
|
+
className: propsClassName,
|
|
113
|
+
insideOpen = false,
|
|
114
|
+
errors,
|
|
115
|
+
labelTitle,
|
|
116
|
+
desc = 'name',
|
|
117
|
+
descId = 'id',
|
|
118
|
+
}) => {
|
|
119
|
+
const [inputValue, setInputValue] = useState(value ?? '');
|
|
120
|
+
const [items, setItems] = useState<SuggestionItem[]>(data ?? []);
|
|
121
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
122
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
123
|
+
const [isHovered, setIsHovered] = useState<boolean>(false);
|
|
124
|
+
const [dropPosition, setDropPosition] = useState<any>({
|
|
125
|
+
top: 0,
|
|
126
|
+
left: 0,
|
|
127
|
+
bottom: 0,
|
|
128
|
+
right: 0,
|
|
129
|
+
});
|
|
130
|
+
const getDataRef = useRef(getData);
|
|
131
|
+
const [hasTyped, setHasTyped] = useState(false);
|
|
132
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
133
|
+
const adorementRef = useRef<HTMLDivElement>(null);
|
|
134
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
135
|
+
|
|
136
|
+
// keep latest getData in a ref so we don't re-trigger effects on every render
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
getDataRef.current = getData;
|
|
139
|
+
}, [getData]);
|
|
140
|
+
|
|
141
|
+
// Keep internal state in sync when used as a controlled component
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (value !== undefined) {
|
|
144
|
+
setInputValue(value);
|
|
145
|
+
}
|
|
146
|
+
}, [value]);
|
|
147
|
+
|
|
148
|
+
// Sync external data prop
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (data) {
|
|
151
|
+
setItems(data);
|
|
152
|
+
}
|
|
153
|
+
}, [data]);
|
|
154
|
+
|
|
155
|
+
// Load from getData only when user types some text (no initial preload)
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
// Do not call API until the user has actually typed at least once
|
|
158
|
+
if (!hasTyped) return;
|
|
159
|
+
|
|
160
|
+
let cancelled = false;
|
|
161
|
+
const query = inputValue.trim();
|
|
162
|
+
if (!getDataRef.current || query === '') {
|
|
163
|
+
// Clear items when input is cleared so dropdown also clears
|
|
164
|
+
if (!cancelled) {
|
|
165
|
+
setItems(data ?? []);
|
|
166
|
+
setIsLoading(false);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const run = async () => {
|
|
172
|
+
setIsLoading(true);
|
|
173
|
+
try {
|
|
174
|
+
const res = await getDataRef.current?.(query);
|
|
175
|
+
if (!cancelled && Array.isArray(res)) {
|
|
176
|
+
setItems(res);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// Optionally log error
|
|
180
|
+
} finally {
|
|
181
|
+
if (!cancelled) {
|
|
182
|
+
setIsLoading(false);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
run();
|
|
188
|
+
|
|
189
|
+
return () => {
|
|
190
|
+
cancelled = true;
|
|
191
|
+
};
|
|
192
|
+
}, [inputValue, hasTyped, data]);
|
|
193
|
+
|
|
194
|
+
// Group by desc field and compute counts
|
|
195
|
+
const grouped = useMemo(() => {
|
|
196
|
+
const map = new Map<string, { name: string; count: number }>();
|
|
197
|
+
|
|
198
|
+
items.forEach((item) => {
|
|
199
|
+
const n = item?.[desc];
|
|
200
|
+
if (!n) return;
|
|
201
|
+
|
|
202
|
+
if (countProvided) {
|
|
203
|
+
// API provides count - use it directly
|
|
204
|
+
const apiCount = item?.[countField];
|
|
205
|
+
const countValue = typeof apiCount === 'number' ? apiCount : typeof apiCount === 'string' ? Number(apiCount) : 0;
|
|
206
|
+
|
|
207
|
+
const existing = map.get(n);
|
|
208
|
+
if (existing) {
|
|
209
|
+
// Sum counts for items with same name
|
|
210
|
+
existing.count += countValue;
|
|
211
|
+
} else {
|
|
212
|
+
map.set(n, { name: n, count: countValue });
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// API doesn't provide count - group and count duplicates
|
|
216
|
+
const existing = map.get(n);
|
|
217
|
+
if (existing) {
|
|
218
|
+
existing.count += 1;
|
|
219
|
+
} else {
|
|
220
|
+
map.set(n, { name: n, count: 0 });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return Array.from(map.values());
|
|
226
|
+
}, [items, countField, desc, countProvided]);
|
|
227
|
+
|
|
228
|
+
// Filter based on what user typed
|
|
229
|
+
const visible = useMemo(() => {
|
|
230
|
+
const term = inputValue.trim().toLowerCase();
|
|
231
|
+
if (!term || !hasTyped) return [];
|
|
232
|
+
return grouped.filter((g) => g.name.toLowerCase().includes(term));
|
|
233
|
+
}, [grouped, inputValue, hasTyped]);
|
|
234
|
+
|
|
235
|
+
// Update dropdown visibility based on visible items
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
setShowDropdown(visible.length > 0);
|
|
238
|
+
}, [visible.length]);
|
|
239
|
+
|
|
240
|
+
// Handle click outside to close dropdown
|
|
241
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
242
|
+
if (
|
|
243
|
+
containerRef.current &&
|
|
244
|
+
!containerRef.current.contains(event.target as Node)
|
|
245
|
+
) {
|
|
246
|
+
setShowDropdown(false);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Handle scroll to close dropdown
|
|
251
|
+
const handleScroll = () => {
|
|
252
|
+
setShowDropdown(false);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Attach event listeners for click outside and scroll
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (showDropdown) {
|
|
258
|
+
document.addEventListener('click', handleClickOutside);
|
|
259
|
+
window.addEventListener('scroll', handleScroll);
|
|
260
|
+
|
|
261
|
+
const mainElement = document.querySelector('main');
|
|
262
|
+
mainElement?.addEventListener('scroll', handleScroll);
|
|
263
|
+
|
|
264
|
+
const gridElements = document.querySelectorAll(
|
|
265
|
+
'.k-grid-content, .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
266
|
+
);
|
|
267
|
+
gridElements.forEach((gridElement: any) => {
|
|
268
|
+
gridElement.addEventListener('scroll', handleScroll);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return () => {
|
|
272
|
+
document.removeEventListener('click', handleClickOutside);
|
|
273
|
+
window.removeEventListener('scroll', handleScroll);
|
|
274
|
+
mainElement?.removeEventListener('scroll', handleScroll);
|
|
275
|
+
gridElements.forEach((gridElement: any) => {
|
|
276
|
+
gridElement.removeEventListener('scroll', handleScroll);
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}, [showDropdown]);
|
|
281
|
+
|
|
282
|
+
const checkIsEmptyField = (): boolean => {
|
|
283
|
+
if (inputValue === undefined) return true;
|
|
284
|
+
else return (inputValue?.toString().length ?? 0) <= 0 ? true : false;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const getHeight = () => {
|
|
288
|
+
switch (size) {
|
|
289
|
+
case 'sm':
|
|
290
|
+
return ' h-[34px] ';
|
|
291
|
+
default:
|
|
292
|
+
return ' ';
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const getErrors = (err: any) => {
|
|
297
|
+
let errMsg = '';
|
|
298
|
+
if (err.message) {
|
|
299
|
+
errMsg = err?.message;
|
|
300
|
+
} else if (err?.id?.message) {
|
|
301
|
+
errMsg = err?.id?.message;
|
|
302
|
+
} else if (err?.name?.message) {
|
|
303
|
+
errMsg = err?.name?.message;
|
|
304
|
+
}
|
|
305
|
+
return errMsg;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const handleError = (data: any) => {
|
|
309
|
+
if (
|
|
310
|
+
getErrors(data[name]) === 'required' ||
|
|
311
|
+
getErrors(data[name]) === 'Required'
|
|
312
|
+
) {
|
|
313
|
+
return `${label ?? labelTitle} is ${getErrors(data[name])}`;
|
|
314
|
+
} else {
|
|
315
|
+
return getErrors(data[name]) ?? '';
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const generateClassName = (
|
|
320
|
+
type: 'input' | 'label' | 'message' | 'adorement'
|
|
321
|
+
): string => {
|
|
322
|
+
let className = propsClassName || '';
|
|
323
|
+
switch (type) {
|
|
324
|
+
case 'input':
|
|
325
|
+
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 ${
|
|
326
|
+
label && isModern
|
|
327
|
+
? 'placeholder-transparent'
|
|
328
|
+
: 'focus:placeholder-grey-secondary placeholder-input-label'
|
|
329
|
+
} focus:placeholder-grey-secondary`;
|
|
330
|
+
|
|
331
|
+
if (errors && errors[name]) {
|
|
332
|
+
className +=
|
|
333
|
+
' border-[#FDA29B] focus:border-error-[#FDA29B] focus:ring-[#FDA29B] focus:ring-3 focus:outline-[#FDA29B] input-outline';
|
|
334
|
+
} else {
|
|
335
|
+
className +=
|
|
336
|
+
' text-grey-dark border-input-light focus:border-blue-navy focus:outline-none focus:ring-0';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
className += getHeight();
|
|
340
|
+
|
|
341
|
+
break;
|
|
342
|
+
case 'label':
|
|
343
|
+
className += ` flex modern-input-label-truncate peer-focus:modern-input-peer-focus-label-size
|
|
344
|
+
${
|
|
345
|
+
disabled
|
|
346
|
+
? 'cursor-pointer'
|
|
347
|
+
: 'cursor-text peer-focus:cursor-pointer'
|
|
348
|
+
} ${
|
|
349
|
+
disabled && !checkIsEmptyField()
|
|
350
|
+
? 'disabled-input-label-bg'
|
|
351
|
+
: !disabled || !checkIsEmptyField()
|
|
352
|
+
? 'active-input-label-bg'
|
|
353
|
+
: ''
|
|
354
|
+
} 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 ${
|
|
355
|
+
disabled
|
|
356
|
+
? 'cursor-pointer'
|
|
357
|
+
: 'cursor-text peer-focus:cursor-pointer'
|
|
358
|
+
}
|
|
359
|
+
${
|
|
360
|
+
checkIsEmptyField()
|
|
361
|
+
? 'modern-input-label-size'
|
|
362
|
+
: 'modern-input-peer-focus-label-size'
|
|
363
|
+
}`;
|
|
364
|
+
if (errors && errors[name]) {
|
|
365
|
+
className += ' text-error-light ';
|
|
366
|
+
} else {
|
|
367
|
+
className += ' text-grey-dark peer-focus:text-blue-navy';
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
case 'message':
|
|
371
|
+
className = ' text-error-icon ';
|
|
372
|
+
break;
|
|
373
|
+
case 'adorement':
|
|
374
|
+
className += ' absolute right-0 adorement gap-1 flex items-center ';
|
|
375
|
+
break;
|
|
376
|
+
default:
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
return className;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const getInnerWidth = () => {
|
|
383
|
+
const innerwidth = adorementRef.current
|
|
384
|
+
? adorementRef.current.offsetWidth
|
|
385
|
+
: 0;
|
|
386
|
+
return innerwidth;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const onLabelClick = () => {
|
|
390
|
+
if (!disabled) {
|
|
391
|
+
inputRef?.current?.focus();
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const getDropPosition = (): any => {
|
|
396
|
+
if (!insideOpen && inputRef?.current) {
|
|
397
|
+
const dropdownHeight = 200; // Assume the height of the dropdown is 200px, adjust as needed
|
|
398
|
+
|
|
399
|
+
const inputRect = inputRef.current.getBoundingClientRect();
|
|
400
|
+
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
401
|
+
const spaceAbove = inputRect.top;
|
|
402
|
+
|
|
403
|
+
let top: number | undefined;
|
|
404
|
+
let bottom: number | undefined;
|
|
405
|
+
if (spaceBelow < dropdownHeight && spaceAbove > dropdownHeight) {
|
|
406
|
+
// Show above if not enough space below
|
|
407
|
+
bottom = window.innerHeight - inputRect.top + 2;
|
|
408
|
+
} else {
|
|
409
|
+
// Show below (default)
|
|
410
|
+
top = inputRect.bottom + 2;
|
|
411
|
+
}
|
|
412
|
+
setDropPosition({
|
|
413
|
+
position: 'fixed',
|
|
414
|
+
left: inputRect.left,
|
|
415
|
+
top,
|
|
416
|
+
bottom,
|
|
417
|
+
width: inputRect.width,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return undefined;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Calculate dropdown position when it opens
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
if (showDropdown && !insideOpen) {
|
|
427
|
+
window.addEventListener('resize', getDropPosition);
|
|
428
|
+
getDropPosition();
|
|
429
|
+
return () => {
|
|
430
|
+
window.removeEventListener('resize', getDropPosition);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}, [showDropdown, insideOpen]);
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<div
|
|
437
|
+
ref={containerRef}
|
|
438
|
+
className={`flex flex-col gap-1 autocomplete-with-suggection ${propsClassName || ''}`}
|
|
439
|
+
>
|
|
440
|
+
{label && !isModern && (
|
|
441
|
+
<div className="mb-3">
|
|
442
|
+
<label className={`text-xs font-medium`}>
|
|
443
|
+
{label}
|
|
444
|
+
{required ? <span className="text-error"> *</span> : <></>}
|
|
445
|
+
</label>
|
|
446
|
+
</div>
|
|
447
|
+
)}
|
|
448
|
+
<div className="tooltip-container">
|
|
449
|
+
{isHovered && errors && errors[name] && (
|
|
450
|
+
<span className="tooltip">{handleError(errors)} </span>
|
|
451
|
+
)}
|
|
452
|
+
<div className="flex relative w-full">
|
|
453
|
+
<div
|
|
454
|
+
className="relative w-full"
|
|
455
|
+
onMouseEnter={() => {}}
|
|
456
|
+
onMouseLeave={() => {}}
|
|
457
|
+
>
|
|
458
|
+
<input
|
|
459
|
+
type="text"
|
|
460
|
+
id={
|
|
461
|
+
id ? `custom-autocomplete-${id}` : `custom-autocomplete-${name}`
|
|
462
|
+
}
|
|
463
|
+
name={name}
|
|
464
|
+
value={inputValue ? inputValue : ''}
|
|
465
|
+
ref={inputRef}
|
|
466
|
+
disabled={disabled}
|
|
467
|
+
onChange={(e) => {
|
|
468
|
+
const val = e.target.value;
|
|
469
|
+
setInputValue(val);
|
|
470
|
+
setHasTyped(true);
|
|
471
|
+
onChange?.(val);
|
|
472
|
+
}}
|
|
473
|
+
onBlur={() => {}}
|
|
474
|
+
placeholder={placeholder}
|
|
475
|
+
autoComplete="off"
|
|
476
|
+
data-testid="custom-autocomplete-suggestions"
|
|
477
|
+
aria-describedby={id}
|
|
478
|
+
style={{
|
|
479
|
+
paddingRight: getInnerWidth(),
|
|
480
|
+
}}
|
|
481
|
+
className={` ${generateClassName('input')}`}
|
|
482
|
+
/>
|
|
483
|
+
{isModern && (
|
|
484
|
+
<label
|
|
485
|
+
htmlFor={id ?? name}
|
|
486
|
+
onClick={() => onLabelClick()}
|
|
487
|
+
className={generateClassName('label')}
|
|
488
|
+
>
|
|
489
|
+
{label ? <span className="truncate">{label}</span> : ''}
|
|
490
|
+
{required ? <span className="text-error"> *</span> : <></>}
|
|
491
|
+
</label>
|
|
492
|
+
)}
|
|
493
|
+
</div>
|
|
494
|
+
<div className="flex items-center justify-center">
|
|
495
|
+
<div
|
|
496
|
+
ref={adorementRef}
|
|
497
|
+
className={`${generateClassName('adorement')} qbs-autocomplete-adorement custom-dorpdown-adorement mr-[1px] ${
|
|
498
|
+
isLoading ? 'bg-white' : ''
|
|
499
|
+
}`}
|
|
500
|
+
>
|
|
501
|
+
{isLoading && <Spinner />}
|
|
502
|
+
{errors && errors[name] && (
|
|
503
|
+
<div
|
|
504
|
+
className={` text-error-label relative cursor-pointer ${generateClassName(
|
|
505
|
+
'message'
|
|
506
|
+
)}`}
|
|
507
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
508
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
509
|
+
>
|
|
510
|
+
<CustomIcons name="alert" type="medium" />
|
|
511
|
+
</div>
|
|
512
|
+
)}
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
{showDropdown && visible.length > 0 && (
|
|
518
|
+
<>
|
|
519
|
+
{insideOpen ? (
|
|
520
|
+
<div className="suggection-list">
|
|
521
|
+
<div className="bg-white border border-grey-light shadow-gray-300 shadow-md rounded-sm max-h-40 overflow-auto text-[11px] text-gray-700 px-3.5 py-1.5">
|
|
522
|
+
{visible.map((g) => (
|
|
523
|
+
<SuggestionItem
|
|
524
|
+
key={g.name}
|
|
525
|
+
name={g.name}
|
|
526
|
+
count={g.count}
|
|
527
|
+
showCount={g.count > 0}
|
|
528
|
+
/>
|
|
529
|
+
))}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
) : (
|
|
533
|
+
<Portal>
|
|
534
|
+
<div
|
|
535
|
+
className="autocomplete-suggections suggection-list bg-white shadow-gray-300 shadow-md border border-grey-light z-50"
|
|
536
|
+
style={dropPosition}
|
|
537
|
+
>
|
|
538
|
+
<div className="h-auto max-h-40 overflow-auto w-full py-1.5">
|
|
539
|
+
{visible.map((g) => (
|
|
540
|
+
<div key={g.name} className="px-3.5 text-[11px] text-gray-700">
|
|
541
|
+
<SuggestionItem
|
|
542
|
+
name={g.name}
|
|
543
|
+
count={g.count}
|
|
544
|
+
showCount={g.count > -1}
|
|
545
|
+
/>
|
|
546
|
+
</div>
|
|
547
|
+
))}
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
</Portal>
|
|
551
|
+
)}
|
|
552
|
+
</>
|
|
553
|
+
)}
|
|
554
|
+
</div>
|
|
555
|
+
);
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export default ModernAutoCompleteSuggections;
|
|
559
|
+
|