@skalfa/skalfa-app 1.0.0 → 1.0.1

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 (87) hide show
  1. package/.env.example +43 -43
  2. package/.github/workflows/publish.yml +39 -0
  3. package/CONTRIBUTING.md +45 -0
  4. package/LICENSE +21 -0
  5. package/README.md +91 -28
  6. package/app/auth/edit/page.tsx +65 -65
  7. package/app/auth/login/page.tsx +63 -63
  8. package/app/auth/me/page.tsx +58 -58
  9. package/app/auth/register/page.tsx +69 -69
  10. package/app/auth/verify/page.tsx +53 -53
  11. package/app/dashboard/user/page.tsx +76 -76
  12. package/app/layout.tsx +37 -37
  13. package/app/manifest.ts +25 -0
  14. package/app/page.tsx +13 -13
  15. package/barrels.json +5 -5
  16. package/blueprints/starter.blueprint.json +102 -102
  17. package/bun.lock +916 -0
  18. package/components/base.components/chip/Chip.component.tsx +39 -39
  19. package/components/base.components/document/DocumentViewer.component.tsx +163 -163
  20. package/components/base.components/document/ExportExcel.component.tsx +340 -340
  21. package/components/base.components/document/ImportExcel.component.tsx +315 -315
  22. package/components/base.components/document/PrintTable.component.tsx +204 -204
  23. package/components/base.components/document/RenderPDF.component.tsx +415 -415
  24. package/components/base.components/input/Checkbox.component.tsx +109 -109
  25. package/components/base.components/input/Input.component.tsx +332 -332
  26. package/components/base.components/input/InputCheckbox.component.tsx +174 -174
  27. package/components/base.components/input/InputCurrency.component.tsx +163 -163
  28. package/components/base.components/input/InputDate.component.tsx +352 -352
  29. package/components/base.components/input/InputDatetime.component.tsx +260 -260
  30. package/components/base.components/input/InputDocument.component.tsx +351 -351
  31. package/components/base.components/input/InputImage.component.tsx +533 -533
  32. package/components/base.components/input/InputMap.component.tsx +317 -317
  33. package/components/base.components/input/InputNumber.component.tsx +192 -192
  34. package/components/base.components/input/InputOtp.component.tsx +169 -169
  35. package/components/base.components/input/InputPassword.component.tsx +236 -236
  36. package/components/base.components/input/InputRadio.component.tsx +175 -175
  37. package/components/base.components/input/InputTime.component.tsx +275 -275
  38. package/components/base.components/input/InputValues.component.tsx +68 -68
  39. package/components/base.components/input/Radio.component.tsx +102 -102
  40. package/components/base.components/input/Select.component.tsx +541 -541
  41. package/components/base.components/modal/BottomSheet.component.tsx +245 -245
  42. package/components/base.components/supervision/FormSupervision.component.tsx +433 -433
  43. package/components/base.components/supervision/TableSupervision.component.tsx +697 -697
  44. package/components/base.components/table/ControlBar.component.tsx +497 -497
  45. package/components/base.components/table/FilterComponent.tsx +518 -518
  46. package/components/base.components/table/Table.component.tsx +469 -469
  47. package/components/base.components/typography/TypographyArticle.component.tsx +26 -26
  48. package/components/base.components/typography/TypographyColumn.component.tsx +20 -20
  49. package/components/base.components/typography/TypographyContent.component.tsx +20 -20
  50. package/components/base.components/typography/TypographyTips.component.tsx +20 -20
  51. package/components/base.components/wrap/Draggable.component.tsx +303 -303
  52. package/components/base.components/wrap/IDBProvider.tsx +12 -12
  53. package/components/base.components/wrap/Image.component.tsx +9 -9
  54. package/components/base.components/wrap/ShortcutProvider.tsx +57 -57
  55. package/components/base.components/wrap/Swipe.component.tsx +93 -93
  56. package/components/index.ts +2 -2
  57. package/contexts/AppProvider.tsx +11 -11
  58. package/contexts/Auth.context.tsx +64 -64
  59. package/contexts/Toggle.context.tsx +44 -44
  60. package/next.config.ts +15 -1
  61. package/package.json +14 -13
  62. package/public/204.svg +19 -19
  63. package/public/500.svg +39 -39
  64. package/public/icon-192.png +0 -0
  65. package/public/icon-512.png +0 -0
  66. package/public/images/logo-fill.png +0 -0
  67. package/public/images/logo-full-fill.png +0 -0
  68. package/public/images/logo-full.png +0 -0
  69. package/public/images/logo.png +0 -0
  70. package/schema/idb/app.schema.ts +8 -8
  71. package/src-tauri/Cargo.toml +14 -0
  72. package/src-tauri/build.rs +3 -0
  73. package/src-tauri/capabilities/default.json +11 -0
  74. package/src-tauri/icons/128x128.png +0 -0
  75. package/src-tauri/icons/128x128@2x.png +0 -0
  76. package/src-tauri/icons/32x32.png +0 -0
  77. package/src-tauri/icons/icon.icns +0 -0
  78. package/src-tauri/icons/icon.ico +0 -0
  79. package/src-tauri/src/main.rs +7 -0
  80. package/src-tauri/tauri.conf.json +36 -0
  81. package/styles/globals.css +231 -231
  82. package/styles/tailwind.safelist +68 -68
  83. package/utils/commands/barrels.ts +27 -27
  84. package/utils/commands/light.ts +21 -21
  85. package/utils/commands/logger.ts +42 -42
  86. package/utils/commands/stubs/table-blueprint.stub +12 -12
  87. package/utils/commands/use-pdf.ts +29 -29
@@ -1,332 +1,332 @@
1
- "use client"
2
-
3
- import { InputHTMLAttributes, ReactNode, Ref, useEffect, useState } from "react";
4
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
- import { cn, pcn, useInputHandler, useInputRandomId, useValidation, validation, ValidationRules } from "@utils";
6
- import { InputValues } from "./InputValues.component";
7
-
8
-
9
-
10
- type CT = "label" | "tip" | "error" | "base" | "icon" | "suggest" | "suggest-item";
11
-
12
- export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
13
- label ?: string;
14
- tip ?: string | ReactNode;
15
- leftIcon ?: any;
16
- rightIcon ?: any;
17
-
18
- value ?: any;
19
- invalid ?: string;
20
- suggestions ?: string[];
21
-
22
- validations ?: ValidationRules;
23
- onlyAlphabet ?: boolean;
24
- uppercase ?: boolean;
25
- lowercase ?: boolean;
26
- multiple ?: boolean;
27
-
28
- onChange ?: (value: any) => any;
29
- register ?: (name: string, validations?: ValidationRules) => void;
30
- unregister ?: (name: string) => void;
31
-
32
- ref ?: Ref<HTMLInputElement>,
33
-
34
- /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
35
- className ?: string;
36
- }
37
-
38
-
39
-
40
- export function InputComponent({
41
- label,
42
- tip,
43
- leftIcon,
44
- rightIcon,
45
- className = "",
46
-
47
- value,
48
- invalid,
49
- suggestions,
50
-
51
- validations,
52
- onlyAlphabet,
53
- uppercase,
54
- lowercase,
55
- multiple,
56
-
57
- register,
58
- unregister,
59
- onChange,
60
-
61
- ref,
62
- ...props
63
- }: InputProps) {
64
-
65
-
66
- const [activeSuggestion, setActiveSuggestion] = useState(0);
67
- const [showSuggestions, setShowSuggestions] = useState(false);
68
- const [dataSuggestions, setDataSuggestions] = useState<string[] | undefined>([]);
69
- const [filteredSuggestions, setFilteredSuggestions] = useState<string[] | undefined>([]);
70
-
71
-
72
- // =========================>
73
- // ## Initial
74
- // =========================>
75
- const inputHandler = useInputHandler(props.name, value, validations, register, unregister, props.type == "file")
76
- const randomId = useInputRandomId()
77
-
78
-
79
- // =========================>
80
- // ## Invalid handler
81
- // =========================>
82
- const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
83
-
84
-
85
- // =========================>
86
- // ## Change value handler
87
- // =========================>
88
- useEffect(() => {
89
- if (inputHandler.value && typeof inputHandler.value === "string") {
90
- let newVal = onlyAlphabet ? inputHandler.value.replace(/[^A-Za-z ]+/g, "") : inputHandler.value;
91
-
92
- if (uppercase) newVal = newVal.toUpperCase();
93
- if (lowercase) newVal = newVal.toLowerCase();
94
-
95
- if (validations && validation.hasRules(validations, "max")) newVal = newVal.slice(0, parseInt(validation.getRules(validations, "max") || "0"));
96
-
97
- inputHandler.setValue(newVal);
98
- }
99
- }, [inputHandler.value, onlyAlphabet, uppercase, lowercase, validations]);
100
-
101
-
102
- // =========================>
103
- // ## suggestions handler
104
- // =========================>
105
- useEffect(() => {
106
- setDataSuggestions(suggestions);
107
- }, [suggestions]);
108
-
109
- const filterSuggestion = (e: any) => {
110
- if (dataSuggestions?.length) {
111
- let filteredSuggestion = [];
112
-
113
- if (e.target.value) {
114
- filteredSuggestion = dataSuggestions
115
- .filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1)
116
- .slice(0, 10);
117
- } else {
118
- filteredSuggestion = dataSuggestions.slice(0, 10);
119
- }
120
-
121
- setActiveSuggestion(-1);
122
- setFilteredSuggestions(filteredSuggestion);
123
- setShowSuggestions(true);
124
- }
125
- };
126
-
127
-
128
- const onKeyDownSuggestion = (e: any) => {
129
- if (dataSuggestions?.length) {
130
- if (e.keyCode === 13) {
131
- const resultValue = filteredSuggestions?.at(activeSuggestion);
132
- setActiveSuggestion(-1);
133
- setFilteredSuggestions([]);
134
- setShowSuggestions(false);
135
- inputHandler.setValue(resultValue ? resultValue : inputHandler.value);
136
- if (onChange) {
137
- onChange(resultValue ? resultValue : inputHandler.value);
138
- }
139
- e.preventDefault();
140
- } else if (e.keyCode === 38) {
141
- if (activeSuggestion === 0) {
142
- return;
143
- }
144
-
145
- setActiveSuggestion(activeSuggestion - 1);
146
- } else if (e.keyCode === 40) {
147
- if (activeSuggestion + 1 >= (filteredSuggestions?.length || 0)) {
148
- return;
149
- }
150
-
151
- setActiveSuggestion(activeSuggestion + 1);
152
- }
153
- }
154
- };
155
-
156
- return (
157
- <>
158
- <div className="relative flex flex-col gap-y-0.5">
159
- <label
160
- htmlFor={randomId}
161
- className={cn(
162
- "input-label",
163
- pcn<CT>(className, "label"),
164
- props.disabled && "opacity-50",
165
- props.disabled && pcn<CT>(className, "label", "disabled"),
166
- inputHandler.focus && "text-primary",
167
- inputHandler.focus && pcn<CT>(className, "label", "focus"),
168
- !!invalidMessage && "text-danger",
169
- !!invalidMessage && pcn<CT>(className, "label", "focus"),
170
- )}
171
- >
172
- {label}
173
- {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
174
- </label>
175
-
176
- {tip && (
177
- <small
178
- className={cn(
179
- "input-tip",
180
- pcn<CT>(className, "tip"),
181
- props.disabled && "opacity-60",
182
- props.disabled && pcn<CT>(className, "tip", "disabled"),
183
- )}
184
- >{tip}</small>
185
- )}
186
-
187
- <div className="relative">
188
- <input
189
- {...props}
190
- ref={ref}
191
- id={randomId}
192
- placeholder={!multiple || (multiple && !inputHandler.value?.length) ? props.placeholder : ""}
193
- className={cn(
194
- "input",
195
- props.type == "file" && "input-file",
196
- leftIcon && "pl-12",
197
- rightIcon && "pr-12",
198
- pcn<CT>(className, "base"),
199
- !!invalidMessage && "input-error",
200
- !!invalidMessage && pcn<CT>(className, "base", "error"),
201
- )}
202
- value={!multiple ? inputHandler.value: undefined}
203
- onChange={(e) => {
204
- if(!multiple) {
205
- inputHandler.setValue(e.target.value);
206
- inputHandler.setIdle(false);
207
- onChange?.(props.type == "file" ? e.target?.files && e.target?.files[0] : e.target.value);
208
- dataSuggestions?.length && filterSuggestion(e);
209
- }
210
- }}
211
- onFocus={(e) => {
212
- props.onFocus?.(e);
213
- inputHandler.setFocus(true);
214
- dataSuggestions?.length && filterSuggestion(e);
215
- }}
216
- onBlur={(e) => {
217
- props.onBlur?.(e);
218
- setTimeout(() => inputHandler.setFocus(false), 100);
219
- }}
220
- onKeyDown={(e) => {
221
- dataSuggestions?.length && onKeyDownSuggestion(e);
222
-
223
- if (multiple && e.key === "Enter" || e.key === ",") {
224
- e.preventDefault();
225
- const currentValue = e.currentTarget.value.trim();
226
- if (!currentValue) return;
227
-
228
- const currentValues = Array.isArray(inputHandler.value) ? [...inputHandler.value] : [];
229
- if (!currentValues.includes(currentValue)) {
230
- const newValues = [...currentValues, currentValue];
231
- onChange?.(newValues);
232
- inputHandler.setValue(newValues);
233
- e.currentTarget.value = "";
234
- }
235
- }
236
- }}
237
- autoComplete={props.autoComplete || dataSuggestions?.length ? "off" : ""}
238
- />
239
-
240
-
241
- {(multiple) && (
242
- <InputValues
243
- value={inputHandler.value || []}
244
- isFocus={inputHandler.focus}
245
- onFocus={() => setTimeout(() => inputHandler.setFocus(true), 110)}
246
- onDelete={(_, index) => {
247
- const values = Array().concat(inputHandler.value);
248
- const newValues = values.filter((_, val) => val != index);
249
-
250
- inputHandler.setValue(newValues);
251
- onChange?.(newValues);
252
- }}
253
- className={`${!inputHandler.focus && (leftIcon ? "ml-[2.5rem]" : "ml-[1rem]")}`}
254
- style={{ maxWidth: `calc(100% - ${leftIcon ? "5.2rem" : "2rem"})` }}
255
- />
256
- )}
257
-
258
- {leftIcon && (
259
- <FontAwesomeIcon
260
- className={cn(
261
- "left-4 input-icon",
262
- pcn<CT>(className, "icon"),
263
- props.disabled && "opacity-60",
264
- props.disabled && pcn<CT>(className, "icon", "disabled"),
265
- inputHandler.focus && "text-primary",
266
- inputHandler.focus && pcn<CT>(className, "icon", "focus"),
267
- )}
268
- icon={leftIcon}
269
- />
270
- )}
271
-
272
- {rightIcon && (
273
- <FontAwesomeIcon
274
- className={cn(
275
- "right-4 input-icon",
276
- pcn<CT>(className, "icon"),
277
- props.disabled && "opacity-60",
278
- props.disabled && pcn<CT>(className, "icon", "disabled"),
279
- inputHandler.focus && "text-primary",
280
- inputHandler.focus && pcn<CT>(className, "icon", "focus"),
281
- )}
282
- icon={rightIcon}
283
- />
284
- )}
285
- </div>
286
-
287
- {!!dataSuggestions?.length && showSuggestions && !!filteredSuggestions?.length && (
288
- <div>
289
- <ul
290
- className={cn(
291
- "input-suggest-container",
292
- pcn<CT>(className, "suggest"),
293
- inputHandler.focus ? "opacity-100 scale-y-100 -translate-y-0" : "opacity-0 scale-y-0 -translate-y-1/2",
294
- )}
295
- >
296
- {filteredSuggestions.map((suggestion, key) => {
297
- return (
298
- <li
299
- className={cn(
300
- "input-suggest",
301
- pcn<CT>(className, "suggest-item"),
302
- inputHandler.value == suggestion && "bg-light-primary text-primary",
303
- inputHandler.value == suggestion && pcn<CT>(className, "suggest-item", "active"),
304
- )}
305
- key={suggestion}
306
- onMouseDown={() => {
307
- setTimeout(() => inputHandler.setFocus(true), 110);
308
- }}
309
- onMouseUp={() => {
310
- setActiveSuggestion(key);
311
- setFilteredSuggestions([]);
312
- setShowSuggestions(false);
313
- inputHandler.setValue(filteredSuggestions[key] || inputHandler.value);
314
- onChange?.(filteredSuggestions[key] || inputHandler.value);
315
- setTimeout(() => inputHandler.setFocus(false), 120);
316
- }}
317
- >
318
- {suggestion}
319
- </li>
320
- );
321
- })}
322
- </ul>
323
- </div>
324
- )}
325
-
326
- {invalidMessage && (
327
- <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
328
- )}
329
- </div>
330
- </>
331
- );
332
- }
1
+ "use client"
2
+
3
+ import { InputHTMLAttributes, ReactNode, Ref, useEffect, useState } from "react";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { cn, pcn, useInputHandler, useInputRandomId, useValidation, validation, ValidationRules } from "@utils";
6
+ import { InputValues } from "./InputValues.component";
7
+
8
+
9
+
10
+ type CT = "label" | "tip" | "error" | "base" | "icon" | "suggest" | "suggest-item";
11
+
12
+ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
13
+ label ?: string;
14
+ tip ?: string | ReactNode;
15
+ leftIcon ?: any;
16
+ rightIcon ?: any;
17
+
18
+ value ?: any;
19
+ invalid ?: string;
20
+ suggestions ?: string[];
21
+
22
+ validations ?: ValidationRules;
23
+ onlyAlphabet ?: boolean;
24
+ uppercase ?: boolean;
25
+ lowercase ?: boolean;
26
+ multiple ?: boolean;
27
+
28
+ onChange ?: (value: any) => any;
29
+ register ?: (name: string, validations?: ValidationRules) => void;
30
+ unregister ?: (name: string) => void;
31
+
32
+ ref ?: Ref<HTMLInputElement>,
33
+
34
+ /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
35
+ className ?: string;
36
+ }
37
+
38
+
39
+
40
+ export function InputComponent({
41
+ label,
42
+ tip,
43
+ leftIcon,
44
+ rightIcon,
45
+ className = "",
46
+
47
+ value,
48
+ invalid,
49
+ suggestions,
50
+
51
+ validations,
52
+ onlyAlphabet,
53
+ uppercase,
54
+ lowercase,
55
+ multiple,
56
+
57
+ register,
58
+ unregister,
59
+ onChange,
60
+
61
+ ref,
62
+ ...props
63
+ }: InputProps) {
64
+
65
+
66
+ const [activeSuggestion, setActiveSuggestion] = useState(0);
67
+ const [showSuggestions, setShowSuggestions] = useState(false);
68
+ const [dataSuggestions, setDataSuggestions] = useState<string[] | undefined>([]);
69
+ const [filteredSuggestions, setFilteredSuggestions] = useState<string[] | undefined>([]);
70
+
71
+
72
+ // =========================>
73
+ // ## Initial
74
+ // =========================>
75
+ const inputHandler = useInputHandler(props.name, value, validations, register, unregister, props.type == "file")
76
+ const randomId = useInputRandomId()
77
+
78
+
79
+ // =========================>
80
+ // ## Invalid handler
81
+ // =========================>
82
+ const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
83
+
84
+
85
+ // =========================>
86
+ // ## Change value handler
87
+ // =========================>
88
+ useEffect(() => {
89
+ if (inputHandler.value && typeof inputHandler.value === "string") {
90
+ let newVal = onlyAlphabet ? inputHandler.value.replace(/[^A-Za-z ]+/g, "") : inputHandler.value;
91
+
92
+ if (uppercase) newVal = newVal.toUpperCase();
93
+ if (lowercase) newVal = newVal.toLowerCase();
94
+
95
+ if (validations && validation.hasRules(validations, "max")) newVal = newVal.slice(0, parseInt(validation.getRules(validations, "max") || "0"));
96
+
97
+ inputHandler.setValue(newVal);
98
+ }
99
+ }, [inputHandler.value, onlyAlphabet, uppercase, lowercase, validations]);
100
+
101
+
102
+ // =========================>
103
+ // ## suggestions handler
104
+ // =========================>
105
+ useEffect(() => {
106
+ setDataSuggestions(suggestions);
107
+ }, [suggestions]);
108
+
109
+ const filterSuggestion = (e: any) => {
110
+ if (dataSuggestions?.length) {
111
+ let filteredSuggestion = [];
112
+
113
+ if (e.target.value) {
114
+ filteredSuggestion = dataSuggestions
115
+ .filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1)
116
+ .slice(0, 10);
117
+ } else {
118
+ filteredSuggestion = dataSuggestions.slice(0, 10);
119
+ }
120
+
121
+ setActiveSuggestion(-1);
122
+ setFilteredSuggestions(filteredSuggestion);
123
+ setShowSuggestions(true);
124
+ }
125
+ };
126
+
127
+
128
+ const onKeyDownSuggestion = (e: any) => {
129
+ if (dataSuggestions?.length) {
130
+ if (e.keyCode === 13) {
131
+ const resultValue = filteredSuggestions?.at(activeSuggestion);
132
+ setActiveSuggestion(-1);
133
+ setFilteredSuggestions([]);
134
+ setShowSuggestions(false);
135
+ inputHandler.setValue(resultValue ? resultValue : inputHandler.value);
136
+ if (onChange) {
137
+ onChange(resultValue ? resultValue : inputHandler.value);
138
+ }
139
+ e.preventDefault();
140
+ } else if (e.keyCode === 38) {
141
+ if (activeSuggestion === 0) {
142
+ return;
143
+ }
144
+
145
+ setActiveSuggestion(activeSuggestion - 1);
146
+ } else if (e.keyCode === 40) {
147
+ if (activeSuggestion + 1 >= (filteredSuggestions?.length || 0)) {
148
+ return;
149
+ }
150
+
151
+ setActiveSuggestion(activeSuggestion + 1);
152
+ }
153
+ }
154
+ };
155
+
156
+ return (
157
+ <>
158
+ <div className="relative flex flex-col gap-y-0.5">
159
+ <label
160
+ htmlFor={randomId}
161
+ className={cn(
162
+ "input-label",
163
+ pcn<CT>(className, "label"),
164
+ props.disabled && "opacity-50",
165
+ props.disabled && pcn<CT>(className, "label", "disabled"),
166
+ inputHandler.focus && "text-primary",
167
+ inputHandler.focus && pcn<CT>(className, "label", "focus"),
168
+ !!invalidMessage && "text-danger",
169
+ !!invalidMessage && pcn<CT>(className, "label", "focus"),
170
+ )}
171
+ >
172
+ {label}
173
+ {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
174
+ </label>
175
+
176
+ {tip && (
177
+ <small
178
+ className={cn(
179
+ "input-tip",
180
+ pcn<CT>(className, "tip"),
181
+ props.disabled && "opacity-60",
182
+ props.disabled && pcn<CT>(className, "tip", "disabled"),
183
+ )}
184
+ >{tip}</small>
185
+ )}
186
+
187
+ <div className="relative">
188
+ <input
189
+ {...props}
190
+ ref={ref}
191
+ id={randomId}
192
+ placeholder={!multiple || (multiple && !inputHandler.value?.length) ? props.placeholder : ""}
193
+ className={cn(
194
+ "input",
195
+ props.type == "file" && "input-file",
196
+ leftIcon && "pl-12",
197
+ rightIcon && "pr-12",
198
+ pcn<CT>(className, "base"),
199
+ !!invalidMessage && "input-error",
200
+ !!invalidMessage && pcn<CT>(className, "base", "error"),
201
+ )}
202
+ value={!multiple ? inputHandler.value: undefined}
203
+ onChange={(e) => {
204
+ if(!multiple) {
205
+ inputHandler.setValue(e.target.value);
206
+ inputHandler.setIdle(false);
207
+ onChange?.(props.type == "file" ? e.target?.files && e.target?.files[0] : e.target.value);
208
+ dataSuggestions?.length && filterSuggestion(e);
209
+ }
210
+ }}
211
+ onFocus={(e) => {
212
+ props.onFocus?.(e);
213
+ inputHandler.setFocus(true);
214
+ dataSuggestions?.length && filterSuggestion(e);
215
+ }}
216
+ onBlur={(e) => {
217
+ props.onBlur?.(e);
218
+ setTimeout(() => inputHandler.setFocus(false), 100);
219
+ }}
220
+ onKeyDown={(e) => {
221
+ dataSuggestions?.length && onKeyDownSuggestion(e);
222
+
223
+ if (multiple && e.key === "Enter" || e.key === ",") {
224
+ e.preventDefault();
225
+ const currentValue = e.currentTarget.value.trim();
226
+ if (!currentValue) return;
227
+
228
+ const currentValues = Array.isArray(inputHandler.value) ? [...inputHandler.value] : [];
229
+ if (!currentValues.includes(currentValue)) {
230
+ const newValues = [...currentValues, currentValue];
231
+ onChange?.(newValues);
232
+ inputHandler.setValue(newValues);
233
+ e.currentTarget.value = "";
234
+ }
235
+ }
236
+ }}
237
+ autoComplete={props.autoComplete || dataSuggestions?.length ? "off" : ""}
238
+ />
239
+
240
+
241
+ {(multiple) && (
242
+ <InputValues
243
+ value={inputHandler.value || []}
244
+ isFocus={inputHandler.focus}
245
+ onFocus={() => setTimeout(() => inputHandler.setFocus(true), 110)}
246
+ onDelete={(_, index) => {
247
+ const values = Array().concat(inputHandler.value);
248
+ const newValues = values.filter((_, val) => val != index);
249
+
250
+ inputHandler.setValue(newValues);
251
+ onChange?.(newValues);
252
+ }}
253
+ className={`${!inputHandler.focus && (leftIcon ? "ml-[2.5rem]" : "ml-[1rem]")}`}
254
+ style={{ maxWidth: `calc(100% - ${leftIcon ? "5.2rem" : "2rem"})` }}
255
+ />
256
+ )}
257
+
258
+ {leftIcon && (
259
+ <FontAwesomeIcon
260
+ className={cn(
261
+ "left-4 input-icon",
262
+ pcn<CT>(className, "icon"),
263
+ props.disabled && "opacity-60",
264
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
265
+ inputHandler.focus && "text-primary",
266
+ inputHandler.focus && pcn<CT>(className, "icon", "focus"),
267
+ )}
268
+ icon={leftIcon}
269
+ />
270
+ )}
271
+
272
+ {rightIcon && (
273
+ <FontAwesomeIcon
274
+ className={cn(
275
+ "right-4 input-icon",
276
+ pcn<CT>(className, "icon"),
277
+ props.disabled && "opacity-60",
278
+ props.disabled && pcn<CT>(className, "icon", "disabled"),
279
+ inputHandler.focus && "text-primary",
280
+ inputHandler.focus && pcn<CT>(className, "icon", "focus"),
281
+ )}
282
+ icon={rightIcon}
283
+ />
284
+ )}
285
+ </div>
286
+
287
+ {!!dataSuggestions?.length && showSuggestions && !!filteredSuggestions?.length && (
288
+ <div>
289
+ <ul
290
+ className={cn(
291
+ "input-suggest-container",
292
+ pcn<CT>(className, "suggest"),
293
+ inputHandler.focus ? "opacity-100 scale-y-100 -translate-y-0" : "opacity-0 scale-y-0 -translate-y-1/2",
294
+ )}
295
+ >
296
+ {filteredSuggestions.map((suggestion, key) => {
297
+ return (
298
+ <li
299
+ className={cn(
300
+ "input-suggest",
301
+ pcn<CT>(className, "suggest-item"),
302
+ inputHandler.value == suggestion && "bg-light-primary text-primary",
303
+ inputHandler.value == suggestion && pcn<CT>(className, "suggest-item", "active"),
304
+ )}
305
+ key={suggestion}
306
+ onMouseDown={() => {
307
+ setTimeout(() => inputHandler.setFocus(true), 110);
308
+ }}
309
+ onMouseUp={() => {
310
+ setActiveSuggestion(key);
311
+ setFilteredSuggestions([]);
312
+ setShowSuggestions(false);
313
+ inputHandler.setValue(filteredSuggestions[key] || inputHandler.value);
314
+ onChange?.(filteredSuggestions[key] || inputHandler.value);
315
+ setTimeout(() => inputHandler.setFocus(false), 120);
316
+ }}
317
+ >
318
+ {suggestion}
319
+ </li>
320
+ );
321
+ })}
322
+ </ul>
323
+ </div>
324
+ )}
325
+
326
+ {invalidMessage && (
327
+ <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
328
+ )}
329
+ </div>
330
+ </>
331
+ );
332
+ }