@skalfa/skalfa-app 1.0.3 → 1.0.6

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 (90) hide show
  1. package/.env.example +8 -16
  2. package/app/auth/edit/page.tsx +1 -1
  3. package/app/auth/login/page.tsx +1 -1
  4. package/app/auth/me/page.tsx +1 -1
  5. package/app/auth/register/page.tsx +1 -1
  6. package/app/auth/verify/page.tsx +1 -1
  7. package/app/dashboard/layout.tsx +2 -2
  8. package/app/dashboard/page.tsx +1 -1
  9. package/app/index.ts +1 -0
  10. package/app/layout.tsx +2 -4
  11. package/app/page.tsx +2 -2
  12. package/bun.lock +7 -2
  13. package/components/index.ts +1 -3
  14. package/package.json +10 -7
  15. package/styles/components.css +1392 -0
  16. package/styles/globals.css +40 -175
  17. package/styles/utilities.css +37 -0
  18. package/tsconfig.json +4 -2
  19. package/utils/commands/skalfa.ts +1 -1
  20. package/components/base.components/accordion/Accordion.component.tsx +0 -82
  21. package/components/base.components/breadcrumb/Breadcrumb.component.tsx +0 -80
  22. package/components/base.components/button/Button.component.tsx +0 -91
  23. package/components/base.components/button/IconButton.component.tsx +0 -88
  24. package/components/base.components/button/button.decorate.ts +0 -82
  25. package/components/base.components/card/AlertCard.component.tsx +0 -69
  26. package/components/base.components/card/Card.component.tsx +0 -25
  27. package/components/base.components/card/DashboardCard.component.tsx +0 -44
  28. package/components/base.components/card/GalleryCard.component.tsx +0 -50
  29. package/components/base.components/card/ProductCard.component.tsx +0 -65
  30. package/components/base.components/card/ProfileCard.component.tsx +0 -71
  31. package/components/base.components/carousel/Carousel.component.tsx +0 -113
  32. package/components/base.components/chip/Chip.component.tsx +0 -39
  33. package/components/base.components/document/DocumentViewer.component.tsx +0 -164
  34. package/components/base.components/document/ExportExcel.component.tsx +0 -340
  35. package/components/base.components/document/ImportExcel.component.tsx +0 -315
  36. package/components/base.components/document/PrintTable.component.tsx +0 -204
  37. package/components/base.components/document/RenderPDF.component.tsx +0 -416
  38. package/components/base.components/index.ts +0 -85
  39. package/components/base.components/input/Checkbox.component.tsx +0 -109
  40. package/components/base.components/input/Input.component.tsx +0 -332
  41. package/components/base.components/input/InputCheckbox.component.tsx +0 -174
  42. package/components/base.components/input/InputCurrency.component.tsx +0 -163
  43. package/components/base.components/input/InputDate.component.tsx +0 -352
  44. package/components/base.components/input/InputDatetime.component.tsx +0 -260
  45. package/components/base.components/input/InputDocument.component.tsx +0 -352
  46. package/components/base.components/input/InputImage.component.tsx +0 -533
  47. package/components/base.components/input/InputMap.component.tsx +0 -318
  48. package/components/base.components/input/InputNumber.component.tsx +0 -192
  49. package/components/base.components/input/InputOtp.component.tsx +0 -169
  50. package/components/base.components/input/InputPassword.component.tsx +0 -236
  51. package/components/base.components/input/InputRadio.component.tsx +0 -175
  52. package/components/base.components/input/InputTime.component.tsx +0 -276
  53. package/components/base.components/input/InputValues.component.tsx +0 -68
  54. package/components/base.components/input/Radio.component.tsx +0 -102
  55. package/components/base.components/input/Select.component.tsx +0 -541
  56. package/components/base.components/modal/BottomSheet.component.tsx +0 -246
  57. package/components/base.components/modal/FloatingPage.component.tsx +0 -104
  58. package/components/base.components/modal/Modal.component.tsx +0 -96
  59. package/components/base.components/modal/ModalConfirm.component.tsx +0 -218
  60. package/components/base.components/modal/Toast.component.tsx +0 -126
  61. package/components/base.components/nav/Bottombar.component.tsx +0 -116
  62. package/components/base.components/nav/Footer.component.tsx +0 -144
  63. package/components/base.components/nav/Headbar.component.tsx +0 -104
  64. package/components/base.components/nav/Navbar.component.tsx +0 -100
  65. package/components/base.components/nav/Sidebar.component.tsx +0 -301
  66. package/components/base.components/nav/Tabbar.component.tsx +0 -60
  67. package/components/base.components/nav/Wizard.component.tsx +0 -73
  68. package/components/base.components/supervision/FormSupervision.component.tsx +0 -434
  69. package/components/base.components/supervision/TableSupervision.component.tsx +0 -697
  70. package/components/base.components/table/ControlBar.component.tsx +0 -497
  71. package/components/base.components/table/FilterComponent.tsx +0 -518
  72. package/components/base.components/table/Pagination.component.tsx +0 -159
  73. package/components/base.components/table/Table.component.tsx +0 -469
  74. package/components/base.components/typography/TypographyArticle.component.tsx +0 -26
  75. package/components/base.components/typography/TypographyColumn.component.tsx +0 -20
  76. package/components/base.components/typography/TypographyContent.component.tsx +0 -20
  77. package/components/base.components/typography/TypographyTips.component.tsx +0 -20
  78. package/components/base.components/wrap/Draggable.component.tsx +0 -303
  79. package/components/base.components/wrap/IDBProvider.tsx +0 -12
  80. package/components/base.components/wrap/Image.component.tsx +0 -10
  81. package/components/base.components/wrap/OutsideClick.component.tsx +0 -48
  82. package/components/base.components/wrap/ScrollContainer.component.tsx +0 -104
  83. package/components/base.components/wrap/ShortcutProvider.tsx +0 -57
  84. package/components/base.components/wrap/Swipe.component.tsx +0 -93
  85. package/components/construct.components/example.tsx +0 -1
  86. package/components/construct.components/index.ts +0 -5
  87. package/components/structure.components/example.tsx +0 -1
  88. package/components/structure.components/index.ts +0 -5
  89. package/schema/idb/app.schema.ts +0 -9
  90. package/schema/index.ts +0 -5
@@ -1,332 +0,0 @@
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,174 +0,0 @@
1
- "use client"
2
-
3
- import { ReactNode, useEffect, useState } from "react";
4
- import { api, ApiType, cn, pcn, useInputHandler, useValidation, validation, ValidationRules } from "@utils";
5
- import { CheckboxComponent } from "@components";
6
-
7
-
8
-
9
- type CT = "label" | "tip" | "error" | "input" | "icon";
10
-
11
- export interface InputCheckboxOptionProps {
12
- value: string | number;
13
- label: string;
14
- };
15
-
16
- export interface InputCheckboxProps {
17
- name : string;
18
- label ?: string;
19
- tip ?: string | ReactNode;
20
- vertical ?: boolean;
21
-
22
- value ?: string[] | number[];
23
- disabled ?: boolean;
24
- invalid ?: string;
25
-
26
- options ?: InputCheckboxOptionProps[];
27
- serverOptionControl ?: ApiType;
28
- customOptions ?: any;
29
- validations ?: ValidationRules;
30
-
31
- onChange ?: (value: string[] | number[]) => any;
32
- register ?: (name: string, validations?: ValidationRules) => void;
33
- unregister ?: (name: string) => void;
34
-
35
- /** Use custom class with: "label::", "tip::", "error::", "icon::". */
36
- className ?: string;
37
-
38
- /** Use custom class with: "label::", "checked::", "error::". */
39
- classNameCheckbox ?: string;
40
- };
41
-
42
-
43
-
44
- export function InputCheckboxComponent({
45
- name,
46
- label,
47
- tip,
48
- vertical,
49
- className="",
50
- classNameCheckbox="",
51
-
52
- value,
53
- disabled,
54
- invalid,
55
-
56
- options,
57
- serverOptionControl,
58
- customOptions,
59
- validations,
60
-
61
- register,
62
- unregister,
63
- onChange,
64
- }: InputCheckboxProps) {
65
-
66
- const [dataOptions, setDataOptions] = useState<InputCheckboxOptionProps[]>([]);
67
- const [loading, setLoading] = useState(false);
68
-
69
-
70
- // =========================>
71
- // ## initial
72
- // =========================>
73
- const inputHandler = useInputHandler(name, value, validations, register, unregister, false)
74
-
75
-
76
- // =========================>
77
- // ## Invalid handler
78
- // =========================>
79
- const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
80
-
81
-
82
- // =========================>
83
- // ## fetch option
84
- // =========================>
85
- useEffect(() => {
86
- const fetchOptions = async () => {
87
- setLoading(true);
88
- const mutateOptions = await api(serverOptionControl || {});
89
- if (mutateOptions?.status == 200) {
90
- customOptions ? setDataOptions([customOptions, ...mutateOptions.data]) : setDataOptions(mutateOptions.data);
91
- setLoading(false);
92
- }
93
- };
94
-
95
- if (serverOptionControl?.path || serverOptionControl?.url) {
96
- fetchOptions();
97
- } else {
98
- !options && setDataOptions([]);
99
- }
100
- }, [serverOptionControl?.path, serverOptionControl?.url]);
101
-
102
-
103
- return (
104
- <>
105
- <div className="w-full relative flex flex-col gap-y-0.5">
106
- <label
107
- className={cn(
108
- "input-label",
109
- pcn<CT>(className, "label"),
110
- disabled && "opacity-50",
111
- disabled && pcn<CT>(className, "label", "disabled"),
112
- invalidMessage && "text-danger",
113
- invalidMessage && pcn<CT>(className, "label", "focus"),
114
- )}
115
- >
116
- {label}
117
- {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
118
- </label>
119
-
120
- {tip && (
121
- <small
122
- className={cn(
123
- "input-tip",
124
- pcn<CT>(className, "tip"),
125
- disabled && "opacity-60",
126
- disabled && pcn<CT>(className, "tip", "disabled"),
127
- )}
128
- >{tip}</small>
129
- )}
130
-
131
- <div
132
- className={cn(
133
- `input overflow-auto input-scroll w-full flex flex-nowrap gap-y-2 gap-4`,
134
- vertical && "flex-col flex-wrap",
135
- pcn<CT>(className, "input"),
136
- invalidMessage && "input-error",
137
- invalidMessage && pcn<CT>(className, "input", "error"),
138
- )}
139
- >
140
- {loading && (vertical ? [1, 2, 3, 4, 5, 6, 7, 8, 9] : [1, 2, 3]).map((_, key) => {
141
- return <div key={key} className="w-1/3 h-6 rounded-lg"></div>;
142
- })}
143
-
144
- {(options || dataOptions) && (options || dataOptions)?.map((option, key) => {
145
- const checked = Array().concat(inputHandler.value).find((val) => val == option.value);
146
-
147
- return (
148
- <CheckboxComponent
149
- key={key}
150
- label={option.label}
151
- name={`option[${option.value}]#${name}`}
152
- checked={!!checked}
153
- disabled={disabled}
154
- className={classNameCheckbox}
155
- onChange={() => {
156
- const newVal = (Array.isArray(inputHandler.value) ? inputHandler.value : [])
157
- .filter((val) => val !== option.value)
158
- .concat(checked ? [] : [option.value]);
159
-
160
- inputHandler.setValue(newVal);
161
- onChange?.(newVal);
162
- }}
163
- />
164
- );
165
- })}
166
- </div>
167
-
168
- {invalidMessage && (
169
- <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
170
- )}
171
- </div>
172
- </>
173
- );
174
- }
@@ -1,163 +0,0 @@
1
- "use client"
2
-
3
- import { InputHTMLAttributes, ReactNode } from "react";
4
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
- import { cn, conversion, pcn, useInputHandler, useInputRandomId, useValidation, validation, ValidationRules } from "@utils";
6
-
7
-
8
-
9
- type CT = "label" | "tip" | "error" | "input" | "icon";
10
-
11
- export interface InputCurrencyProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
12
- label ?: string;
13
- tip ?: string | ReactNode;
14
- leftIcon ?: any;
15
- rightIcon ?: any;
16
-
17
- value ?: number;
18
- invalid ?: string;
19
- validations ?: ValidationRules;
20
- format ?: {
21
- locale ?: string;
22
- currency ?: string;
23
- };
24
-
25
- onChange ?: (value: number) => any;
26
- register ?: (name: string, validations?: ValidationRules) => void;
27
- unregister ?: (name: string) => void;
28
-
29
- /** Use custom class with: "label::", "tip::", "error::", "icon::". */
30
- className ?: string;
31
- }
32
-
33
-
34
-
35
- export function InputCurrencyComponent({
36
- label,
37
- tip,
38
- leftIcon,
39
- rightIcon,
40
-
41
- value,
42
- invalid,
43
-
44
- validations,
45
- format,
46
-
47
- register,
48
- unregister,
49
- onChange,
50
-
51
- className = "",
52
- ...props
53
- }: InputCurrencyProps) {
54
-
55
- // =========================>
56
- // ## Initial
57
- // =========================>
58
- const inputHandler = useInputHandler(props.name, value, validations, register, unregister, false)
59
- const randomId = useInputRandomId()
60
-
61
-
62
- // =========================>
63
- // ## Invalid handler
64
- // =========================>
65
- const [invalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
66
-
67
-
68
- return (
69
- <div className="relative flex flex-col gap-y-0.5">
70
- <label
71
- htmlFor={randomId}
72
- className={cn(
73
- "input-label",
74
- pcn<CT>(className, "label"),
75
- props.disabled && "opacity-50",
76
- props.disabled && pcn<CT>(className, "label", "disabled"),
77
- inputHandler.focus && "text-primary",
78
- inputHandler.focus && pcn<CT>(className, "label", "focus"),
79
- !!invalidMessage && "text-danger",
80
- !!invalidMessage && pcn<CT>(className, "label", "focus"),
81
- )}
82
- >
83
- {label}
84
- {validations && validation.hasRules(validations, "required") && <span className="text-danger ml-1">*</span>}
85
- </label>
86
-
87
- {tip && (
88
- <small
89
- className={cn(
90
- "input-tip",
91
- pcn<CT>(className, "tip"),
92
- props.disabled && "opacity-60",
93
- props.disabled && pcn<CT>(className, "tip", "disabled"),
94
- )}
95
- >{tip}</small>
96
- )}
97
-
98
- <div className="relative">
99
- <input
100
- {...props}
101
- id={randomId}
102
- className={cn(
103
- "input",
104
- leftIcon && "pl-12",
105
- rightIcon && "pr-12",
106
- pcn<CT>(className, "input"),
107
- !!invalidMessage && "input-error",
108
- !!invalidMessage && pcn<CT>(className, "input", "error"),
109
- )}
110
- value={inputHandler.value ? conversion.currency(inputHandler.value, format?.locale, format?.currency) : ""}
111
- onChange={(e) => {
112
- const val = Number(e.target.value.replace(/[^0-9]/g,""));
113
-
114
- inputHandler.setValue(val);
115
- inputHandler.setIdle(false);
116
-
117
- onChange?.(val);
118
- }}
119
- onFocus={(e) => {
120
- props.onFocus?.(e);
121
- inputHandler.setFocus(true);
122
- }}
123
- onBlur={(e) => {
124
- props.onBlur?.(e);
125
- setTimeout(() => inputHandler.setFocus(false), 100);
126
- }}
127
- autoComplete="off"
128
- />
129
-
130
- {leftIcon && (
131
- <FontAwesomeIcon
132
- className={cn(
133
- "left-4 input-icon",
134
- pcn<CT>(className, "icon"),
135
- props.disabled && "opacity-60",
136
- props.disabled && pcn<CT>(className, "icon", "disabled"),
137
- inputHandler.focus && "text-primary",
138
- inputHandler.focus && pcn<CT>(className, "icon", "focus"),
139
- )}
140
- icon={leftIcon}
141
- />
142
- )}
143
- {rightIcon && (
144
- <FontAwesomeIcon
145
- className={cn(
146
- "right-4 input-icon",
147
- pcn<CT>(className, "icon"),
148
- props.disabled && "opacity-60",
149
- props.disabled && pcn<CT>(className, "icon", "disabled"),
150
- inputHandler.focus && "text-primary",
151
- inputHandler.focus && pcn<CT>(className, "icon", "focus"),
152
- )}
153
- icon={rightIcon}
154
- />
155
- )}
156
- </div>
157
-
158
- {invalidMessage && (
159
- <small className={cn("input-error-message", pcn<CT>(className, "error"))}>{invalidMessage}</small>
160
- )}
161
- </div>
162
- );
163
- }