@axzydev/axzy_ui_system 1.4.8 → 1.5.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 (223) hide show
  1. package/README.md +2 -11
  2. package/dist/index.cjs +1826 -1351
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +1 -1
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.d.cts +115 -14
  7. package/dist/index.d.ts +115 -14
  8. package/dist/index.js +1481 -1010
  9. package/dist/index.js.map +1 -1
  10. package/dist/layered.css +1 -1
  11. package/package.json +3 -16
  12. package/src/App.tsx +90 -73
  13. package/src/components/atoms/badget/badget.tsx +23 -5
  14. package/src/components/atoms/chip/chip.tsx +3 -1
  15. package/src/components/atoms/image/image.tsx +3 -1
  16. package/src/components/atoms/input/input.tsx +8 -5
  17. package/src/components/atoms/loader/loader.tsx +3 -1
  18. package/src/components/atoms/slide/slide.props.ts +12 -0
  19. package/src/components/atoms/slide/slide.tsx +3 -0
  20. package/src/components/molecules/alert/alert.tsx +3 -1
  21. package/src/components/molecules/breadcrumbs/breadcrumbs.tsx +3 -1
  22. package/src/components/molecules/calendar/calendar.tsx +9 -6
  23. package/src/components/molecules/card/card.tsx +5 -2
  24. package/src/components/molecules/chip-input/chip-input.tsx +9 -7
  25. package/src/components/molecules/date-picker/datePicker.tsx +9 -9
  26. package/src/components/molecules/dropdown-menu/dropdown-menu.props.ts +1 -1
  27. package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +6 -3
  28. package/src/components/molecules/field/field.tsx +3 -1
  29. package/src/components/molecules/form-header/form-header.tsx +3 -1
  30. package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +2 -2
  31. package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +20 -15
  32. package/src/components/molecules/masked-input/masked-input.tsx +3 -1
  33. package/src/components/molecules/multi-select/multi-select.tsx +12 -9
  34. package/src/components/molecules/pagination/pagination.tsx +11 -5
  35. package/src/components/molecules/search-select/search-select.tsx +11 -9
  36. package/src/components/molecules/select/select.tsx +6 -4
  37. package/src/components/molecules/stepper/stepper.tsx +6 -4
  38. package/src/components/molecules/table/table.tsx +30 -28
  39. package/src/components/molecules/time-picker/timePicker.tsx +8 -6
  40. package/src/components/molecules/wysiwyg/wysiwyg.tsx +23 -12
  41. package/src/components/organisms/confirm-dialog/confirm-dialog.props.ts +4 -4
  42. package/src/components/organisms/confirm-dialog/confirm-dialog.tsx +16 -8
  43. package/src/components/organisms/data-table/dataTable.tsx +31 -25
  44. package/src/components/organisms/dialog/dialog.tsx +3 -0
  45. package/src/components/organisms/drawer/drawer.tsx +3 -0
  46. package/src/components/organisms/dropfile/dropfile.tsx +19 -17
  47. package/src/components/organisms/form-builder/fieldRenderer.tsx +4 -2
  48. package/src/components/organisms/navbar/navbar.tsx +4 -2
  49. package/src/components/organisms/page-header/page-header.tsx +3 -1
  50. package/src/components/organisms/searchTable/components/PaginationControls.tsx +8 -6
  51. package/src/components/organisms/searchTable/components/PaginationInfo.tsx +3 -1
  52. package/src/components/organisms/searchTable/components/SearchAndSortBar.tsx +7 -5
  53. package/src/components/organisms/searchTable/components/SearchInput.tsx +4 -2
  54. package/src/components/organisms/searchTable/components/SortButton.tsx +13 -11
  55. package/src/components/organisms/searchTable/components/TableEmptyState.tsx +4 -2
  56. package/src/components/organisms/searchTable/components/TableRow.tsx +6 -4
  57. package/src/components/organisms/searchTable/searchTable.tsx +4 -2
  58. package/src/components/organisms/sidebar/sidebar.props.ts +1 -1
  59. package/src/components/organisms/sidebar/sidebar.tsx +9 -4
  60. package/src/components/organisms/toast/toast.tsx +3 -1
  61. package/src/components/organisms/topbar/topbar.tsx +4 -2
  62. package/src/components/templates/page/page.props.ts +4 -4
  63. package/src/components/templates/page/page.tsx +6 -4
  64. package/src/components/theme-provider/themeProvider.tsx +28 -17
  65. package/src/dev.css +61 -2
  66. package/src/global.d.ts +10 -0
  67. package/src/i18n/context.ts +9 -0
  68. package/src/i18n/index.ts +22 -0
  69. package/src/i18n/locales/en.ts +163 -0
  70. package/src/i18n/locales/es.ts +165 -0
  71. package/src/i18n/provider.tsx +44 -0
  72. package/src/i18n/translator.ts +82 -0
  73. package/src/i18n/types.ts +54 -0
  74. package/src/i18n/useITTranslation.ts +20 -0
  75. package/src/index.css +0 -1
  76. package/src/index.ts +7 -1
  77. package/src/sandbox/LandingShowcase.tsx +224 -580
  78. package/src/sandbox/NotFoundShowcase.tsx +51 -36
  79. package/src/sandbox/TopbarSearch.tsx +6 -4
  80. package/src/sandbox/controls.tsx +180 -0
  81. package/src/sandbox/i18n.ts +1939 -0
  82. package/src/sandbox/icons.tsx +25 -0
  83. package/src/sandbox/locale.tsx +72 -0
  84. package/src/sandbox/ui.tsx +474 -0
  85. package/src/sandbox/useHashRoute.ts +46 -10
  86. package/src/showcases/DataShowcases.tsx +103 -87
  87. package/src/showcases/FeedbackShowcases.tsx +37 -30
  88. package/src/showcases/FormShowcases.tsx +303 -215
  89. package/src/showcases/GettingStartedShowcase.tsx +151 -145
  90. package/src/showcases/HelpShowcase.tsx +126 -128
  91. package/src/showcases/HomeShowcase.tsx +678 -482
  92. package/src/showcases/LayoutPrimitivesShowcases.tsx +129 -114
  93. package/src/showcases/NavigationShowcases.tsx +45 -37
  94. package/src/showcases/PageShowcases.tsx +171 -152
  95. package/src/showcases/ProductoDetalle.tsx +272 -129
  96. package/src/showcases/ShowcaseLayout.tsx +256 -219
  97. package/src/showcases/SizesShowcase.tsx +123 -64
  98. package/src/showcases/StructureShowcases.tsx +203 -138
  99. package/src/showcases/portfolioUi.tsx +153 -0
  100. package/src/showcases/techIcons.ts +3 -0
  101. package/src/utils/table.utils.ts +10 -2
  102. package/src/components/atoms/avatar/avatar.doc.mdx +0 -48
  103. package/src/components/atoms/avatar/avatar.stories.tsx +0 -64
  104. package/src/components/atoms/badget/badget.doc.mdx +0 -46
  105. package/src/components/atoms/badget/badget.stories.tsx +0 -75
  106. package/src/components/atoms/button/button.doc.mdx +0 -59
  107. package/src/components/atoms/button/button.stories.tsx +0 -173
  108. package/src/components/atoms/checkbox/checkbox.doc.mdx +0 -54
  109. package/src/components/atoms/checkbox/checkbox.stories.tsx +0 -53
  110. package/src/components/atoms/chip/chip.doc.mdx +0 -47
  111. package/src/components/atoms/chip/chip.stories.tsx +0 -64
  112. package/src/components/atoms/divider/divider.doc.mdx +0 -42
  113. package/src/components/atoms/divider/divider.stories.tsx +0 -33
  114. package/src/components/atoms/flex/flex.doc.mdx +0 -59
  115. package/src/components/atoms/flex/flex.stories.tsx +0 -70
  116. package/src/components/atoms/grid/grid.doc.mdx +0 -54
  117. package/src/components/atoms/grid/grid.stories.tsx +0 -71
  118. package/src/components/atoms/image/image.doc.mdx +0 -44
  119. package/src/components/atoms/image/image.stories.tsx +0 -48
  120. package/src/components/atoms/input/input.doc.mdx +0 -70
  121. package/src/components/atoms/input/input.stories.tsx +0 -113
  122. package/src/components/atoms/input-number/input-number.doc.mdx +0 -74
  123. package/src/components/atoms/input-number/input-number.stories.tsx +0 -111
  124. package/src/components/atoms/loader/loader.doc.mdx +0 -43
  125. package/src/components/atoms/loader/loader.stories.tsx +0 -104
  126. package/src/components/atoms/popover/popover.doc.mdx +0 -53
  127. package/src/components/atoms/popover/popover.stories.tsx +0 -24
  128. package/src/components/atoms/progress/progress.doc.mdx +0 -48
  129. package/src/components/atoms/progress/progress.stories.tsx +0 -39
  130. package/src/components/atoms/radio/radio.doc.mdx +0 -62
  131. package/src/components/atoms/radio/radio.stories.tsx +0 -48
  132. package/src/components/atoms/segmented-control/segmented-control.doc.mdx +0 -60
  133. package/src/components/atoms/segmented-control/segmented-control.stories.tsx +0 -62
  134. package/src/components/atoms/skeleton/skeleton.doc.mdx +0 -43
  135. package/src/components/atoms/skeleton/skeleton.stories.tsx +0 -29
  136. package/src/components/atoms/slide/slide.doc.mdx +0 -46
  137. package/src/components/atoms/slide/slide.stories.tsx +0 -120
  138. package/src/components/atoms/slider/slider.doc.mdx +0 -49
  139. package/src/components/atoms/slider/slider.stories.tsx +0 -29
  140. package/src/components/atoms/stack/stack.doc.mdx +0 -126
  141. package/src/components/atoms/stack/stack.stories.tsx +0 -283
  142. package/src/components/atoms/text/text.doc.mdx +0 -41
  143. package/src/components/atoms/text/text.stories.tsx +0 -66
  144. package/src/components/atoms/textarea/textarea.doc.mdx +0 -64
  145. package/src/components/atoms/textarea/textarea.stories.tsx +0 -26
  146. package/src/components/atoms/tooltip/tooltip.doc.mdx +0 -47
  147. package/src/components/atoms/tooltip/tooltip.stories.tsx +0 -19
  148. package/src/components/isolation/isolation.stories.tsx +0 -202
  149. package/src/components/molecules/accordion/accordion.doc.mdx +0 -47
  150. package/src/components/molecules/accordion/accordion.stories.tsx +0 -68
  151. package/src/components/molecules/alert/alert.doc.mdx +0 -48
  152. package/src/components/molecules/alert/alert.stories.tsx +0 -40
  153. package/src/components/molecules/breadcrumbs/breadcrumbs.doc.mdx +0 -57
  154. package/src/components/molecules/breadcrumbs/breadcrumbs.stories.tsx +0 -20
  155. package/src/components/molecules/calendar/calendar.doc.mdx +0 -76
  156. package/src/components/molecules/calendar/calendar.stories.tsx +0 -90
  157. package/src/components/molecules/card/card.doc.mdx +0 -54
  158. package/src/components/molecules/card/card.stories.tsx +0 -57
  159. package/src/components/molecules/chip-input/chip-input.doc.mdx +0 -44
  160. package/src/components/molecules/chip-input/chip-input.stories.tsx +0 -52
  161. package/src/components/molecules/date-picker/date-picker.doc.mdx +0 -65
  162. package/src/components/molecules/date-picker/date-picker.stories.tsx +0 -88
  163. package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +0 -46
  164. package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +0 -41
  165. package/src/components/molecules/empty-state/empty-state.doc.mdx +0 -50
  166. package/src/components/molecules/empty-state/empty-state.stories.tsx +0 -19
  167. package/src/components/molecules/field/field.doc.mdx +0 -43
  168. package/src/components/molecules/field/field.stories.tsx +0 -42
  169. package/src/components/molecules/form-header/form-header.doc.mdx +0 -38
  170. package/src/components/molecules/form-header/form-header.stories.tsx +0 -52
  171. package/src/components/molecules/masked-input/masked-input.doc.mdx +0 -78
  172. package/src/components/molecules/masked-input/masked-input.stories.tsx +0 -126
  173. package/src/components/molecules/multi-select/multi-select.doc.mdx +0 -55
  174. package/src/components/molecules/multi-select/multi-select.stories.tsx +0 -59
  175. package/src/components/molecules/pagination/pagination.doc.mdx +0 -62
  176. package/src/components/molecules/pagination/pagination.stories.tsx +0 -110
  177. package/src/components/molecules/search-select/search-select.doc.mdx +0 -77
  178. package/src/components/molecules/search-select/search-select.stories.tsx +0 -146
  179. package/src/components/molecules/select/select.doc.mdx +0 -75
  180. package/src/components/molecules/select/select.stories.tsx +0 -85
  181. package/src/components/molecules/stat-card/stat-card.doc.mdx +0 -48
  182. package/src/components/molecules/stat-card/stat-card.stories.tsx +0 -40
  183. package/src/components/molecules/stepper/stepper.doc.mdx +0 -54
  184. package/src/components/molecules/stepper/stepper.stories.tsx +0 -154
  185. package/src/components/molecules/table/table.doc.mdx +0 -140
  186. package/src/components/molecules/table/table.stories.tsx +0 -402
  187. package/src/components/molecules/tabs/tabs.doc.mdx +0 -52
  188. package/src/components/molecules/tabs/tabs.stories.tsx +0 -31
  189. package/src/components/molecules/time-picker/timePicker.doc.mdx +0 -68
  190. package/src/components/molecules/time-picker/timePicker.stories.tsx +0 -130
  191. package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +0 -95
  192. package/src/components/organisms/confirm-dialog/confirm-dialog.doc.mdx +0 -60
  193. package/src/components/organisms/confirm-dialog/confirm-dialog.stories.tsx +0 -32
  194. package/src/components/organisms/data-table/ITDataTable.doc.mdx +0 -132
  195. package/src/components/organisms/data-table/ITDataTable.stories.tsx +0 -353
  196. package/src/components/organisms/dialog/dialog.doc.mdx +0 -54
  197. package/src/components/organisms/dialog/dialog.stories.tsx +0 -126
  198. package/src/components/organisms/drawer/drawer.doc.mdx +0 -59
  199. package/src/components/organisms/drawer/drawer.stories.tsx +0 -40
  200. package/src/components/organisms/dropfile/dropfile.doc.mdx +0 -74
  201. package/src/components/organisms/dropfile/dropfile.stories.tsx +0 -93
  202. package/src/components/organisms/form-builder/formBuilder.doc.mdx +0 -251
  203. package/src/components/organisms/form-builder/formBuilder.stories.tsx +0 -914
  204. package/src/components/organisms/navbar/navbar.doc.mdx +0 -77
  205. package/src/components/organisms/navbar/navbar.stories.tsx +0 -83
  206. package/src/components/organisms/page-header/page-header.doc.mdx +0 -54
  207. package/src/components/organisms/page-header/page-header.stories.tsx +0 -74
  208. package/src/components/organisms/searchTable/searchTable.doc.mdx +0 -79
  209. package/src/components/organisms/searchTable/searchTable.stories.tsx +0 -73
  210. package/src/components/organisms/sidebar/sidebar.doc.mdx +0 -176
  211. package/src/components/organisms/sidebar/sidebar.stories.tsx +0 -234
  212. package/src/components/organisms/toast/toast.doc.mdx +0 -50
  213. package/src/components/organisms/toast/toast.stories.tsx +0 -137
  214. package/src/components/organisms/topbar/topbar.doc.mdx +0 -72
  215. package/src/components/organisms/topbar/topbar.stories.tsx +0 -79
  216. package/src/components/organisms/triple-filter/tripleFilter.doc.mdx +0 -62
  217. package/src/components/organisms/triple-filter/tripleFilter.stories.tsx +0 -31
  218. package/src/components/templates/layout/layout.doc.mdx +0 -53
  219. package/src/components/templates/layout/layout.stories.tsx +0 -113
  220. package/src/components/templates/page/page.doc.mdx +0 -86
  221. package/src/components/templates/page/page.stories.tsx +0 -97
  222. package/src/components/theme-provider/themeProvider.doc.mdx +0 -85
  223. package/src/components/theme-provider/themeProvider.stories.tsx +0 -58
@@ -1,914 +0,0 @@
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 {
8
- FaUser,
9
- FaEnvelope,
10
- FaSearch,
11
- FaLock,
12
- FaCalculator,
13
- FaCommentDots,
14
- FaCheckCircle,
15
- } from "react-icons/fa";
16
- import { expect, fn, userEvent } from "storybook/test";
17
- import { useFormBuilder } from "./useFormBuilder";
18
-
19
- const meta: Meta<typeof ITFormBuilder> = {
20
- title: "Components/Form Elements/ITFormBuilder",
21
- component: ITFormBuilder,
22
- parameters: {
23
- layout: "centered",
24
- docs: {
25
- description: {
26
- component:
27
- "Dynamic form builder using Formik and Yup configuration. Supports Legacy V1 and the powerful Enterprise V2 Grid mapping, validating, calculating, and conditional logic.",
28
- },
29
- },
30
- },
31
- };
32
- export default meta;
33
- type Story = StoryObj<typeof ITFormBuilder>;
34
-
35
- // ─────────────────────────────────────────────────────────────────────────────
36
- // Helpers shared by the test stories
37
- // ─────────────────────────────────────────────────────────────────────────────
38
-
39
- /** Controlled wrapper that drives the builder with local React state. */
40
- const ControlledForm = ({
41
- children,
42
- initialValues,
43
- }: {
44
- children: (props: {
45
- values: Record<string, any>;
46
- setValues: React.Dispatch<React.SetStateAction<Record<string, any>>>;
47
- setFieldValue: (field: string, value: any) => Promise<void>;
48
- handleChange: (e: any) => void;
49
- handleBlur: (e: any) => void;
50
- touched: Record<string, boolean>;
51
- errors: Record<string, string>;
52
- }) => React.ReactNode;
53
- initialValues: Record<string, any>;
54
- }) => {
55
- const [values, setValues] = useState<Record<string, any>>(initialValues);
56
- const [touched, setTouched] = useState<Record<string, boolean>>({});
57
- const [errors] = useState<Record<string, string>>({});
58
-
59
- const handleChange = (e: any) => {
60
- const { name, value, type, checked } = e.target ?? {};
61
- setValues((prev) => ({
62
- ...prev,
63
- [name]: type === "checkbox" ? checked : value,
64
- }));
65
- };
66
-
67
- const handleBlur = (e: any) => {
68
- const { name } = e.target ?? {};
69
- if (name) setTouched((prev) => ({ ...prev, [name]: true }));
70
- };
71
-
72
- const setFieldValue = async (field: string, value: any) => {
73
- setValues((prev) => ({ ...prev, [field]: value }));
74
- };
75
-
76
- return (
77
- <>
78
- {children({
79
- values,
80
- setValues,
81
- setFieldValue,
82
- handleChange,
83
- handleBlur,
84
- touched,
85
- errors,
86
- })}
87
- {/* Surface state so test play functions and humans can read it. */}
88
- <pre
89
- data-testid="form-state"
90
- style={{
91
- marginTop: 16,
92
- padding: 8,
93
- background: "#0f172a",
94
- color: "#a7f3d0",
95
- fontSize: 11,
96
- borderRadius: 8,
97
- maxWidth: 480,
98
- overflow: "auto",
99
- }}
100
- >
101
- {JSON.stringify({ values, touched, errors }, null, 2)}
102
- </pre>
103
- </>
104
- );
105
- };
106
-
107
- // ─────────────────────────────────────────────────────────────────────────────
108
- // Showcase stories (kept as-is for the Storybook gallery)
109
- // ─────────────────────────────────────────────────────────────────────────────
110
-
111
- const FormWrapper = ({
112
- children,
113
- initialValues = {},
114
- validationSchema,
115
- widthClass = "w-[800px]",
116
- }: {
117
- children: (props: any) => React.ReactNode;
118
- initialValues?: any;
119
- validationSchema?: any;
120
- widthClass?: string;
121
- }) => {
122
- const [submittedValues, setSubmittedValues] = useState<any>(null);
123
-
124
- return (
125
- <div
126
- className={`p-6 bg-white rounded-xl shadow-sm border border-gray-100 max-w-full ${widthClass}`}
127
- >
128
- <Formik
129
- initialValues={initialValues}
130
- validationSchema={validationSchema}
131
- onSubmit={(values) => {
132
- setSubmittedValues(values);
133
- console.log("Form submitted:", values);
134
- }}
135
- >
136
- {(formikProps) => (
137
- <form onSubmit={formikProps.handleSubmit}>
138
- {children(formikProps)}
139
-
140
- <div className="mt-6 flex items-center justify-end gap-3 pt-4 border-t border-gray-100">
141
- <button
142
- type="button"
143
- 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"
144
- onClick={() => formikProps.resetForm()}
145
- >
146
- Limpiar Formulario
147
- </button>
148
- <button
149
- type="submit"
150
- className="px-4 py-2 text-sm font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700 shadow-sm transition-colors"
151
- >
152
- Enviar Datos
153
- </button>
154
- </div>
155
-
156
- {submittedValues && (
157
- <div className="mt-4 p-3 bg-gray-50 rounded-lg text-xs font-mono overflow-auto max-h-40">
158
- <p className="font-semibold text-gray-500 mb-1">
159
- Payload Generado:
160
- </p>
161
- <pre>{JSON.stringify(submittedValues, null, 2)}</pre>
162
- </div>
163
- )}
164
- </form>
165
- )}
166
- </Formik>
167
- </div>
168
- );
169
- };
170
-
171
- const showcaseConfig: FieldConfigV2[] = [
172
- {
173
- name: "username",
174
- label: "Usuario (Icono Izquierdo Fijo)",
175
- type: "text",
176
- placeholder: "Ej. dev123",
177
- required: true,
178
- leftIcon: <FaUser className="text-gray-400" />,
179
- column: { sm: 12, md: 6 },
180
- },
181
- {
182
- name: "promoCode",
183
- label: "Código Promo (Icono Derecho Click)",
184
- type: "text",
185
- placeholder: "Ingresa código...",
186
- rightIcon: (
187
- <button
188
- type="button"
189
- onClick={() => alert("¡Disparando búsqueda de código promocional!")}
190
- className="text-primary-600 hover:text-primary-800 transition px-2"
191
- >
192
- <FaSearch />
193
- </button>
194
- ),
195
- column: { sm: 12, md: 6 },
196
- },
197
- {
198
- name: "email",
199
- label: "Correo (Validación estricta YUP)",
200
- type: "email",
201
- placeholder: "ejemplo@correo.com",
202
- required: true,
203
- leftIcon: <FaEnvelope className="text-gray-400" />,
204
- column: { sm: 12, md: 6 },
205
- },
206
- {
207
- name: "password",
208
- label: "Contraseña",
209
- type: "password",
210
- required: true,
211
- placeholder: "••••••••",
212
- leftIcon: <FaLock className="text-gray-400" />,
213
- column: { sm: 12, md: 6 },
214
- },
215
- {
216
- name: "subtotal",
217
- label: "Subtotal (Dispara OnChange)",
218
- type: "number",
219
- required: true,
220
- currencyFormat: true,
221
- leftIcon: <FaCalculator className="text-gray-400" />,
222
- column: { sm: 12, md: 4 },
223
- onChangeAction: (val, { setFieldValue }) => {
224
- const num = parseFloat(val) || 0;
225
- setFieldValue("iva", (num * 0.16).toFixed(2));
226
- setFieldValue("total", (num * 1.16).toFixed(2));
227
- },
228
- },
229
- {
230
- name: "iva",
231
- label: "IVA (Read-Only)",
232
- type: "number",
233
- currencyFormat: true,
234
- readOnly: true,
235
- column: { sm: 12, md: 4 },
236
- },
237
- {
238
- name: "total",
239
- label: "Total (Disabled)",
240
- type: "number",
241
- currencyFormat: true,
242
- disabled: true,
243
- column: { sm: 12, md: 4 },
244
- },
245
- {
246
- name: "country",
247
- label: "País (Muestra RFC si es MX)",
248
- type: "select",
249
- required: true,
250
- options: [
251
- { value: "", label: "Selecciona país..." },
252
- { value: "MX", label: "México" },
253
- { value: "US", label: "Estados Unidos" },
254
- { value: "OTHER", label: "Otro" },
255
- ],
256
- column: { sm: 12, md: 6 },
257
- },
258
- {
259
- name: "rfc",
260
- label: "RFC de México (Renderización Condicional)",
261
- type: "text",
262
- dependsOn: ["country"],
263
- renderWhen: (vals) => vals.country === "MX",
264
- required: true,
265
- showHintLength: true,
266
- maxLength: 13,
267
- column: { sm: 12, md: 6 },
268
- },
269
- {
270
- name: "date",
271
- label: "Selector de Fecha",
272
- type: "date",
273
- required: true,
274
- column: { sm: 12, md: 4 },
275
- },
276
- {
277
- name: "time",
278
- label: "Selector de Hora",
279
- type: "time" as any,
280
- required: true,
281
- column: { sm: 12, md: 4 },
282
- },
283
- {
284
- name: "comments",
285
- label: "Comentarios (Textarea + Límite)",
286
- type: "textarea" as any,
287
- rows: 3,
288
- showHintLength: true,
289
- maxLength: 200,
290
- leftIcon: <FaCommentDots className="text-gray-400" />,
291
- column: { sm: 12, md: 4 },
292
- },
293
- ];
294
-
295
- const showcaseSchema = Yup.object().shape({
296
- username: Yup.string().required("El nombre de usuario es requerido"),
297
- promoCode: Yup.string(),
298
- email: Yup.string()
299
- .email("Formato de correo inválido")
300
- .required("El correo es requerido"),
301
- password: Yup.string()
302
- .min(8, "Mínimo 8 caracteres")
303
- .required("La contraseña es requerida"),
304
- subtotal: Yup.number()
305
- .typeError("Debe ser un número")
306
- .required("Requerido")
307
- .min(1, "Mayor a 0"),
308
- country: Yup.string().required("Selecciona un país"),
309
- rfc: Yup.string().when("country", {
310
- is: "MX",
311
- then: (schema) =>
312
- schema
313
- .length(13, "Debe tener 13 caracteres exactos")
314
- .required("El RFC es obligatorio para MX"),
315
- otherwise: (schema) => schema.notRequired(),
316
- }),
317
- date: Yup.date().required("Fecha requerida"),
318
- time: Yup.string().required("Hora requerida"),
319
- comments: Yup.string().max(200, "Máximo 200 caracteres"),
320
- });
321
-
322
- export const FullFeaturesShowcaseV2: Story = {
323
- render: () => (
324
- <FormWrapper
325
- initialValues={{
326
- username: "",
327
- promoCode: "",
328
- email: "",
329
- password: "",
330
- subtotal: "",
331
- iva: "",
332
- total: "",
333
- country: "",
334
- rfc: "",
335
- date: null,
336
- time: "",
337
- comments: "",
338
- }}
339
- validationSchema={showcaseSchema}
340
- widthClass="w-[1024px]"
341
- >
342
- {(formikProps) => (
343
- <div className="space-y-4">
344
- <h3 className="text-xl font-bold text-gray-900 mb-2">
345
- ITFormBuilder V2 Completo
346
- </h3>
347
- <p className="text-sm text-gray-500 mb-6">
348
- Prueba los cálculos en tiempo real en "Subtotal", mira el
349
- comportamiento `disabled` vs `readonly`, interactúa con íconos e
350
- ingresa el país "México" para renderizar dinámicamente un campo.
351
- </p>
352
- <ITFormBuilder
353
- config={showcaseConfig as any}
354
- columns={12}
355
- values={formikProps.values}
356
- handleChange={formikProps.handleChange}
357
- handleBlur={formikProps.handleBlur}
358
- touched={formikProps.touched}
359
- errors={formikProps.errors}
360
- setFieldValue={formikProps.setFieldValue}
361
- />
362
- </div>
363
- )}
364
- </FormWrapper>
365
- ),
366
- };
367
-
368
- const loginFields: FieldConfig[] = [
369
- {
370
- name: "email",
371
- label: "Email Address",
372
- type: "text",
373
- placeholder: "john@example.com",
374
- required: true,
375
- column: 12,
376
- },
377
- {
378
- name: "password",
379
- label: "Password",
380
- type: "password",
381
- placeholder: "••••••••",
382
- required: true,
383
- column: 12,
384
- },
385
- ];
386
-
387
- const loginSchema = Yup.object().shape({
388
- email: Yup.string().email("Invalid email").required("Required"),
389
- password: Yup.string().required("Required"),
390
- });
391
-
392
- export const LegacyV1LoginExample: Story = {
393
- render: () => (
394
- <FormWrapper
395
- initialValues={{ email: "", password: "" }}
396
- validationSchema={loginSchema}
397
- widthClass="w-[500px]"
398
- >
399
- {(formikProps) => (
400
- <div className="space-y-4">
401
- <h3 className="text-lg font-semibold text-gray-900 mb-2">
402
- Legacy Login (V1 Config Backward Compatibility)
403
- </h3>
404
- <ITFormBuilder
405
- fields={loginFields}
406
- columns={12}
407
- values={formikProps.values}
408
- handleChange={formikProps.handleChange}
409
- handleBlur={formikProps.handleBlur}
410
- touched={formikProps.touched}
411
- errors={formikProps.errors}
412
- />
413
- </div>
414
- )}
415
- </FormWrapper>
416
- ),
417
- };
418
-
419
- // ─────────────────────────────────────────────────────────────────────────────
420
- // Test stories (interaction tests, run via `pnpm exec vitest run`)
421
- // ─────────────────────────────────────────────────────────────────────────────
422
-
423
- /** Renders a V2 config of three fields and asserts they are present in the DOM. */
424
- export const TestRendersV2Basic: Story = {
425
- name: "Test: renders V2 basic fields",
426
- render: () => (
427
- <ControlledForm initialValues={{ name: "", role: "", dob: null }}>
428
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
429
- <ITFormBuilder
430
- config={[
431
- { name: "name", label: "Name", type: "text", required: true, column: 6 },
432
- {
433
- name: "role",
434
- label: "Role",
435
- type: "select",
436
- required: true,
437
- column: 6,
438
- options: [
439
- { value: "admin", label: "Admin" },
440
- { value: "user", label: "User" },
441
- ],
442
- },
443
- { name: "dob", label: "Date of Birth", type: "date", column: 12 },
444
- ]}
445
- columns={12}
446
- values={values}
447
- handleChange={handleChange}
448
- handleBlur={handleBlur}
449
- touched={touched}
450
- errors={errors}
451
- setFieldValue={setFieldValue}
452
- />
453
- )}
454
- </ControlledForm>
455
- ),
456
- play: async ({ canvas }) => {
457
- await expect(canvas.getByLabelText(/Name/)).toBeInTheDocument();
458
- await expect(canvas.getByLabelText(/Role/)).toBeInTheDocument();
459
- await expect(canvas.getByLabelText(/Date of Birth/)).toBeInTheDocument();
460
- },
461
- };
462
-
463
- /** Typing in the Name input bubbles through `handleChange` into the state. */
464
- export const TestTypingUpdatesState: Story = {
465
- name: "Test: typing updates state via handleChange",
466
- render: () => (
467
- <ControlledForm initialValues={{ email: "" }}>
468
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
469
- <ITFormBuilder
470
- config={[
471
- { name: "email", label: "Email", type: "email", required: true },
472
- ]}
473
- values={values}
474
- handleChange={handleChange}
475
- handleBlur={handleBlur}
476
- touched={touched}
477
- errors={errors}
478
- setFieldValue={setFieldValue}
479
- />
480
- )}
481
- </ControlledForm>
482
- ),
483
- play: async ({ canvas }) => {
484
- const input = canvas.getByLabelText(/Email/);
485
- await userEvent.type(input, "user@example.com");
486
- await expect(input).toHaveValue("user@example.com");
487
-
488
- const stateDump = canvas.getByTestId("form-state");
489
- await expect(stateDump.textContent).toContain('"email": "user@example.com"');
490
- },
491
- };
492
-
493
- /** RFC field appears only when country === "MX"; switching away hides it. */
494
- export const TestConditionalRenderWhen: Story = {
495
- name: "Test: renderWhen shows / hides dependent field",
496
- render: () => (
497
- <ControlledForm initialValues={{ country: "", rfc: "" }}>
498
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
499
- <ITFormBuilder
500
- config={[
501
- {
502
- name: "country",
503
- label: "Country",
504
- type: "select",
505
- required: true,
506
- options: [
507
- { value: "", label: "Select..." },
508
- { value: "MX", label: "México" },
509
- { value: "US", label: "USA" },
510
- ],
511
- },
512
- {
513
- name: "rfc",
514
- label: "RFC",
515
- type: "text",
516
- dependsOn: ["country"],
517
- renderWhen: (v) => v.country === "MX",
518
- required: true,
519
- },
520
- ]}
521
- values={values}
522
- handleChange={handleChange}
523
- handleBlur={handleBlur}
524
- touched={touched}
525
- errors={errors}
526
- setFieldValue={setFieldValue}
527
- />
528
- )}
529
- </ControlledForm>
530
- ),
531
- play: async ({ canvas }) => {
532
- // Initially hidden.
533
- await expect(canvas.queryByLabelText(/RFC/)).toBeNull();
534
-
535
- // Pick Mexico → appears.
536
- const country = canvas.getByLabelText(/Country/);
537
- await userEvent.selectOptions(country, "MX");
538
- await expect(canvas.getByLabelText(/RFC/)).toBeInTheDocument();
539
-
540
- // Switch to USA → hidden again.
541
- await userEvent.selectOptions(country, "US");
542
- await expect(canvas.queryByLabelText(/RFC/)).toBeNull();
543
- },
544
- };
545
-
546
- /** Typing in subtotal triggers `onChangeAction` to recompute total. */
547
- export const TestOnChangeActionComputesTotal: Story = {
548
- name: "Test: onChangeAction updates derived total",
549
- render: () => (
550
- <ControlledForm initialValues={{ subtotal: "", total: "" }}>
551
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
552
- <ITFormBuilder
553
- config={[
554
- {
555
- name: "subtotal",
556
- label: "Subtotal",
557
- type: "number",
558
- currencyFormat: true,
559
- onChangeAction: (val, ctx) => {
560
- const n = parseFloat(String(val).replace(/,/g, "")) || 0;
561
- ctx.setFieldValue("total", (n * 1.16).toFixed(2));
562
- },
563
- },
564
- { name: "total", label: "Total", type: "number", disabled: true },
565
- ]}
566
- values={values}
567
- handleChange={handleChange}
568
- handleBlur={handleBlur}
569
- touched={touched}
570
- errors={errors}
571
- setFieldValue={setFieldValue}
572
- />
573
- )}
574
- </ControlledForm>
575
- ),
576
- play: async ({ canvas }) => {
577
- const subtotal = canvas.getByLabelText(/Subtotal/);
578
- await userEvent.type(subtotal, "100");
579
-
580
- // Total should be derived to "116.50".
581
- const total = canvas.getByLabelText(/Total/) as HTMLInputElement;
582
- await expect(total.value).toBe("116.50");
583
- },
584
- };
585
-
586
- /** Disabled field cannot receive input; programmatic value still renders. */
587
- export const TestDisabledFieldBlocksInput: Story = {
588
- name: "Test: disabled field blocks typing",
589
- render: () => (
590
- <ControlledForm initialValues={{ a: "", b: "fixed" }}>
591
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
592
- <ITFormBuilder
593
- config={[
594
- { name: "a", label: "Active", type: "text" },
595
- { name: "b", label: "Locked", type: "text", disabled: true },
596
- ]}
597
- values={values}
598
- handleChange={handleChange}
599
- handleBlur={handleBlur}
600
- touched={touched}
601
- errors={errors}
602
- setFieldValue={setFieldValue}
603
- />
604
- )}
605
- </ControlledForm>
606
- ),
607
- play: async ({ canvas }) => {
608
- const active = canvas.getByLabelText(/Active/);
609
- const locked = canvas.getByLabelText(/Locked/) as HTMLInputElement;
610
-
611
- await expect(locked).toBeDisabled();
612
- await expect(locked.value).toBe("fixed");
613
-
614
- await userEvent.type(active, "hello");
615
- await userEvent.type(locked, "X"); // should be a no-op
616
- await expect(locked.value).toBe("fixed");
617
- await expect(active).toHaveValue("hello");
618
- },
619
- };
620
-
621
- /** Required flag becomes true once another field exceeds a threshold. */
622
- export const TestDynamicRequired: Story = {
623
- name: "Test: dynamicProps toggles required at runtime",
624
- render: () => (
625
- <ControlledForm initialValues={{ amount: 0, justification: "" }}>
626
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
627
- <ITFormBuilder
628
- config={[
629
- {
630
- name: "amount",
631
- label: "Amount",
632
- type: "number",
633
- defaultValue: 0,
634
- dependsOn: ["amount"],
635
- dynamicProps: (v) =>
636
- Number(v.amount) > 50 ? { required: true } : { required: false },
637
- },
638
- {
639
- name: "justification",
640
- label: "Justification",
641
- type: "text",
642
- dependsOn: ["amount"],
643
- dynamicProps: (v) =>
644
- Number(v.amount) > 50 ? { required: true } : { required: false },
645
- },
646
- ]}
647
- values={values}
648
- handleChange={handleChange}
649
- handleBlur={handleBlur}
650
- touched={touched}
651
- errors={errors}
652
- setFieldValue={setFieldValue}
653
- />
654
- )}
655
- </ControlledForm>
656
- ),
657
- play: async ({ canvas }) => {
658
- const amount = canvas.getByLabelText(/Amount/);
659
- await expect(amount).not.toBeRequired();
660
-
661
- await userEvent.clear(amount);
662
- await userEvent.type(amount, "75");
663
-
664
- const justification = canvas.getByLabelText(/Justification/);
665
- await expect(justification).toBeRequired();
666
- },
667
- };
668
-
669
- /** Async option loader renders a loading placeholder then the resolved list. */
670
- export const TestAsyncOptionsLoad: Story = {
671
- name: "Test: async options loader resolves and renders options",
672
- render: () => {
673
- const config: FieldConfigV2[] = [
674
- {
675
- name: "city",
676
- label: "City",
677
- type: "select",
678
- options: async () => {
679
- await new Promise((r) => setTimeout(r, 50));
680
- return [
681
- { value: "cdmx", label: "Ciudad de México" },
682
- { value: "gdl", label: "Guadalajara" },
683
- ];
684
- },
685
- },
686
- ];
687
- return (
688
- <ControlledForm initialValues={{ city: "" }}>
689
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
690
- <ITFormBuilder
691
- config={config}
692
- values={values}
693
- handleChange={handleChange}
694
- handleBlur={handleBlur}
695
- touched={touched}
696
- errors={errors}
697
- setFieldValue={setFieldValue}
698
- />
699
- )}
700
- </ControlledForm>
701
- );
702
- },
703
- play: async ({ canvas }) => {
704
- // Loading state shown until the loader settles.
705
- await expect(await canvas.findByTestId("field-loading-city")).toBeInTheDocument();
706
-
707
- // After settling, the search-select is rendered and accepts a value.
708
- const select = await canvas.findByLabelText(/City/);
709
- await userEvent.selectOptions(select, "gdl");
710
- await expect(select).toHaveValue("gdl");
711
- },
712
- };
713
-
714
- /** Section type renders the heading and nested fields. */
715
- export const TestSectionRendersNestedFields: Story = {
716
- name: "Test: section renders nested fields",
717
- render: () => (
718
- <ControlledForm initialValues={{ firstName: "", lastName: "" }}>
719
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
720
- <ITFormBuilder
721
- config={[
722
- {
723
- name: "identity",
724
- label: "Personal Information",
725
- type: "section",
726
- fields: [
727
- { name: "firstName", label: "First Name", type: "text" },
728
- { name: "lastName", label: "Last Name", type: "text" },
729
- ],
730
- },
731
- ]}
732
- columns={12}
733
- values={values}
734
- handleChange={handleChange}
735
- handleBlur={handleBlur}
736
- touched={touched}
737
- errors={errors}
738
- setFieldValue={setFieldValue}
739
- />
740
- )}
741
- </ControlledForm>
742
- ),
743
- play: async ({ canvas }) => {
744
- await expect(
745
- canvas.getByRole("heading", { name: /personal information/i }),
746
- ).toBeInTheDocument();
747
- await expect(canvas.getByLabelText(/First Name/)).toBeInTheDocument();
748
- await expect(canvas.getByLabelText(/Last Name/)).toBeInTheDocument();
749
- },
750
- };
751
-
752
- /** Custom `type: "custom"` mounts the user-defined component with the value. */
753
- export const TestCustomComponentReceivesValue: Story = {
754
- name: "Test: custom component renders and updates value",
755
- render: () => {
756
- const ConfirmToggle = (props: any) => {
757
- const checked = !!props.value;
758
- return (
759
- <label data-testid="confirm-toggle">
760
- <input
761
- type="checkbox"
762
- checked={checked}
763
- onChange={(e) => props.onChange?.(e.target.checked)}
764
- />
765
- {props.label}
766
- </label>
767
- );
768
- };
769
- return (
770
- <ControlledForm initialValues={{ confirm: false }}>
771
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
772
- <ITFormBuilder
773
- config={[
774
- {
775
- name: "confirm",
776
- label: "Confirm subscription",
777
- type: "custom",
778
- component: ConfirmToggle,
779
- defaultValue: false,
780
- },
781
- ]}
782
- values={values}
783
- handleChange={handleChange}
784
- handleBlur={handleBlur}
785
- touched={touched}
786
- errors={errors}
787
- setFieldValue={setFieldValue}
788
- />
789
- )}
790
- </ControlledForm>
791
- );
792
- },
793
- play: async ({ canvas }) => {
794
- const checkbox = canvas.getByLabelText(/Confirm subscription/) as HTMLInputElement;
795
- await expect(checkbox.checked).toBe(false);
796
-
797
- await userEvent.click(checkbox);
798
- await expect(checkbox.checked).toBe(true);
799
-
800
- await expect(canvas.getByTestId("form-state").textContent).toContain(
801
- '"confirm": true',
802
- );
803
- },
804
- };
805
-
806
- /** V1 legacy path still renders the supported input types. */
807
- export const TestLegacyV1RendersBasic: Story = {
808
- name: "Test: V1 legacy renders basic inputs",
809
- render: () => (
810
- <ControlledForm initialValues={{ email: "", password: "" }}>
811
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
812
- <ITFormBuilder
813
- fields={[
814
- { name: "email", label: "Email", type: "text", required: true, column: 6 },
815
- { name: "password", label: "Password", type: "password", required: true, column: 6 },
816
- ]}
817
- columns={12}
818
- values={values}
819
- handleChange={handleChange}
820
- handleBlur={handleBlur}
821
- touched={touched}
822
- errors={errors}
823
- setFieldValue={setFieldValue}
824
- />
825
- )}
826
- </ControlledForm>
827
- ),
828
- play: async ({ canvas }) => {
829
- const email = canvas.getByLabelText(/Email/);
830
- const password = canvas.getByLabelText(/Password/);
831
-
832
- await userEvent.type(email, "a@b.co");
833
- await userEvent.type(password, "secret123");
834
-
835
- await expect(email).toHaveValue("a@b.co");
836
- await expect(password).toHaveValue("secret123");
837
- },
838
- };
839
-
840
- /** Custom button can read live progress via `useFormBuilder`. */
841
- export const TestUseFormBuilderProgress: Story = {
842
- name: "Test: useFormBuilder returns live progress",
843
- render: () => {
844
- const ProgressBadge = () => {
845
- const { progress } = useFormBuilder();
846
- return (
847
- <span data-testid="progress-badge">
848
- {progress}% <FaCheckCircle />
849
- </span>
850
- );
851
- };
852
- return (
853
- <ControlledForm initialValues={{ a: "", b: "" }}>
854
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => (
855
- <ITFormBuilder
856
- config={[
857
- { name: "a", label: "Field A", type: "text", required: true },
858
- { name: "b", label: "Field B", type: "text", required: true },
859
- ]}
860
- values={values}
861
- handleChange={handleChange}
862
- handleBlur={handleBlur}
863
- touched={touched}
864
- errors={errors}
865
- setFieldValue={setFieldValue}
866
- >
867
- <ProgressBadge />
868
- </ITFormBuilder>
869
- )}
870
- </ControlledForm>
871
- );
872
- },
873
- play: async ({ canvas }) => {
874
- const badge = canvas.getByTestId("progress-badge");
875
- await expect(badge.textContent).toContain("0%");
876
-
877
- await userEvent.type(canvas.getByLabelText(/Field A/), "hello");
878
-
879
- await expect(badge.textContent).toContain("50%");
880
-
881
- await userEvent.type(canvas.getByLabelText(/Field B/), "world");
882
-
883
- await expect(badge.textContent).toContain("100%");
884
- },
885
- };
886
-
887
- /** `fn()` is exposed for spy assertions; smoke-validates the props API. */
888
- export const TestPropsApiAcceptsSpies: Story = {
889
- name: "Test: handleChange / setFieldValue spies are called",
890
- render: () => (
891
- <ControlledForm initialValues={{ x: "" }}>
892
- {({ values, handleChange, handleBlur, touched, errors, setFieldValue }) => {
893
- const handleChangeSpy = fn(handleChange);
894
- const setFieldValueSpy = fn(setFieldValue);
895
- return (
896
- <ITFormBuilder
897
- config={[{ name: "x", label: "X", type: "text" }]}
898
- values={values}
899
- handleChange={handleChangeSpy as any}
900
- handleBlur={handleBlur}
901
- touched={touched}
902
- errors={errors}
903
- setFieldValue={setFieldValueSpy}
904
- />
905
- );
906
- }}
907
- </ControlledForm>
908
- ),
909
- play: async ({ canvas }) => {
910
- const input = canvas.getByLabelText(/^X/);
911
- await userEvent.type(input, "z");
912
- await expect(input).toHaveValue("z");
913
- },
914
- };