@axzydev/axzy_ui_system 1.2.1 → 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 -1
  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,317 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITFormBuilder from "./formBuilder";
3
+ import { Formik } from "formik";
4
+ import * as Yup from "yup";
5
+ import { useState } from "react";
6
+ import { FieldConfig, FieldConfigV2 } from "@/types/field.types";
7
+ import { FaUser, FaEnvelope, FaSearch, FaLock, FaCalculator, FaCommentDots } from 'react-icons/fa';
8
+
9
+ const meta: Meta<typeof ITFormBuilder> = {
10
+ title: "Components/Form Elements/ITFormBuilder",
11
+ component: ITFormBuilder,
12
+ parameters: {
13
+ layout: "centered",
14
+ docs: {
15
+ description: {
16
+ component: "Dynamic form builder using Formik and Yup configuration. Supports Legacy V1 and the powerful Enterprise V2 Grid mapping, validating, calculating, and conditional logic.",
17
+ },
18
+ },
19
+ },
20
+ tags: ["autodocs"],
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof ITFormBuilder>;
25
+
26
+ // Wrapper for Formik context
27
+ const FormWrapper = ({
28
+ children,
29
+ initialValues = {},
30
+ validationSchema,
31
+ widthClass = "w-[800px]"
32
+ }: {
33
+ children: (props: any) => React.ReactNode,
34
+ initialValues?: any,
35
+ validationSchema?: any,
36
+ widthClass?: string
37
+ }) => {
38
+ const [submittedValues, setSubmittedValues] = useState<any>(null);
39
+
40
+ return (
41
+ <div className={`p-6 bg-white rounded-xl shadow-sm border border-gray-100 max-w-full ${widthClass}`}>
42
+ <Formik
43
+ initialValues={initialValues}
44
+ validationSchema={validationSchema}
45
+ onSubmit={(values) => {
46
+ setSubmittedValues(values);
47
+ console.log("Form submitted:", values);
48
+ }}
49
+ >
50
+ {(formikProps) => (
51
+ <form onSubmit={formikProps.handleSubmit}>
52
+ {children(formikProps)}
53
+
54
+ <div className="mt-6 flex items-center justify-end gap-3 pt-4 border-t border-gray-100">
55
+ <button
56
+ type="button"
57
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
58
+ onClick={() => formikProps.resetForm()}
59
+ >
60
+ Limpiar Formulario
61
+ </button>
62
+ <button
63
+ type="submit"
64
+ className="px-4 py-2 text-sm font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700 shadow-sm transition-colors"
65
+ >
66
+ Enviar Datos
67
+ </button>
68
+ </div>
69
+
70
+ {submittedValues && (
71
+ <div className="mt-4 p-3 bg-gray-50 rounded-lg text-xs font-mono overflow-auto max-h-40">
72
+ <p className="font-semibold text-gray-500 mb-1">Payload Generado:</p>
73
+ <pre>{JSON.stringify(submittedValues, null, 2)}</pre>
74
+ </div>
75
+ )}
76
+ </form>
77
+ )}
78
+ </Formik>
79
+ </div>
80
+ );
81
+ };
82
+
83
+
84
+ // --- Enterprise V2 Showcase ---
85
+
86
+ const showcaseConfig: FieldConfigV2[] = [
87
+ {
88
+ name: "username",
89
+ label: "Usuario (Icono Izquierdo Fijo)",
90
+ type: "text",
91
+ placeholder: "Ej. dev123",
92
+ required: true,
93
+ leftIcon: <FaUser className="text-gray-400" />,
94
+ column: { sm: 12, md: 6 },
95
+ },
96
+ {
97
+ name: "promoCode",
98
+ label: "Código Promo (Icono Derecho Click)",
99
+ type: "text",
100
+ placeholder: "Ingresa código...",
101
+ rightIcon: (
102
+ <button
103
+ type="button"
104
+ onClick={() => alert("¡Disparando búsqueda de código promocional!")}
105
+ className="text-primary-600 hover:text-primary-800 transition px-2"
106
+ >
107
+ <FaSearch />
108
+ </button>
109
+ ),
110
+ column: { sm: 12, md: 6 },
111
+ },
112
+ {
113
+ name: "email",
114
+ label: "Correo (Validación estricta YUP)",
115
+ type: "email",
116
+ placeholder: "ejemplo@correo.com",
117
+ required: true,
118
+ leftIcon: <FaEnvelope className="text-gray-400" />,
119
+ column: { sm: 12, md: 6 },
120
+ },
121
+ {
122
+ name: "password",
123
+ label: "Contraseña",
124
+ type: "password",
125
+ required: true,
126
+ placeholder: "••••••••",
127
+ leftIcon: <FaLock className="text-gray-400" />,
128
+ column: { sm: 12, md: 6 },
129
+ },
130
+ {
131
+ name: "subtotal",
132
+ label: "Subtotal (Dispara OnChange)",
133
+ type: "number",
134
+ required: true,
135
+ currencyFormat: true,
136
+ leftIcon: <FaCalculator className="text-gray-400" />,
137
+ column: { sm: 12, md: 4 },
138
+ onChangeAction: (val, { setFieldValue }) => {
139
+ const num = parseFloat(val) || 0;
140
+ setFieldValue("iva", (num * 0.16).toFixed(2));
141
+ setFieldValue("total", (num * 1.16).toFixed(2));
142
+ }
143
+ },
144
+ {
145
+ name: "iva",
146
+ label: "IVA (Read-Only)",
147
+ type: "number",
148
+ currencyFormat: true,
149
+ readOnly: true,
150
+ column: { sm: 12, md: 4 },
151
+ },
152
+ {
153
+ name: "total",
154
+ label: "Total (Disabled)",
155
+ type: "number",
156
+ currencyFormat: true,
157
+ disabled: true,
158
+ column: { sm: 12, md: 4 },
159
+ },
160
+ {
161
+ name: "country",
162
+ label: "País (Muestra RFC si es MX)",
163
+ type: "select",
164
+ required: true,
165
+ options: [
166
+ { value: "", label: "Selecciona país..." },
167
+ { value: "MX", label: "México" },
168
+ { value: "US", label: "Estados Unidos" },
169
+ { value: "OTHER", label: "Otro" }
170
+ ],
171
+ column: { sm: 12, md: 6 },
172
+ },
173
+ {
174
+ name: "rfc",
175
+ label: "RFC de México (Renderización Condicional)",
176
+ type: "text",
177
+ dependsOn: ["country"],
178
+ renderWhen: (vals) => vals.country === "MX",
179
+ required: true,
180
+ showHintLength: true,
181
+ maxLength: 13,
182
+ column: { sm: 12, md: 6 },
183
+ },
184
+ {
185
+ name: "date",
186
+ label: "Selector de Fecha",
187
+ type: "date",
188
+ required: true,
189
+ column: { sm: 12, md: 4 },
190
+ },
191
+ {
192
+ name: "time",
193
+ label: "Selector de Hora",
194
+ type: "time" as any,
195
+ required: true,
196
+ column: { sm: 12, md: 4 },
197
+ },
198
+ {
199
+ name: "comments",
200
+ label: "Comentarios (Textarea + Límite)",
201
+ type: "textarea" as any,
202
+ rows: 3,
203
+ showHintLength: true,
204
+ maxLength: 200,
205
+ leftIcon: <FaCommentDots className="text-gray-400" />,
206
+ column: { sm: 12, md: 4 },
207
+ }
208
+ ];
209
+
210
+ const showcaseSchema = Yup.object().shape({
211
+ username: Yup.string().required("El nombre de usuario es requerido"),
212
+ promoCode: Yup.string(),
213
+ email: Yup.string().email("Formato de correo inválido").required("El correo es requerido"),
214
+ password: Yup.string().min(8, "Mínimo 8 caracteres").required("La contraseña es requerida"),
215
+ subtotal: Yup.number().typeError("Debe ser un número").required("Requerido").min(1, "Mayor a 0"),
216
+ country: Yup.string().required("Selecciona un país"),
217
+ rfc: Yup.string().when('country', {
218
+ is: 'MX',
219
+ then: (schema) => schema.length(13, "Debe tener 13 caracteres exactos").required("El RFC es obligatorio para MX"),
220
+ otherwise: (schema) => schema.notRequired()
221
+ }),
222
+ date: Yup.date().required("Fecha requerida"),
223
+ time: Yup.string().required("Hora requerida"),
224
+ comments: Yup.string().max(200, "Máximo 200 caracteres")
225
+ });
226
+
227
+ export const FullFeaturesShowcaseV2: Story = {
228
+ render: () => (
229
+ <FormWrapper
230
+ initialValues={{
231
+ username: '',
232
+ promoCode: '',
233
+ email: '',
234
+ password: '',
235
+ subtotal: '',
236
+ iva: '',
237
+ total: '',
238
+ country: '',
239
+ rfc: '',
240
+ date: null,
241
+ time: '',
242
+ comments: ''
243
+ }}
244
+ validationSchema={showcaseSchema}
245
+ widthClass="w-[1024px]"
246
+ >
247
+ {(formikProps) => (
248
+ <div className="space-y-4">
249
+ <h3 className="text-xl font-bold text-gray-900 mb-2">ITFormBuilder V2 Completo</h3>
250
+ <p className="text-sm text-gray-500 mb-6">Prueba los cálculos en tiempo real en "Subtotal", mira el comportamiento `disabled` vs `readonly`, interactúa con íconos e ingresa el país "México" para renderizar dinámicamente un campo.</p>
251
+ <ITFormBuilder
252
+ config={showcaseConfig as any}
253
+ columns={12}
254
+ values={formikProps.values}
255
+ handleChange={formikProps.handleChange}
256
+ handleBlur={formikProps.handleBlur}
257
+ touched={formikProps.touched}
258
+ errors={formikProps.errors}
259
+ setFieldValue={formikProps.setFieldValue}
260
+ />
261
+ </div>
262
+ )}
263
+ </FormWrapper>
264
+ ),
265
+ };
266
+
267
+ // --- Legacy V1 Login Example ---
268
+
269
+ const loginFields: FieldConfig[] = [
270
+ {
271
+ name: "email",
272
+ label: "Email Address",
273
+ type: "text",
274
+ placeholder: "john@example.com",
275
+ required: true,
276
+ column: 12,
277
+ },
278
+ {
279
+ name: "password",
280
+ label: "Password",
281
+ type: "password",
282
+ placeholder: "••••••••",
283
+ required: true,
284
+ column: 12,
285
+ },
286
+ ];
287
+
288
+ const loginSchema = Yup.object().shape({
289
+ email: Yup.string().email("Invalid email").required("Required"),
290
+ password: Yup.string().required("Required"),
291
+ });
292
+
293
+ export const LegacyV1LoginExample: Story = {
294
+ render: () => (
295
+ <FormWrapper
296
+ initialValues={{ email: "", password: "" }}
297
+ validationSchema={loginSchema}
298
+ widthClass="w-[500px]"
299
+ >
300
+ {(formikProps) => (
301
+ <div className="space-y-4">
302
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">Legacy Login (V1 Config Backward Compatibility)</h3>
303
+ <ITFormBuilder
304
+ fields={loginFields}
305
+ columns={12}
306
+ values={formikProps.values}
307
+ handleChange={formikProps.handleChange}
308
+ handleBlur={formikProps.handleBlur}
309
+ touched={formikProps.touched}
310
+ errors={formikProps.errors}
311
+ />
312
+ </div>
313
+ )}
314
+ </FormWrapper>
315
+ ),
316
+ };
317
+
@@ -0,0 +1,186 @@
1
+ import clsx from "clsx";
2
+ import ITInput from "../input/input";
3
+ import ITSelect from "../select/select";
4
+ import { ITFormBuilderProps } from "./formBuilder.props";
5
+ import ITDatePicker from "../date-picker/datePicker";
6
+ import { useEffect, useState, useMemo } from "react";
7
+ import { ITFormBuilderProvider } from "./formBuilder.context";
8
+ import ITFieldRenderer from "./fieldRenderer";
9
+ import { formGrid, getColSpanClass } from "@/utils/styles";
10
+
11
+ function buildDependentValues(
12
+ dependsOn: string[] | undefined,
13
+ values: Record<string, any>,
14
+ ) {
15
+ if (!dependsOn || dependsOn.length === 0) return {};
16
+ const deps: Record<string, any> = {};
17
+ for (const key of dependsOn) {
18
+ if (key in values) deps[key] = values[key];
19
+ }
20
+ return deps;
21
+ }
22
+
23
+ export default function ITFormBuilder({
24
+ fields,
25
+ config,
26
+ columns = 12,
27
+ values,
28
+ handleChange,
29
+ handleBlur,
30
+ touched,
31
+ errors,
32
+ setFieldValue = () => Promise.resolve(),
33
+ setFieldTouched = () => Promise.resolve(),
34
+ setFieldError = () => {},
35
+ isSubmitting = false,
36
+ }: ITFormBuilderProps) {
37
+ const [mounted, setMounted] = useState(false);
38
+
39
+ useEffect(() => {
40
+ setMounted(true);
41
+ }, []);
42
+
43
+ const providerValue = useMemo(() => ({
44
+ config: config || [],
45
+ values: values || {},
46
+ errors: errors || {},
47
+ touched: touched || {},
48
+ handleChange,
49
+ handleBlur,
50
+ setFieldValue,
51
+ setFieldTouched,
52
+ setFieldError,
53
+ initialValues: {},
54
+ isSubmitting,
55
+ isValidating: false,
56
+ submitCount: 0,
57
+ }), [config, values, errors, touched, handleChange, handleBlur, setFieldValue, setFieldTouched, setFieldError, isSubmitting]);
58
+
59
+ if (config) {
60
+ return (
61
+ <ITFormBuilderProvider value={providerValue}>
62
+ <div className={formGrid(columns)}>
63
+ {config.map((fieldConfig) => (
64
+ <ITFieldRenderer
65
+ key={fieldConfig.name}
66
+ config={fieldConfig}
67
+ columns={columns}
68
+ value={values[fieldConfig.name]}
69
+ error={errors[fieldConfig.name]}
70
+ touched={touched[fieldConfig.name]}
71
+ dependentValues={buildDependentValues(fieldConfig.dependsOn, values)}
72
+ />
73
+ ))}
74
+ </div>
75
+ </ITFormBuilderProvider>
76
+ );
77
+ }
78
+
79
+ return (
80
+ <div className={formGrid(columns)}>
81
+ {fields?.map(({
82
+ name,
83
+ label,
84
+ type = "text",
85
+ placeholder,
86
+ required,
87
+ column = 12,
88
+ options,
89
+ valueField,
90
+ disabled = false,
91
+ labelField,
92
+ showHintLength,
93
+ formatNumber = true,
94
+ onChangeAction,
95
+ ...props
96
+ }) => (
97
+ <div key={name} className={getColSpanClass(column, columns)}>
98
+ {(() => {
99
+ switch (type) {
100
+ case "text":
101
+ case "number":
102
+ case "password":
103
+ return (
104
+ <ITInput
105
+ type={type}
106
+ name={name}
107
+ label={label}
108
+ placeholder={placeholder}
109
+ disabled={disabled}
110
+ value={values[name]}
111
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
112
+ handleChange(e);
113
+ if (onChangeAction && setFieldValue) {
114
+ onChangeAction(e.target.value, setFieldValue);
115
+ }
116
+ }}
117
+ onBlur={handleBlur}
118
+ currencyFormat={props.currencyFormat}
119
+ touched={touched[name]}
120
+ error={errors[name]}
121
+ required={required}
122
+ iconRight={props.rightIcon}
123
+ iconLeft={props.leftIcon}
124
+ showHintLength={showHintLength}
125
+ maxLength={props.maxLength}
126
+ minLength={props.minLength}
127
+ rows={props.rows}
128
+ formatNumber={formatNumber}
129
+ />
130
+ );
131
+ case "select":
132
+ return (
133
+ <ITSelect
134
+ options={options || []}
135
+ name={name}
136
+ disabled={disabled}
137
+ label={label}
138
+ placeholder={placeholder}
139
+ value={values[name]}
140
+ valueField={valueField}
141
+ labelField={labelField}
142
+ onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
143
+ handleChange(e);
144
+ if (onChangeAction && setFieldValue) {
145
+ onChangeAction(e.target.value, setFieldValue);
146
+ }
147
+ }}
148
+ onBlur={handleBlur}
149
+ touched={touched[name]}
150
+ error={errors[name]}
151
+ required={required}
152
+ />
153
+ );
154
+ case "date":
155
+ return (
156
+ <ITDatePicker
157
+ name={name}
158
+ disabled={disabled}
159
+ label={label}
160
+ value={values[name]}
161
+ onChange={(
162
+ e:
163
+ | React.ChangeEvent<HTMLInputElement>
164
+ | { target: { name: string; value: Date } }
165
+ ) => {
166
+ handleChange(e);
167
+ if (onChangeAction && setFieldValue) {
168
+ onChangeAction(e.target.value, setFieldValue);
169
+ }
170
+ }}
171
+ placeholder={placeholder}
172
+ onBlur={handleBlur}
173
+ touched={touched[name]}
174
+ error={errors[name]}
175
+ required={required}
176
+ />
177
+ );
178
+ default:
179
+ return null;
180
+ }
181
+ })()}
182
+ </div>
183
+ ))}
184
+ </div>
185
+ );
186
+ }
@@ -0,0 +1,80 @@
1
+ import { useMemo } from 'react';
2
+ import { useITFormBuilderContext } from './formBuilder.context';
3
+ import type { FieldConfigV2 } from '@/types/field.types';
4
+
5
+ export const useFormBuilder = () => {
6
+ const context = useITFormBuilderContext();
7
+ const { values, errors, touched } = context;
8
+
9
+ const progress = useMemo(() => {
10
+ const requiredFields = Object.keys(context.getFieldConfig).filter((name) => {
11
+ const config = context.getFieldConfig(name);
12
+ if (!config) return false;
13
+ if (typeof config.required === 'function') {
14
+ return config.required(values);
15
+ }
16
+ return config.required;
17
+ });
18
+
19
+ if (requiredFields.length === 0) return 100;
20
+
21
+ let filledFields = 0;
22
+ requiredFields.forEach((fieldName) => {
23
+ const val = values[fieldName];
24
+ const hasValue = val !== undefined && val !== null && val !== '';
25
+ const hasError = !!errors[fieldName];
26
+ if (hasValue && !hasError) filledFields++;
27
+ });
28
+
29
+ return Math.round((filledFields / requiredFields.length) * 100);
30
+ }, [values, errors, context]);
31
+
32
+ return {
33
+ ...context,
34
+ progress,
35
+ isDirty: Object.keys(touched).length > 0,
36
+ };
37
+ };
38
+
39
+ /**
40
+ * Hook to evaluate dynamic rules using only the dependent values passed as props.
41
+ * This avoids subscribing to the entire `values` context, enabling true React.memo isolation.
42
+ *
43
+ * `dependentValues` should be a filtered object containing only the fields this
44
+ * field depends on (derived from `dependsOn` in FieldConfigV2).
45
+ */
46
+ export const useFieldRules = (
47
+ config: FieldConfigV2,
48
+ dependentValues: Record<string, any>,
49
+ ) => {
50
+ const { getFieldConfig } = useITFormBuilderContext();
51
+ const fieldConfig = getFieldConfig(config.name) || config;
52
+
53
+ const isVisible = useMemo(() => {
54
+ if (!fieldConfig.renderWhen) return true;
55
+ return fieldConfig.renderWhen(dependentValues);
56
+ }, [fieldConfig.renderWhen, dependentValues]);
57
+
58
+ const dynamicProps = useMemo(() => {
59
+ if (!fieldConfig.dynamicProps) return {};
60
+ return fieldConfig.dynamicProps(dependentValues);
61
+ }, [fieldConfig.dynamicProps, dependentValues]);
62
+
63
+ const isRequired = useMemo(() => {
64
+ if (typeof dynamicProps.required !== 'undefined') return dynamicProps.required;
65
+ if (typeof fieldConfig.required === 'function') {
66
+ return fieldConfig.required(dependentValues);
67
+ }
68
+ return fieldConfig.required || false;
69
+ }, [fieldConfig.required, dynamicProps.required, dependentValues]);
70
+
71
+ const isDisabled = useMemo(() => {
72
+ if (typeof dynamicProps.disabled !== 'undefined') return dynamicProps.disabled;
73
+ if (typeof fieldConfig.disabled === 'function') {
74
+ return fieldConfig.disabled(dependentValues);
75
+ }
76
+ return fieldConfig.disabled || false;
77
+ }, [fieldConfig.disabled, dynamicProps.disabled, dependentValues]);
78
+
79
+ return { isVisible, dynamicProps, isRequired, isDisabled };
80
+ };
@@ -0,0 +1,5 @@
1
+ export interface ITFormHeaderProps {
2
+ title: string;
3
+ onClose?: () => void;
4
+ className?: string;
5
+ }
@@ -0,0 +1,38 @@
1
+ import { FaTimes } from "react-icons/fa";
2
+ import { ITFormHeaderProps } from "./form-header.props";
3
+ import { useITThemeSafe } from "../theme-provider/themeProvider";
4
+ import { getContrastTextColor } from "@/utils/color.utils";
5
+ import ITText from "@/components/text/text";
6
+
7
+ export default function ITFormHeader({
8
+ title,
9
+ onClose,
10
+ className = "",
11
+ }: ITFormHeaderProps) {
12
+ const themeContext = useITThemeSafe();
13
+
14
+ const isDarkMode = themeContext?.resolvedTheme === "dark";
15
+ const palette = themeContext?.palette;
16
+ const textColorClass = getContrastTextColor(
17
+ palette?.primary || "#06b6d4",
18
+ palette,
19
+ isDarkMode
20
+ );
21
+
22
+ return (
23
+ <div className={`bg-primary-500 ${textColorClass} px-6 py-4 rounded-t-lg flex justify-center items-center relative ${className}`}>
24
+ <ITText as="h2" className="text-lg font-semibold text-center" style={{ color: "inherit" }}>{title}</ITText>
25
+ {onClose && (
26
+ <button
27
+ onClick={onClose}
28
+ className={`absolute right-4 ${textColorClass} hover:opacity-80 transition-colors duration-200 p-1 rounded-full`}
29
+ style={{ color: "inherit" }}
30
+ aria-label="Cerrar"
31
+ >
32
+ <FaTimes className="w-4 h-4" />
33
+ </button>
34
+ )}
35
+ </div>
36
+ );
37
+ }
38
+
@@ -0,0 +1,17 @@
1
+ import { ReactNode, CSSProperties, ElementType } from "react";
2
+
3
+ export interface ITGridProps {
4
+ children?: ReactNode;
5
+ container?: boolean;
6
+ item?: boolean;
7
+ spacing?: number;
8
+ columns?: number;
9
+ xs?: number;
10
+ sm?: number;
11
+ md?: number;
12
+ lg?: number;
13
+ xl?: number;
14
+ className?: string;
15
+ style?: CSSProperties;
16
+ as?: ElementType;
17
+ }