@axzydev/axzy_ui_system 1.2.1 → 1.2.3
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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/dev.css +2 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- 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,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
|
+
}
|