@axzydev/axzy_ui_system 1.2.0 → 1.2.2

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 (202) hide show
  1. package/dist/index.css +82 -197
  2. package/dist/index.css.map +1 -1
  3. package/package.json +2 -2
  4. package/src/App.tsx +354 -0
  5. package/src/assets/logo.png +0 -0
  6. package/src/assets/react.svg +1 -0
  7. package/src/components/alert/alert.props.ts +13 -0
  8. package/src/components/alert/alert.stories.tsx +41 -0
  9. package/src/components/alert/alert.tsx +53 -0
  10. package/src/components/avatar/avatar.props.ts +14 -0
  11. package/src/components/avatar/avatar.stories.tsx +46 -0
  12. package/src/components/avatar/avatar.tsx +53 -0
  13. package/src/components/badget/badget.props.ts +12 -0
  14. package/src/components/badget/badget.stories.tsx +76 -0
  15. package/src/components/badget/badget.tsx +61 -0
  16. package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
  17. package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
  18. package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
  19. package/src/components/button/button.props.ts +18 -0
  20. package/src/components/button/button.stories.tsx +174 -0
  21. package/src/components/button/button.tsx +117 -0
  22. package/src/components/calendar/calendar.props.ts +33 -0
  23. package/src/components/calendar/calendar.stories.tsx +91 -0
  24. package/src/components/calendar/calendar.tsx +608 -0
  25. package/src/components/calendar/index.ts +3 -0
  26. package/src/components/card/card.props.ts +13 -0
  27. package/src/components/card/card.stories.tsx +58 -0
  28. package/src/components/card/card.tsx +79 -0
  29. package/src/components/checkbox/checkbox.props.ts +11 -0
  30. package/src/components/checkbox/checkbox.stories.tsx +54 -0
  31. package/src/components/checkbox/checkbox.tsx +52 -0
  32. package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
  33. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
  34. package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
  35. package/src/components/data-table/ITDataTable.stories.tsx +213 -0
  36. package/src/components/data-table/dataTable.props.ts +69 -0
  37. package/src/components/data-table/dataTable.tsx +313 -0
  38. package/src/components/date-picker/date-picker.props.ts +30 -0
  39. package/src/components/date-picker/date-picker.stories.tsx +90 -0
  40. package/src/components/date-picker/datePicker.tsx +307 -0
  41. package/src/components/dialog/dialog.props.ts +9 -0
  42. package/src/components/dialog/dialog.stories.tsx +80 -0
  43. package/src/components/dialog/dialog.tsx +88 -0
  44. package/src/components/divider/divider.props.ts +8 -0
  45. package/src/components/divider/divider.stories.tsx +34 -0
  46. package/src/components/divider/divider.tsx +21 -0
  47. package/src/components/drawer/drawer.props.ts +14 -0
  48. package/src/components/drawer/drawer.stories.tsx +41 -0
  49. package/src/components/drawer/drawer.tsx +53 -0
  50. package/src/components/dropfile/dropfile.stories.tsx +75 -0
  51. package/src/components/dropfile/dropfile.tsx +407 -0
  52. package/src/components/empty-state/empty-state.props.ts +9 -0
  53. package/src/components/empty-state/empty-state.stories.tsx +20 -0
  54. package/src/components/empty-state/empty-state.tsx +21 -0
  55. package/src/components/flex/flex.props.ts +22 -0
  56. package/src/components/flex/flex.stories.tsx +71 -0
  57. package/src/components/flex/flex.tsx +79 -0
  58. package/src/components/form-builder/fieldRenderer.tsx +218 -0
  59. package/src/components/form-builder/formBuilder.context.tsx +70 -0
  60. package/src/components/form-builder/formBuilder.props.ts +43 -0
  61. package/src/components/form-builder/formBuilder.stories.tsx +317 -0
  62. package/src/components/form-builder/formBuilder.tsx +186 -0
  63. package/src/components/form-builder/useFormBuilder.ts +80 -0
  64. package/src/components/form-header/form-header.props.ts +5 -0
  65. package/src/components/form-header/form-header.tsx +38 -0
  66. package/src/components/grid/grid.props.ts +17 -0
  67. package/src/components/grid/grid.stories.tsx +72 -0
  68. package/src/components/grid/grid.tsx +69 -0
  69. package/src/components/image/image.props.ts +7 -0
  70. package/src/components/image/image.tsx +38 -0
  71. package/src/components/input/input.props.ts +49 -0
  72. package/src/components/input/input.stories.tsx +115 -0
  73. package/src/components/input/input.tsx +615 -0
  74. package/src/components/layout/layout.props.ts +10 -0
  75. package/src/components/layout/layout.stories.tsx +114 -0
  76. package/src/components/layout/layout.tsx +80 -0
  77. package/src/components/loader/loader.props.ts +8 -0
  78. package/src/components/loader/loader.stories.tsx +105 -0
  79. package/src/components/loader/loader.tsx +108 -0
  80. package/src/components/navbar/navbar.props.ts +37 -0
  81. package/src/components/navbar/navbar.tsx +328 -0
  82. package/src/components/page/page.props.ts +19 -0
  83. package/src/components/page/page.stories.tsx +98 -0
  84. package/src/components/page/page.tsx +90 -0
  85. package/src/components/page-header/page-header.props.ts +11 -0
  86. package/src/components/page-header/page-header.stories.tsx +61 -0
  87. package/src/components/page-header/page-header.tsx +62 -0
  88. package/src/components/pagination/pagination.props.ts +53 -0
  89. package/src/components/pagination/pagination.stories.tsx +111 -0
  90. package/src/components/pagination/pagination.tsx +241 -0
  91. package/src/components/popover/popover.props.ts +12 -0
  92. package/src/components/popover/popover.stories.tsx +25 -0
  93. package/src/components/popover/popover.tsx +45 -0
  94. package/src/components/progress/progress.props.ts +12 -0
  95. package/src/components/progress/progress.stories.tsx +40 -0
  96. package/src/components/progress/progress.tsx +52 -0
  97. package/src/components/radio/radio.props.ts +16 -0
  98. package/src/components/radio/radio.stories.tsx +50 -0
  99. package/src/components/radio/radio.tsx +58 -0
  100. package/src/components/search-select/index.ts +2 -0
  101. package/src/components/search-select/search-select.props.ts +46 -0
  102. package/src/components/search-select/search-select.stories.tsx +129 -0
  103. package/src/components/search-select/search-select.tsx +229 -0
  104. package/src/components/searchTable/components/EditableCell.tsx +149 -0
  105. package/src/components/searchTable/components/PaginationControls.tsx +86 -0
  106. package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
  107. package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
  108. package/src/components/searchTable/components/SearchInput.tsx +33 -0
  109. package/src/components/searchTable/components/SortButton.tsx +50 -0
  110. package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
  111. package/src/components/searchTable/components/TableHeader.tsx +35 -0
  112. package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
  113. package/src/components/searchTable/components/TableRow.tsx +144 -0
  114. package/src/components/searchTable/searchTable.props.ts +56 -0
  115. package/src/components/searchTable/searchTable.tsx +187 -0
  116. package/src/components/segmented-control/segmented-control.props.ts +18 -0
  117. package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
  118. package/src/components/segmented-control/segmented-control.tsx +52 -0
  119. package/src/components/select/select.props.ts +25 -0
  120. package/src/components/select/select.stories.tsx +86 -0
  121. package/src/components/select/select.tsx +150 -0
  122. package/src/components/sidebar/sidebar.props.ts +28 -0
  123. package/src/components/sidebar/sidebar.stories.tsx +117 -0
  124. package/src/components/sidebar/sidebar.tsx +313 -0
  125. package/src/components/skeleton/skeleton.props.ts +12 -0
  126. package/src/components/skeleton/skeleton.stories.tsx +30 -0
  127. package/src/components/skeleton/skeleton.tsx +45 -0
  128. package/src/components/slide/slide.props.ts +45 -0
  129. package/src/components/slide/slide.stories.tsx +121 -0
  130. package/src/components/slide/slide.tsx +109 -0
  131. package/src/components/slider/slider.props.ts +10 -0
  132. package/src/components/slider/slider.stories.tsx +30 -0
  133. package/src/components/slider/slider.tsx +49 -0
  134. package/src/components/stack/stack.props.ts +19 -0
  135. package/src/components/stack/stack.stories.tsx +79 -0
  136. package/src/components/stack/stack.tsx +79 -0
  137. package/src/components/stat-card/stat-card.props.ts +13 -0
  138. package/src/components/stat-card/stat-card.stories.tsx +41 -0
  139. package/src/components/stat-card/stat-card.tsx +44 -0
  140. package/src/components/stepper/stepper.css +26 -0
  141. package/src/components/stepper/stepper.props.ts +29 -0
  142. package/src/components/stepper/stepper.stories.tsx +155 -0
  143. package/src/components/stepper/stepper.tsx +227 -0
  144. package/src/components/table/table.props.ts +43 -0
  145. package/src/components/table/table.stories.tsx +189 -0
  146. package/src/components/table/table.tsx +376 -0
  147. package/src/components/tabs/tabs.props.ts +18 -0
  148. package/src/components/tabs/tabs.stories.tsx +32 -0
  149. package/src/components/tabs/tabs.tsx +74 -0
  150. package/src/components/text/text.props.ts +9 -0
  151. package/src/components/text/text.tsx +20 -0
  152. package/src/components/textarea/textarea.props.ts +15 -0
  153. package/src/components/textarea/textarea.stories.tsx +27 -0
  154. package/src/components/textarea/textarea.tsx +55 -0
  155. package/src/components/theme-provider/themeProvider.props.ts +28 -0
  156. package/src/components/theme-provider/themeProvider.tsx +1854 -0
  157. package/src/components/time-picker/timePicker.props.ts +16 -0
  158. package/src/components/time-picker/timePicker.stories.tsx +131 -0
  159. package/src/components/time-picker/timePicker.tsx +317 -0
  160. package/src/components/toast/toast.css +32 -0
  161. package/src/components/toast/toast.props.ts +13 -0
  162. package/src/components/toast/toast.stories.tsx +138 -0
  163. package/src/components/toast/toast.tsx +87 -0
  164. package/src/components/tooltip/tooltip.props.ts +11 -0
  165. package/src/components/tooltip/tooltip.stories.tsx +20 -0
  166. package/src/components/tooltip/tooltip.tsx +55 -0
  167. package/src/components/topbar/topbar.props.ts +21 -0
  168. package/src/components/topbar/topbar.stories.tsx +80 -0
  169. package/src/components/topbar/topbar.tsx +205 -0
  170. package/src/components/triple-filter/tripleFilter.props.ts +15 -0
  171. package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
  172. package/src/components/triple-filter/tripleFilter.tsx +50 -0
  173. package/src/hooks/useClickOutside.ts +21 -0
  174. package/src/hooks/useDebouncedSearch.ts +55 -0
  175. package/src/hooks/useEditableRow.ts +157 -0
  176. package/src/hooks/useTableState.ts +122 -0
  177. package/src/index.css +168 -0
  178. package/src/index.ts +165 -0
  179. package/src/main.tsx +9 -0
  180. package/src/showcases/DataShowcases.tsx +260 -0
  181. package/src/showcases/FeedbackShowcases.tsx +268 -0
  182. package/src/showcases/FormShowcases.tsx +1159 -0
  183. package/src/showcases/HomeShowcase.tsx +324 -0
  184. package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
  185. package/src/showcases/NavigationShowcases.tsx +193 -0
  186. package/src/showcases/PageShowcases.tsx +207 -0
  187. package/src/showcases/ShowcaseLayout.tsx +139 -0
  188. package/src/showcases/StructureShowcases.tsx +152 -0
  189. package/src/types/badget.types.ts +37 -0
  190. package/src/types/button.types.ts +16 -0
  191. package/src/types/colors.types.ts +3 -0
  192. package/src/types/field.types.ts +103 -0
  193. package/src/types/formik.types.ts +15 -0
  194. package/src/types/input.types.ts +14 -0
  195. package/src/types/loader.types.ts +9 -0
  196. package/src/types/sizes.types.ts +1 -0
  197. package/src/types/table.types.ts +15 -0
  198. package/src/types/toast.types.ts +8 -0
  199. package/src/types/yup.types.ts +11 -0
  200. package/src/utils/color.utils.ts +99 -0
  201. package/src/utils/styles.ts +120 -0
  202. package/src/utils/table.utils.ts +10 -0
@@ -0,0 +1,615 @@
1
+
2
+ import clsx from "clsx";
3
+ import { ITInputProps } from "./input.props";
4
+ import { KeyboardEvent, useState, useEffect, useRef, useCallback } from "react";
5
+ import { theme } from "@/theme/theme";
6
+ import { disabledOverlay, iconAbsoluteLeft, iconAbsoluteRight, inputError, inputLabel } from "@/utils/styles";
7
+ import ITText from "@/components/text/text";
8
+
9
+ export default function ITInput({
10
+ name,
11
+ type = "text",
12
+ label,
13
+ placeholder,
14
+ value,
15
+ onChange,
16
+ onBlur,
17
+ disabled = false,
18
+ className,
19
+ containerClassName,
20
+ labelClassName,
21
+ touched,
22
+ error,
23
+ formatNumber = true,
24
+ required = false,
25
+ autoFocus = false,
26
+ onClick,
27
+ onKeyDown,
28
+ iconLeft,
29
+ iconRight,
30
+ maxLength,
31
+ minLength,
32
+ checked,
33
+ showHintLength = false,
34
+ currencyFormat = false,
35
+ rows = 4,
36
+ min,
37
+ max,
38
+ readOnly = false,
39
+ focusContent
40
+ }: ITInputProps) {
41
+ const isCheckboxOrRadio = type === "checkbox" || type === "radio";
42
+ const isNumberType = type === "number";
43
+ const isTextArea = type === "textarea";
44
+
45
+ const [displayValue, setDisplayValue] = useState<string>("");
46
+ const [isFocused, setIsFocused] = useState(false);
47
+ const [hasSelectedAll, setHasSelectedAll] = useState(false);
48
+ const [showPassword, setShowPassword] = useState(false);
49
+ const [localTouched, setLocalTouched] = useState(false);
50
+
51
+ const inputRef = useRef<HTMLInputElement>(null);
52
+
53
+ // Theme logic
54
+ const inputTheme = (theme as any).input || {};
55
+
56
+ const getStyle = () => {
57
+ const style: React.CSSProperties = {
58
+ backgroundColor: inputTheme.backgroundColor,
59
+ borderColor: inputTheme.borderColor,
60
+ borderRadius: inputTheme.borderRadius,
61
+ padding: inputTheme.padding,
62
+ fontSize: inputTheme.fontSize,
63
+ borderWidth: '1px',
64
+ borderStyle: 'solid',
65
+ transition: 'all 0.2s',
66
+ color: 'var(--input-text-color, var(--color-secondary-900))', // Theme-aware text color
67
+ };
68
+
69
+ if (disabled) {
70
+ style.backgroundColor = inputTheme.disabled?.backgroundColor || style.backgroundColor;
71
+ style.borderColor = inputTheme.disabled?.borderColor || style.borderColor;
72
+ style.opacity = 0.7; // Visual cue
73
+ }
74
+
75
+ if (hasError) {
76
+ style.borderColor = inputTheme.error?.borderColor || 'red';
77
+ if (isFocused) {
78
+ style.boxShadow = inputTheme.error?.ring;
79
+ }
80
+ } else if (isFocused && !readOnly) {
81
+ style.boxShadow = inputTheme.focus?.ring;
82
+ }
83
+
84
+ if (iconLeft) {
85
+ style.paddingLeft = '2.5rem';
86
+ }
87
+ if (iconRight) {
88
+ style.paddingRight = '2.5rem';
89
+ }
90
+
91
+ return style;
92
+ };
93
+
94
+ const isTouched = touched !== undefined ? touched : localTouched;
95
+ const isEmpty = isCheckboxOrRadio
96
+ ? !checked
97
+ : (value === undefined || value === null || String(value).trim() === "");
98
+
99
+ const effectiveError = error !== undefined && error !== false
100
+ ? (error === true ? "Este campo es requerido" : error)
101
+ : (required && isEmpty ? "Este campo es requerido" : undefined);
102
+
103
+ const hasError = isTouched && !!effectiveError;
104
+ const errorMessage = typeof effectiveError === "string" ? effectiveError : "Este campo es requerido";
105
+
106
+
107
+ const handleClick = (e: React.MouseEvent<HTMLInputElement>) => {
108
+ if (onClick) {
109
+ onClick();
110
+ }
111
+
112
+ if (!readOnly && !hasSelectedAll) {
113
+ e.currentTarget.select();
114
+ setHasSelectedAll(true);
115
+ }
116
+ };
117
+
118
+ const formatValue = useCallback(
119
+ (val: number | string | undefined | null): string => {
120
+ const num =
121
+ typeof val === "string" ? parseFloat(val.replace(/,/g, "")) : val;
122
+
123
+ if (num == null || isNaN(num)) {
124
+ return "";
125
+ }
126
+
127
+ if (currencyFormat) {
128
+ return num.toLocaleString("es-MX", {
129
+ minimumFractionDigits: 2,
130
+ maximumFractionDigits: 2,
131
+ });
132
+ }
133
+
134
+ return num.toString();
135
+ },
136
+ [currencyFormat]
137
+ );
138
+
139
+ const unformatValue = useCallback(
140
+ (val: number | string | undefined | null): string => {
141
+ if (val == null) return "";
142
+
143
+ return String(val).replace(/,/g, "");
144
+ },
145
+ []
146
+ );
147
+
148
+ useEffect(() => {
149
+ if (!isFocused) {
150
+ if (isNumberType) {
151
+ if (formatNumber) {
152
+ setDisplayValue(formatValue(value));
153
+ } else {
154
+ setDisplayValue(String(value ?? ""));
155
+ }
156
+ } else {
157
+ setDisplayValue(String(value ?? ""));
158
+ }
159
+ }
160
+ }, [value, isFocused, isNumberType, formatValue, formatNumber]);
161
+ const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
162
+ if (readOnly || !isNumberType) return;
163
+
164
+ const { key, ctrlKey, metaKey } = e;
165
+ const {
166
+ value: currentValue,
167
+ selectionStart,
168
+ selectionEnd,
169
+ } = e.currentTarget;
170
+
171
+ const allowedKeys = [
172
+ "Backspace",
173
+ "Tab",
174
+ "Escape",
175
+ "Enter",
176
+ "ArrowLeft",
177
+ "ArrowRight",
178
+ "Delete",
179
+ "Home",
180
+ "End",
181
+ "Unidentified" // mobile keyboards
182
+ ];
183
+ if (allowedKeys.includes(key) || ctrlKey || metaKey) {
184
+ return;
185
+ }
186
+
187
+ if (!currencyFormat && (key === "." || key === ",")) {
188
+ e.preventDefault();
189
+ return;
190
+ }
191
+
192
+ if (
193
+ currencyFormat &&
194
+ (key === "." || key === ",") &&
195
+ currentValue.includes(".")
196
+ ) {
197
+ // Check if the current dot is within the selected range (it will be overwritten)
198
+ const dotIndex = currentValue.indexOf(".");
199
+ const replacingDot = selectionStart !== null && selectionEnd !== null && selectionStart <= dotIndex && dotIndex < selectionEnd;
200
+ if (!replacingDot) {
201
+ e.preventDefault();
202
+ return;
203
+ }
204
+ }
205
+
206
+ const allowedCharsRegex = currencyFormat ? /^[0-9.,]$/ : /^[0-9]$/;
207
+ // If it's a mobile key event like Unidentified, we bypass the regex check safely
208
+ if (key !== "Unidentified" && !allowedCharsRegex.test(key)) {
209
+ e.preventDefault();
210
+ return;
211
+ }
212
+
213
+ if (
214
+ max !== undefined &&
215
+ /^[0-9]$/.test(key) &&
216
+ selectionStart !== null &&
217
+ selectionEnd !== null
218
+ ) {
219
+ const currentUnformatted = unformatValue(currentValue);
220
+ const nextValueStr =
221
+ currentUnformatted.slice(0, selectionStart) +
222
+ key +
223
+ currentUnformatted.slice(selectionEnd);
224
+
225
+ const numericValue = parseFloat(nextValueStr);
226
+
227
+ if (!isNaN(numericValue) && numericValue > max) {
228
+ e.preventDefault();
229
+ }
230
+ }
231
+ };
232
+
233
+ const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
234
+ if (readOnly) return;
235
+
236
+ let rawValue = e.target.value;
237
+ let cleanedValue = "";
238
+
239
+ if (currencyFormat) {
240
+ if (rawValue.includes(",") && rawValue.includes(".")) {
241
+ rawValue = rawValue.replace(/,/g, "");
242
+ } else if (rawValue.includes(",")) {
243
+ rawValue = rawValue.replace(/,/g, ".");
244
+ }
245
+
246
+ cleanedValue = rawValue.replace(/[^0-9.]/g, "");
247
+ const parts = cleanedValue.split(".");
248
+ if (parts.length > 1) {
249
+ // Keep only first dot, and restrict decimals to 2 digits
250
+ const decimals = parts.slice(1).join("").substring(0, 2);
251
+ cleanedValue = parts[0] + "." + decimals;
252
+ }
253
+ } else {
254
+ cleanedValue = rawValue.replace(/[^0-9]/g, "");
255
+ }
256
+
257
+ setDisplayValue(cleanedValue);
258
+
259
+ if (onChange) {
260
+ let valueToSend: number | string = cleanedValue;
261
+
262
+ if (!formatNumber) {
263
+ valueToSend = cleanedValue;
264
+ } else if (cleanedValue !== "") {
265
+ if (currencyFormat) {
266
+ const numericValue = parseFloat(cleanedValue);
267
+ if (!isNaN(numericValue)) {
268
+ // ALWAYS send string representation to avoid dropping trailing decimals
269
+ valueToSend = cleanedValue;
270
+ }
271
+ } else {
272
+ const numericValue = parseInt(cleanedValue, 10);
273
+ if (!isNaN(numericValue)) {
274
+ valueToSend = cleanedValue;
275
+ }
276
+ }
277
+ }
278
+
279
+ const newEvent = {
280
+ ...e,
281
+ target: {
282
+ ...e.target,
283
+ name,
284
+ value: valueToSend.toString(),
285
+ },
286
+ };
287
+ onChange(newEvent);
288
+ }
289
+ };
290
+
291
+ const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
292
+ if (readOnly) return;
293
+
294
+ const value = e.target.value;
295
+
296
+ if(maxLength){
297
+ if(value.length > maxLength) return;
298
+ }
299
+ if (onChange) {
300
+
301
+ const newEvent = {
302
+ ...e,
303
+ target: {
304
+ ...e.target,
305
+ name,
306
+ value: value,
307
+ },
308
+ };
309
+ onChange(newEvent);
310
+ }
311
+
312
+ }
313
+
314
+ const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
315
+ setIsFocused(true);
316
+ setHasSelectedAll(false);
317
+ if (readOnly || !isNumberType) return;
318
+
319
+ setDisplayValue(unformatValue(value));
320
+ e.currentTarget.select();
321
+ };
322
+
323
+ const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
324
+ setLocalTouched(true);
325
+ setHasSelectedAll(false);
326
+ setIsFocused(false);
327
+ if (readOnly) {
328
+ onBlur?.(e);
329
+ return;
330
+ }
331
+
332
+ if (isNumberType) {
333
+ const currentValue = displayValue;
334
+
335
+ // Caso cuando formatNumber es false - mantener el valor como string sin parsing
336
+ if (!formatNumber) {
337
+ if (onChange && String(value) !== currentValue) {
338
+ const newEvent = {
339
+ ...e,
340
+ target: {
341
+ ...e.target,
342
+ name,
343
+ value: currentValue, // Mantener como string
344
+ },
345
+ };
346
+ onChange(newEvent);
347
+ }
348
+ onBlur?.(e);
349
+ return;
350
+ }
351
+
352
+ // Lógica original para cuando formatNumber es true
353
+ let numericValue: number | undefined = undefined;
354
+ let valueToSend: number | string | undefined = undefined;
355
+
356
+ let cleanedValue = "";
357
+ if (currencyFormat) {
358
+ cleanedValue = currentValue.replace(/[^0-9.]/g, "");
359
+ const parts = cleanedValue.split(".");
360
+ if (parts.length > 2) {
361
+ cleanedValue = parts[0] + "." + parts.slice(1).join("");
362
+ }
363
+ if (cleanedValue === ".") cleanedValue = "";
364
+ } else {
365
+ cleanedValue = currentValue.replace(/[^0-9]/g, "");
366
+ }
367
+
368
+ const parsed = currencyFormat
369
+ ? parseFloat(cleanedValue)
370
+ : parseInt(cleanedValue, 10);
371
+
372
+ if (!isNaN(parsed)) {
373
+ numericValue = parsed;
374
+
375
+ if (min !== undefined && numericValue < min) {
376
+ numericValue = min;
377
+ }
378
+ if (max !== undefined && numericValue > max) {
379
+ numericValue = max;
380
+ }
381
+ valueToSend = numericValue;
382
+
383
+ setDisplayValue(formatValue(numericValue));
384
+ } else {
385
+ setDisplayValue("");
386
+ valueToSend = undefined;
387
+ }
388
+
389
+ if (onChange && String(value) !== String(valueToSend)) {
390
+ const newEvent = {
391
+ ...e,
392
+ target: {
393
+ ...e.target,
394
+ name,
395
+ value: valueToSend,
396
+ },
397
+ };
398
+ onChange(newEvent);
399
+ } else if (
400
+ String(value) === String(valueToSend) &&
401
+ displayValue !== formatValue(value) &&
402
+ !isNaN(parsed)
403
+ ) {
404
+ setDisplayValue(formatValue(value));
405
+ } else if (isNaN(parsed)) {
406
+ setDisplayValue("");
407
+ }
408
+ }
409
+ onBlur?.(e);
410
+ };
411
+
412
+ const currentLength = isNumberType
413
+ ? (currencyFormat ? displayValue.replace(/[.,]/g, "") : displayValue).length
414
+ : typeof value === "string"
415
+ ? value.length
416
+ : String(value ?? "").length;
417
+
418
+ return (
419
+ <div className={clsx("w-full", containerClassName)}>
420
+ {isCheckboxOrRadio ? (
421
+ // CHECKBOX / RADIO LAYOUT (Row)
422
+ <div className="flex items-center gap-2">
423
+ <input
424
+ ref={inputRef}
425
+ name={name}
426
+ id={name}
427
+ type={type}
428
+ checked={checked}
429
+ onChange={(e) => {
430
+ setLocalTouched(true);
431
+ handleTextChange(e);
432
+ }}
433
+ onBlur={(e) => {
434
+ setLocalTouched(true);
435
+ onBlur?.(e);
436
+ }}
437
+ onKeyDown={onKeyDown}
438
+ disabled={disabled}
439
+ required={required}
440
+ className={clsx(
441
+ "peer",
442
+ "form-radio h-4 w-4 text-slate-600 focus:ring-slate-500 transition-all duration-200",
443
+ type === "checkbox" && "form-checkbox rounded",
444
+ className,
445
+ { [disabledOverlay]: disabled },
446
+ { "border-red-500": hasError }
447
+ )}
448
+ />
449
+ {label && (
450
+ <label htmlFor={name} className="text-sm text-gray-700 dark:text-slate-300 select-none">
451
+ {label} {required && <ITText as="span" className="text-red-500">*</ITText>}
452
+ </label>
453
+ )}
454
+ </div>
455
+ ) : (
456
+ // TEXT / NUMBER / TEXTAREA LAYOUT (Column)
457
+ <div className="flex flex-col gap-1.5">
458
+ {label && (
459
+ <label
460
+ htmlFor={name}
461
+ className={clsx(
462
+ inputLabel(hasError),
463
+ labelClassName
464
+ )}
465
+ >
466
+ {label}
467
+ {required && <ITText as="span" className="text-red-500 ml-1">*</ITText>}
468
+ </label>
469
+ )}
470
+
471
+ <div className="relative w-full">
472
+ {iconLeft && (
473
+ <div className={iconAbsoluteLeft}>
474
+ {iconLeft}
475
+ </div>
476
+ )}
477
+
478
+ {isTextArea ? (
479
+ <textarea
480
+ name={name}
481
+ id={name}
482
+ placeholder={placeholder}
483
+ value={value ?? ""}
484
+ onChange={readOnly ? undefined : onChange}
485
+ onBlur={(e) => {
486
+ if (readOnly) return;
487
+ setLocalTouched(true);
488
+ onBlur?.(e);
489
+ }}
490
+ onKeyDown={onKeyDown}
491
+ readOnly={readOnly}
492
+ maxLength={maxLength}
493
+ minLength={minLength}
494
+ disabled={disabled}
495
+ required={required}
496
+ autoFocus={autoFocus}
497
+ onClick={onClick}
498
+ rows={rows}
499
+ className={clsx(
500
+ "peer",
501
+ "focus:outline-none w-full resize-none",
502
+ className,
503
+ { "cursor-not-allowed": disabled }
504
+ )}
505
+ style={getStyle()}
506
+ />
507
+ ) : (
508
+ <>
509
+ <input
510
+ ref={inputRef}
511
+ name={name}
512
+ id={name}
513
+ type={
514
+ isNumberType
515
+ ? "text"
516
+ : type === "password"
517
+ ? showPassword
518
+ ? "text"
519
+ : "password"
520
+ : type
521
+ }
522
+ inputMode={
523
+ isNumberType
524
+ ? currencyFormat
525
+ ? "decimal"
526
+ : "numeric"
527
+ : undefined
528
+ }
529
+ placeholder={placeholder}
530
+ value={isNumberType ? displayValue : String(value ?? "")}
531
+ // checked not needed here
532
+ onChange={isNumberType ? handleNumberChange : handleTextChange}
533
+ onFocus={isNumberType ? handleFocus : () => setIsFocused(true)}
534
+ onBlur={
535
+ isNumberType
536
+ ? handleBlur
537
+ : (e) => {
538
+ setIsFocused(false);
539
+ setLocalTouched(true);
540
+ onBlur?.(e);
541
+ }
542
+ }
543
+ onKeyDown={isNumberType ? handleKeyDown : onKeyDown}
544
+ readOnly={readOnly}
545
+ maxLength={isNumberType && !currencyFormat ? maxLength : undefined}
546
+ minLength={minLength}
547
+ min={min}
548
+ max={max}
549
+ disabled={disabled}
550
+ required={required}
551
+ autoFocus={autoFocus}
552
+ onClick={focusContent ? handleClick : onClick}
553
+ className={clsx(
554
+ "peer",
555
+ "focus:outline-none w-full",
556
+ className,
557
+ { "cursor-not-allowed": disabled },
558
+ { "pl-10": iconLeft },
559
+ { "pr-10": iconRight || type === "password" }
560
+ )}
561
+ style={getStyle()}
562
+ />
563
+
564
+ {/* Password Toggle Button */}
565
+ {type === "password" && (
566
+ <button
567
+ type="button"
568
+ className="absolute inset-y-0 right-0 flex items-center pr-3 z-10 text-gray-400 hover:text-gray-600 focus:outline-none"
569
+ onClick={() => setShowPassword(!showPassword)}
570
+ tabIndex={-1} // Don't allow tabbing into the eye icon
571
+ >
572
+ {showPassword ? (
573
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/></svg>
574
+ ) : (
575
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>
576
+ )}
577
+ </button>
578
+ )}
579
+ </>
580
+ )}
581
+
582
+ {iconRight && type !== "password" && (
583
+ <div className={iconAbsoluteRight}>
584
+ {iconRight}
585
+ </div>
586
+ )}
587
+ </div>
588
+ </div>
589
+ )}
590
+
591
+ {/* Validation message aligned with input */}
592
+ {hasError && !isCheckboxOrRadio && (
593
+ <div className="flex-shrink-0 min-w-[140px] flex items-center pt-3">
594
+ <ITText as="p" className={inputError}>{errorMessage}</ITText>
595
+ </div>
596
+ )}
597
+
598
+ {/* Length hint below if needed */}
599
+ {showHintLength && (minLength || maxLength) && !isCheckboxOrRadio && (
600
+ <div className="mt-1 text-xs">
601
+ <ITText as="p" className="text-gray-500">
602
+ {currentLength}{maxLength && `/${maxLength}`}
603
+ </ITText>
604
+ </div>
605
+ )}
606
+
607
+ {/* Validation for checkbox/radio - keep below */}
608
+ {isCheckboxOrRadio && hasError && (
609
+ <div className="mt-1 text-xs">
610
+ <ITText as="p" className="text-red-500">{errorMessage}</ITText>
611
+ </div>
612
+ )}
613
+ </div>
614
+ );
615
+ }
@@ -0,0 +1,10 @@
1
+ import { ITTopBarProps } from "../topbar/topbar.props";
2
+ import { ITSidebarProps } from "../sidebar/sidebar.props";
3
+
4
+ export interface ITLayoutProps {
5
+ topBar: ITTopBarProps;
6
+ sidebar: ITSidebarProps;
7
+ children: React.ReactNode;
8
+ className?: string;
9
+ contentClassName?: string;
10
+ }