@addsign/moje-agenda-shared-lib 1.0.61 → 2.0.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.
Files changed (73) hide show
  1. package/dist/assets/style.css +4369 -0
  2. package/dist/assets/tailwind.css +1249 -1231
  3. package/dist/components/Button.js +1 -1
  4. package/dist/components/datatable/DataTable.js +1 -1
  5. package/dist/components/datatable/DataTableServer.js +1 -1
  6. package/dist/components/form/AutocompleteSearchBar.js +1 -1
  7. package/dist/components/form/AutocompleteSearchBarServer.js +1 -1
  8. package/dist/components/form/FileInput.js +1 -1
  9. package/dist/components/form/FileInputMultiple.js +1 -1
  10. package/dist/components/form/FormField.js +1 -1
  11. package/dist/components/form/PositionsSelectorSingle.js +1 -1
  12. package/dist/components/form/SelectField.js +1 -1
  13. package/dist/components/profiles/ProfileOverview.js +1 -1
  14. package/dist/main.js +1 -1
  15. package/dist/tailwind-l0sNRNKZ.js +2 -0
  16. package/dist/tailwind-l0sNRNKZ.js.map +1 -0
  17. package/lib/components/Button.tsx +57 -0
  18. package/lib/components/Calendar.tsx +242 -0
  19. package/lib/components/ConfirmationModalDialog.tsx +115 -0
  20. package/lib/components/Modal.tsx +73 -0
  21. package/lib/components/ModalDialog.tsx +63 -0
  22. package/lib/components/Spinner.tsx +25 -0
  23. package/lib/components/SpinnerIcon.tsx +12 -0
  24. package/lib/components/datatable/DataTable.tsx +442 -0
  25. package/lib/components/datatable/DataTableServer.tsx +939 -0
  26. package/lib/components/datatable/DatatableSettings.tsx +48 -0
  27. package/lib/components/datatable/Resizable.tsx +99 -0
  28. package/lib/components/datatable/types.ts +33 -0
  29. package/lib/components/form/AutocompleteSearchBar.tsx +424 -0
  30. package/lib/components/form/AutocompleteSearchBarServer.tsx +257 -0
  31. package/lib/components/form/DateField.tsx +124 -0
  32. package/lib/components/form/DateRangeField.tsx +116 -0
  33. package/lib/components/form/FileInput.tsx +188 -0
  34. package/lib/components/form/FileInputMultiple.tsx +186 -0
  35. package/lib/components/form/FormField.tsx +371 -0
  36. package/lib/components/form/InputField.tsx +230 -0
  37. package/lib/components/form/PositionsSelectorSingle.tsx +266 -0
  38. package/lib/components/form/RadioGroup.tsx +64 -0
  39. package/lib/components/form/SelectField.tsx +267 -0
  40. package/lib/components/layout/IconInCircle.tsx +29 -0
  41. package/lib/components/layout/PageTitle.tsx +19 -0
  42. package/lib/components/layout/SectionTitle.tsx +22 -0
  43. package/lib/components/profiles/ProfileOverview.tsx +212 -0
  44. package/lib/components/ui/Calendar.tsx +68 -0
  45. package/lib/components/ui/Combobox.tsx +122 -0
  46. package/lib/components/ui/DatePicker.tsx +124 -0
  47. package/lib/components/ui/DateTimePicker.tsx +187 -0
  48. package/lib/components/ui/Dialog.tsx +118 -0
  49. package/lib/components/ui/ScrollArea.tsx +45 -0
  50. package/lib/components/ui/button.tsx +56 -0
  51. package/lib/components/ui/command.tsx +153 -0
  52. package/lib/components/ui/form.tsx +177 -0
  53. package/lib/components/ui/input.tsx +22 -0
  54. package/lib/components/ui/label.tsx +24 -0
  55. package/lib/components/ui/popover.tsx +31 -0
  56. package/lib/components/ui/radioGroup.tsx +44 -0
  57. package/lib/components/ui/select.tsx +158 -0
  58. package/lib/contexts/FederationContext.tsx +28 -0
  59. package/lib/contexts/useFederationContext.ts +4 -0
  60. package/lib/css/tailwind.css +10 -0
  61. package/lib/fonts/arial.ts +3 -0
  62. package/lib/fonts/arialBold.ts +4 -0
  63. package/lib/main.ts +64 -0
  64. package/lib/types.ts +492 -0
  65. package/lib/utils/PdfManager.ts +224 -0
  66. package/lib/utils/getFullName.tsx +83 -0
  67. package/lib/utils/getIntersectingDays.ts +28 -0
  68. package/lib/utils/handleErrors.ts +28 -0
  69. package/lib/utils/hasRightInModule.ts +17 -0
  70. package/lib/utils/hasRole.ts +12 -0
  71. package/lib/utils/utils.ts +6 -0
  72. package/lib/vite-env.d.ts +1 -0
  73. package/package.json +3 -2
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { MdRefresh } from "react-icons/md";
3
+ import { PiFileXlsLight } from "react-icons/pi";
4
+ import { TiArrowBackOutline } from "react-icons/ti";
5
+
6
+ export interface IDatatableSettingsProps {
7
+ tableId: string; // id tabulky
8
+ onSuccess: () => void;
9
+ onExport: () => void;
10
+ }
11
+
12
+ export function DatatableSettings({
13
+ tableId,
14
+ onSuccess,
15
+ onExport,
16
+ }: IDatatableSettingsProps) {
17
+ const refreshTable = React.useCallback(() => {
18
+ const storageKey = `datatable:${tableId}`;
19
+ localStorage.setItem(storageKey, "{}");
20
+ onSuccess();
21
+ }, [tableId]);
22
+
23
+ return (
24
+ <div className="flex items-center gap-5 py-3">
25
+ <div
26
+ className=" text-xl h-full cursor-pointer text-gray-600 hover:text-black"
27
+ title="Obnovit Data"
28
+ onClick={onSuccess}
29
+ >
30
+ <MdRefresh />
31
+ </div>
32
+ <div
33
+ className=" text-xl h-full cursor-pointer text-gray-600 hover:text-black"
34
+ title="Obnovit výchozí nastavení tabulky"
35
+ onClick={() => refreshTable()}
36
+ >
37
+ <TiArrowBackOutline />
38
+ </div>
39
+ <div
40
+ className=" text-xl h-full cursor-pointer text-gray-600 hover:text-black"
41
+ title="Stáhnout jako XLS soubor"
42
+ onClick={onExport}
43
+ >
44
+ <PiFileXlsLight />
45
+ </div>{" "}
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,99 @@
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ interface IResizableProps {
4
+ tableId: string;
5
+ colKey: string;
6
+ children: any;
7
+ defaultWidth: string;
8
+ }
9
+
10
+ export const Resizable = ({
11
+ tableId,
12
+ colKey,
13
+ defaultWidth,
14
+ children,
15
+ }: IResizableProps) => {
16
+ const [node, setNode] = React.useState<HTMLElement | null>(null);
17
+
18
+ const [width, setWidth] = useState(() => {
19
+ const storageKey = `datatable:${tableId}`;
20
+ const storedData = localStorage.getItem(storageKey);
21
+ const data = storedData ? JSON.parse(storedData) : {};
22
+
23
+ const columnWidths = data.columnWidths || {};
24
+ const storedWidth = columnWidths[colKey];
25
+ return storedWidth ? storedWidth : defaultWidth;
26
+ });
27
+
28
+ //storing sizes
29
+ useEffect(() => {
30
+ const storageKey = `datatable:${tableId}`;
31
+ const storedData = localStorage.getItem(storageKey);
32
+ const data = storedData ? JSON.parse(storedData) : {};
33
+
34
+ data.columnWidths = data.columnWidths || {};
35
+ data.columnWidths[colKey] = width;
36
+
37
+ localStorage.setItem(storageKey, JSON.stringify(data));
38
+ }, [width, tableId, colKey, defaultWidth]);
39
+
40
+ const ref = React.useCallback((nodeEle: HTMLElement) => {
41
+ setNode(nodeEle);
42
+ }, []);
43
+
44
+ const handleMouseDown = React.useCallback(
45
+ (e: MouseEvent) => {
46
+ if (!node) {
47
+ return;
48
+ }
49
+
50
+ const parent = node.parentElement;
51
+ const startPos = {
52
+ x: e.clientX,
53
+ y: e.clientY,
54
+ };
55
+ // const styles = window.getComputedStyle(parent);
56
+ const styles = parent ? window.getComputedStyle(parent) : null;
57
+
58
+ const w = parseInt(styles?.width || "0", 10);
59
+
60
+ const handleMouseMove = (e: MouseEvent) => {
61
+ const dx = e.clientX - startPos.x;
62
+ const newWidth = w + dx + "px";
63
+
64
+ parent?.style.setProperty("width", newWidth);
65
+ setWidth(newWidth);
66
+ };
67
+
68
+ const handleMouseUp = () => {
69
+ document.removeEventListener("mousemove", handleMouseMove);
70
+ document.removeEventListener("mouseup", handleMouseUp);
71
+ };
72
+
73
+ document.addEventListener("mousemove", handleMouseMove);
74
+ document.addEventListener("mouseup", handleMouseUp);
75
+ },
76
+ [node]
77
+ );
78
+
79
+ React.useEffect(() => {
80
+ if (!node) {
81
+ return;
82
+ }
83
+
84
+ const parent = node.parentElement;
85
+ if (node.parentNode !== node.parentNode?.parentNode?.lastChild) {
86
+ parent?.style.setProperty("width", `${width}`);
87
+ } else {
88
+ parent?.style.setProperty("width", `auto`);
89
+ }
90
+
91
+ node.addEventListener("mousedown", handleMouseDown);
92
+
93
+ return () => {
94
+ node.removeEventListener("mousedown", handleMouseDown);
95
+ };
96
+ }, [node, handleMouseDown, tableId]);
97
+
98
+ return children({ ref });
99
+ };
@@ -0,0 +1,33 @@
1
+ import { IOptionItem } from "../../types";
2
+
3
+ export interface DataTableColumn<T> {
4
+ key: keyof T | "actions";
5
+ header: string;
6
+ render?: (item: T) => React.ReactNode;
7
+ actions?: DataTableAction<T>[];
8
+ filterType?: string;
9
+ classes?: string;
10
+ type?: "string" | "number" | "date";
11
+ filterOperator?: ">=" | "<=" | "==";
12
+ filterSource?: string;
13
+ filterValueKey?: string;
14
+ filterLabelKey?: string;
15
+ filterParam?: string;
16
+ filterParam2?: string;
17
+ filterOptions?: IOptionItem[];
18
+ sortParam?: string;
19
+ sumarize?: boolean;
20
+ width?: string;
21
+ }
22
+ export interface DataTableAction<T> {
23
+ label: string;
24
+ rowAction?: boolean,
25
+ onClick: (item: T) => void;
26
+ visible?: (item: T) => boolean;
27
+ icon?: React.ReactNode;
28
+ }
29
+
30
+ export type DataTableInternalItems = {
31
+ _isHighlighted?: boolean;
32
+ };
33
+
@@ -0,0 +1,424 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ import { useClickAway } from "react-use";
3
+ import { IOptionItem, useFederationContext } from "../../main";
4
+ import { MdClose, MdExpandLess, MdExpandMore } from "react-icons/md";
5
+
6
+ interface OptionListProps {
7
+ options: IOptionItem[];
8
+ selectedOptionIndex: number;
9
+ handleOptionClick: (option: IOptionItem) => void;
10
+ label?: string;
11
+ }
12
+
13
+ interface SearchInputProps {
14
+ value: string;
15
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
16
+ onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
17
+ onFocus: () => void;
18
+ clearQuery: () => void;
19
+ inputRef: React.RefObject<HTMLInputElement>;
20
+ placeholder?: string;
21
+ isFocused?: boolean;
22
+ onBlur: () => void;
23
+ isLoading?: boolean;
24
+ clearable?: boolean;
25
+ }
26
+
27
+ const AutocompleteSearchBar: React.FC<{
28
+ label: string;
29
+ name: string;
30
+ required?: boolean;
31
+ disabled?: boolean;
32
+ description?: string;
33
+ fetchUrl?: string;
34
+ valueKey: string;
35
+ labelKey: string;
36
+ onChange: (value: any) => void;
37
+ onFocus: () => void;
38
+ onBlur: () => void;
39
+ placeholder?: string;
40
+ serverSearch?: boolean;
41
+ value?: string;
42
+ initOptions?: IOptionItem[];
43
+ showId?: boolean;
44
+ clearable?: boolean;
45
+ rounded?: boolean;
46
+ }> = ({
47
+ label,
48
+ name,
49
+ required,
50
+ disabled,
51
+ description,
52
+ fetchUrl,
53
+ valueKey,
54
+ labelKey,
55
+ onChange,
56
+ onFocus,
57
+ onBlur,
58
+ placeholder,
59
+ serverSearch = false,
60
+ value,
61
+ initOptions,
62
+ showId,
63
+ clearable = true,
64
+ rounded = true,
65
+ }) => {
66
+ const [query, setQuery] = useState("");
67
+ const [loading, setLoading] = useState(false);
68
+ const [options, setOptions] = useState<IOptionItem[]>([]);
69
+ const [selectedOptionIndex, setSelectedOptionIndex] = useState(-1);
70
+ const [selectedOption, setSelectedOption] = useState<IOptionItem | null>(
71
+ null
72
+ );
73
+ const [searchResults, setSearchResults] = useState<IOptionItem[]>([]);
74
+ const inputRef = useRef<HTMLInputElement>(null);
75
+ const ref = useRef(null);
76
+ const federationContext = useFederationContext();
77
+
78
+ const [isFocused, setIsFocused] = useState(false);
79
+
80
+ const [errors] = useState<any>({});
81
+
82
+ useEffect(() => {
83
+ const fetchOptions = async () => {
84
+ setLoading(true);
85
+
86
+ const { data } = await federationContext.apiClient.get(fetchUrl || "");
87
+ // osetreni obou variant navratu z BE
88
+ const tmpData = data.content || data;
89
+
90
+ setOptions(
91
+ tmpData.map((item: any) => ({
92
+ value: item[valueKey],
93
+ label: item[labelKey] + (showId ? " (" + item[valueKey] + ")" : ""),
94
+ }))
95
+ ); // Adjust map according to actual API response
96
+ setLoading(false);
97
+ };
98
+
99
+ if (fetchUrl) fetchOptions();
100
+ }, [fetchUrl, labelKey, showId, valueKey, federationContext.apiClient]);
101
+
102
+ useEffect(() => {
103
+ if (initOptions) setOptions(initOptions);
104
+ }, [initOptions]);
105
+
106
+ useEffect(() => {
107
+ if (value && options) {
108
+ const initOption = options.find((it) => it.value == value);
109
+
110
+ if (initOption) {
111
+ setQuery(initOption.label);
112
+ setSelectedOption(initOption);
113
+ }
114
+ }
115
+ }, [value, options]);
116
+
117
+ useEffect(() => {
118
+ if (!value) {
119
+ setQuery("");
120
+ setSelectedOption(null);
121
+ }
122
+ }, [value]);
123
+
124
+ useEffect(() => {
125
+ const fetchOptions = async () => {
126
+ setLoading(true);
127
+ const { data } = await federationContext.apiClient.get(fetchUrl + query);
128
+ setOptions(
129
+ data.map((item: any) => ({
130
+ value: item[valueKey],
131
+ label: item[labelKey] + (showId ? " (" + item[valueKey] + ")" : ""),
132
+ }))
133
+ ); // Adjust map according to actual API response
134
+ setSearchResults(
135
+ data.map((item: any) => ({
136
+ value: item[valueKey],
137
+ label: item[labelKey] + (showId ? " (" + item[valueKey] + ")" : ""),
138
+ }))
139
+ ); // Adjust map according to actual API response
140
+
141
+ setLoading(false);
142
+ };
143
+
144
+ if (serverSearch) fetchOptions();
145
+ }, [
146
+ query,
147
+ serverSearch,
148
+ valueKey,
149
+ labelKey,
150
+ fetchUrl,
151
+ federationContext.apiClient,
152
+ showId,
153
+ ]);
154
+
155
+ useClickAway(ref, () => {
156
+ if (isFocused) {
157
+ setSearchResults([]);
158
+ if (!selectedOption) {
159
+ setQuery("");
160
+ onChange(null);
161
+ }
162
+
163
+ handleBlur();
164
+ onBlur();
165
+ setIsFocused(false);
166
+ }
167
+ });
168
+
169
+ const handleQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
170
+ setQuery(event.target.value);
171
+ setSelectedOption(null);
172
+ setSelectedOptionIndex(-1);
173
+ setSearchResults(
174
+ options.filter((option) =>
175
+ option?.label
176
+ ?.toLowerCase()
177
+ .includes(event.target?.value?.toLowerCase())
178
+ )
179
+ );
180
+ };
181
+ const handleFocus = () => {
182
+ setIsFocused(true);
183
+ onFocus();
184
+ setSearchResults(
185
+ options /*.filter((option) =>
186
+ option.label.toLowerCase().includes(query.toLowerCase()),
187
+ ),*/
188
+ );
189
+ inputRef.current?.select(); // Add this line
190
+ };
191
+
192
+ const handleClearQuery = () => {
193
+ setQuery("");
194
+ onChange(null);
195
+ setSelectedOption(null);
196
+ setTimeout(() => {
197
+ setSelectedOptionIndex(-1);
198
+ setSearchResults(options);
199
+ }, 1000);
200
+ };
201
+
202
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
203
+ if (event.key === "ArrowUp" && searchResults.length > 0) {
204
+ setSelectedOptionIndex((prevIndex) =>
205
+ prevIndex > 0 ? prevIndex - 1 : searchResults.length - 1
206
+ );
207
+ } else if (event.key === "ArrowDown" && searchResults.length > 0) {
208
+ setSelectedOptionIndex((prevIndex) =>
209
+ prevIndex < searchResults.length - 1 ? prevIndex + 1 : 0
210
+ );
211
+ } else if (event.key === "Enter" && selectedOptionIndex !== -1) {
212
+ handleOptionClick(searchResults[selectedOptionIndex]);
213
+ } else if (event.key === "Escape" && selectedOptionIndex !== -1) {
214
+ handleBlur();
215
+ }
216
+ };
217
+
218
+ const handleOptionClick = (option: IOptionItem) => {
219
+ setQuery(option.label);
220
+ setSelectedOption(option);
221
+ setSelectedOptionIndex(-1);
222
+
223
+ onChange(option.value);
224
+
225
+ handleBlur();
226
+ };
227
+ const handleBlur = () => {
228
+ setSearchResults([]);
229
+ onBlur();
230
+ setIsFocused(false);
231
+ };
232
+
233
+ return (
234
+ <div className="w-full flex-col justify-start items-start gap-1.5 inline-flex">
235
+ <div
236
+ className="self-stretch flex-col justify-start items-start gap-1.5 flex relative"
237
+ ref={ref}
238
+ >
239
+ {label && (
240
+ <label
241
+ className="text-slate-700 text-sm leading-tight font-medium"
242
+ htmlFor={name}
243
+ >
244
+ {label} {required ? "*" : ""}
245
+ </label>
246
+ )}
247
+ <div
248
+ className={`self-stretch px-3 py-1.5 justify-start items-center gap-2 inline-flex outline-none border bg-white
249
+ ${
250
+ isFocused
251
+ ? "outline-4 outline-indigo-200 outline-offset-0 border-indigo-300"
252
+ : ""
253
+ }
254
+ ${
255
+ isFocused && errors[name]?.message
256
+ ? "outline-4 outline-red-200 outline-offset-0 border-none"
257
+ : ""
258
+ }
259
+ ${!isFocused && errors[name]?.message ? "border-red-200" : ""}
260
+ ${disabled ? "bg-gray-100" : "bg-transparent"}
261
+ ${rounded ? " rounded-lg " : " rounded-none "}`}
262
+ >
263
+ <div className="Content grow shrink basis-0 min-h-5 lg:min-h-7 justify-start items-start gap-2 flex relative flex-col w-full mx-auto">
264
+ <SearchInput
265
+ value={query}
266
+ onChange={handleQueryChange}
267
+ onKeyDown={handleKeyDown}
268
+ onFocus={handleFocus} // Pass the onFocus handler
269
+ clearQuery={handleClearQuery} // Pass the onFocus handler
270
+ inputRef={inputRef}
271
+ placeholder={placeholder}
272
+ isFocused={isFocused}
273
+ onBlur={handleBlur}
274
+ isLoading={loading}
275
+ clearable={clearable}
276
+ />
277
+ </div>
278
+ </div>
279
+ {searchResults.length > 0 && isFocused && (
280
+ <OptionList
281
+ options={searchResults}
282
+ selectedOptionIndex={selectedOptionIndex}
283
+ handleOptionClick={handleOptionClick}
284
+ label={label}
285
+ />
286
+ )}
287
+ </div>
288
+ {description && (
289
+ <div className="HintText self-stretch text-slate-600 text-sm font-normal leading-tight">
290
+ {description}
291
+ </div>
292
+ )}
293
+ </div>
294
+ );
295
+ };
296
+
297
+ const OptionList: React.FC<OptionListProps> = ({
298
+ options,
299
+ selectedOptionIndex,
300
+ handleOptionClick,
301
+ label,
302
+ }) => {
303
+ const containerRef = useRef<HTMLDivElement>(null);
304
+
305
+ useEffect(() => {
306
+ // Ensure the selected option is scrolled into view
307
+ if (selectedOptionIndex >= 0 && selectedOptionIndex < options.length) {
308
+ const selectedElement = containerRef.current?.children[
309
+ selectedOptionIndex
310
+ ] as HTMLDivElement;
311
+ selectedElement?.scrollIntoView({
312
+ behavior: "instant",
313
+ block: "nearest",
314
+ });
315
+ }
316
+ }, [selectedOptionIndex, options.length]);
317
+
318
+ return (
319
+ <div
320
+ ref={containerRef}
321
+ className={`overflow-y-auto resultOptionContainer w-full max-h-96 outline-indigo-200 absolute outline-4 bg-white border border-gray-200 drop-shadow-xl z-50 ${label ? "top-[68px]" : "top-[40px]"} *:
322
+
323
+ `}
324
+ >
325
+ {options?.map((option, index) => (
326
+ <div
327
+ key={option.value as string}
328
+ className={`py-2 px-4 flex items-center justify-between gap-8 hover:bg-gray-200 cursor-pointer text-sm ${
329
+ selectedOptionIndex === index ? "bg-gray-200" : ""
330
+ }`}
331
+ onClick={() => handleOptionClick(option)}
332
+ >
333
+ <p className="font-normal">{option.label}</p>
334
+ </div>
335
+ ))}
336
+ </div>
337
+ );
338
+ };
339
+
340
+ const SearchInput: React.FC<SearchInputProps> = ({
341
+ value,
342
+ onChange,
343
+ onKeyDown,
344
+ onFocus,
345
+ onBlur,
346
+ inputRef,
347
+ placeholder,
348
+ clearQuery,
349
+ isFocused,
350
+ isLoading,
351
+ clearable = true,
352
+ }) => {
353
+ const handleExpandMoreClick = () => {
354
+ inputRef.current?.focus();
355
+ onFocus();
356
+ };
357
+
358
+ const handleClearQuery = () => {
359
+ clearQuery();
360
+ };
361
+
362
+ return (
363
+ <div className="relative flex items-center w-full sharedResource text-sm bg-white">
364
+ <input
365
+ type="text"
366
+ className={`pl-1 py-1 border-gray-800 bg-white w-full ${
367
+ isLoading ? "pr-10 " : "pr-20"
368
+ }`}
369
+ value={value}
370
+ onChange={onChange}
371
+ onKeyDown={onKeyDown}
372
+ onFocus={onFocus}
373
+ ref={inputRef}
374
+ placeholder={placeholder}
375
+ disabled={isLoading}
376
+ />
377
+ {isLoading && (
378
+ <div className="loader absolute right-0 text-gray-500">
379
+ {/* You can replace this div with an SVG or another icon of your choice */}
380
+ <svg
381
+ className="animate-spin h-5 w-5"
382
+ xmlns="http://www.w3.org/2000/svg"
383
+ fill="none"
384
+ viewBox="0 0 24 24"
385
+ >
386
+ <circle
387
+ className="opacity-25"
388
+ cx="12"
389
+ cy="12"
390
+ r="10"
391
+ stroke="currentColor"
392
+ strokeWidth="4"
393
+ />
394
+ <path
395
+ className="opacity-75"
396
+ fill="currentColor"
397
+ d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
398
+ />
399
+ </svg>
400
+ </div>
401
+ )}
402
+ {isFocused && !isLoading && (
403
+ <MdExpandLess
404
+ className="material-icons absolute right-0 cursor-pointer text-gray-900 lext-lg"
405
+ onClick={onBlur}
406
+ />
407
+ )}
408
+ {!isFocused && !isLoading && (
409
+ <MdExpandMore
410
+ className="material-icons absolute right-0 cursor-pointer text-gray-900 lext-lg"
411
+ onClick={handleExpandMoreClick}
412
+ />
413
+ )}
414
+ {value && clearable && !isLoading && (
415
+ <MdClose
416
+ className="material-icons absolute right-6 cursor-pointer text-gray-900 lext-lg"
417
+ onClick={handleClearQuery}
418
+ />
419
+ )}
420
+ </div>
421
+ );
422
+ };
423
+
424
+ export default AutocompleteSearchBar;