flysoft-react-ui 1.2.4 → 1.2.5
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/AI_CONTEXT.md +1400 -217
- package/AI_INTEGRATION_GUIDE.md +343 -0
- package/INTEGRATION_GUIDE.md +60 -0
- package/README.md +5 -3
- package/dist/components/form-controls/Input.d.ts.map +1 -1
- package/dist/components/layout/Accordion.d.ts +1 -0
- package/dist/components/layout/Accordion.d.ts.map +1 -1
- package/dist/components/layout/DataTable.d.ts.map +1 -1
- package/dist/components/layout/DropdownMenu.d.ts +2 -1
- package/dist/components/layout/DropdownMenu.d.ts.map +1 -1
- package/dist/components/layout/DropdownPanel.d.ts +2 -1
- package/dist/components/layout/DropdownPanel.d.ts.map +1 -1
- package/dist/components/layout/Filter.d.ts +1 -0
- package/dist/components/layout/Filter.d.ts.map +1 -1
- package/dist/components/layout/Menu.d.ts +2 -1
- package/dist/components/layout/Menu.d.ts.map +1 -1
- package/dist/components/layout/TabsGroup.d.ts +1 -0
- package/dist/components/layout/TabsGroup.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11889 -24
- package/dist/index.js.map +1 -1
- package/dist/templates/forms/ContactForm.d.ts +1 -0
- package/dist/templates/forms/ContactForm.d.ts.map +1 -1
- package/dist/templates/forms/LoginForm.d.ts +1 -0
- package/dist/templates/forms/LoginForm.d.ts.map +1 -1
- package/dist/templates/forms/RegistrationForm.d.ts +1 -0
- package/dist/templates/forms/RegistrationForm.d.ts.map +1 -1
- package/dist/templates/layouts/DashboardLayout.d.ts +1 -0
- package/dist/templates/layouts/DashboardLayout.d.ts.map +1 -1
- package/dist/templates/layouts/SidebarLayout.d.ts +1 -0
- package/dist/templates/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/templates/patterns/FormPattern.d.ts +1 -0
- package/dist/templates/patterns/FormPattern.d.ts.map +1 -1
- package/dist/templates/patterns/ListPattern.d.ts +77 -0
- package/dist/templates/patterns/ListPattern.d.ts.map +1 -0
- package/package.json +6 -3
- package/dist/App.d.ts +0 -4
- package/dist/App.d.ts.map +0 -1
- package/dist/App.js +0 -30
- package/dist/components/ThemeSwitcher.js +0 -12
- package/dist/components/form-controls/AutocompleteInput.js +0 -680
- package/dist/components/form-controls/Button.js +0 -211
- package/dist/components/form-controls/Checkbox.js +0 -79
- package/dist/components/form-controls/CurrencyInput.js +0 -106
- package/dist/components/form-controls/DateInput.js +0 -578
- package/dist/components/form-controls/DatePicker.js +0 -144
- package/dist/components/form-controls/Input.js +0 -35
- package/dist/components/form-controls/LinkButton.js +0 -248
- package/dist/components/form-controls/Pagination.js +0 -23
- package/dist/components/form-controls/RadioButtonGroup.js +0 -220
- package/dist/components/form-controls/SearchSelectInput.js +0 -336
- package/dist/components/form-controls/index.js +0 -11
- package/dist/components/index.js +0 -7
- package/dist/components/layout/Accordion.js +0 -67
- package/dist/components/layout/AppLayout.js +0 -230
- package/dist/components/layout/Card.js +0 -54
- package/dist/components/layout/Collection.js +0 -18
- package/dist/components/layout/DataField.js +0 -38
- package/dist/components/layout/DataTable.js +0 -164
- package/dist/components/layout/DropdownMenu.js +0 -176
- package/dist/components/layout/DropdownPanel.js +0 -162
- package/dist/components/layout/Filter.js +0 -629
- package/dist/components/layout/Menu.js +0 -21
- package/dist/components/layout/TabPanel.js +0 -11
- package/dist/components/layout/TabsGroup.js +0 -52
- package/dist/components/layout/index.js +0 -12
- package/dist/components/utils/Avatar.js +0 -77
- package/dist/components/utils/Badge.js +0 -151
- package/dist/components/utils/Dialog.js +0 -44
- package/dist/components/utils/FiltersDialog.js +0 -104
- package/dist/components/utils/Loader.js +0 -44
- package/dist/components/utils/RoadMap.js +0 -139
- package/dist/components/utils/Skeleton.js +0 -10
- package/dist/components/utils/Snackbar.js +0 -136
- package/dist/components/utils/SnackbarContainer.js +0 -26
- package/dist/components/utils/iconUtils.js +0 -40
- package/dist/components/utils/index.js +0 -9
- package/dist/contexts/AppLayoutContext.js +0 -104
- package/dist/contexts/AuthContext.js +0 -224
- package/dist/contexts/CrudContext.js +0 -333
- package/dist/contexts/SnackbarContext.js +0 -41
- package/dist/contexts/ThemeContext.js +0 -197
- package/dist/contexts/index.js +0 -13
- package/dist/contexts/presets.js +0 -311
- package/dist/contexts/types.js +0 -1
- package/dist/docs/AccordionDocs.d.ts +0 -4
- package/dist/docs/AccordionDocs.d.ts.map +0 -1
- package/dist/docs/AccordionDocs.js +0 -21
- package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts +0 -13
- package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/AuthDocs.js +0 -18
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts +0 -2
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.js +0 -22
- package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts +0 -24
- package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/mockAuthService.js +0 -78
- package/dist/docs/AutocompleteInputDocs.d.ts +0 -4
- package/dist/docs/AutocompleteInputDocs.d.ts.map +0 -1
- package/dist/docs/AutocompleteInputDocs.js +0 -84
- package/dist/docs/AvatarDocs.d.ts +0 -4
- package/dist/docs/AvatarDocs.d.ts.map +0 -1
- package/dist/docs/AvatarDocs.js +0 -7
- package/dist/docs/BadgeDocs.d.ts +0 -4
- package/dist/docs/BadgeDocs.d.ts.map +0 -1
- package/dist/docs/BadgeDocs.js +0 -9
- package/dist/docs/ButtonDocs.d.ts +0 -4
- package/dist/docs/ButtonDocs.d.ts.map +0 -1
- package/dist/docs/ButtonDocs.js +0 -7
- package/dist/docs/CardDocs.d.ts +0 -4
- package/dist/docs/CardDocs.d.ts.map +0 -1
- package/dist/docs/CardDocs.js +0 -22
- package/dist/docs/CheckboxDocs.d.ts +0 -4
- package/dist/docs/CheckboxDocs.d.ts.map +0 -1
- package/dist/docs/CheckboxDocs.js +0 -7
- package/dist/docs/CurrencyInputDocs.d.ts +0 -4
- package/dist/docs/CurrencyInputDocs.d.ts.map +0 -1
- package/dist/docs/CurrencyInputDocs.js +0 -22
- package/dist/docs/DataFieldDocs.d.ts +0 -4
- package/dist/docs/DataFieldDocs.d.ts.map +0 -1
- package/dist/docs/DataFieldDocs.js +0 -7
- package/dist/docs/DataTableDocs.d.ts +0 -4
- package/dist/docs/DataTableDocs.d.ts.map +0 -1
- package/dist/docs/DataTableDocs.js +0 -244
- package/dist/docs/DateInputDocs.d.ts +0 -5
- package/dist/docs/DateInputDocs.d.ts.map +0 -1
- package/dist/docs/DateInputDocs.js +0 -19
- package/dist/docs/DatePickerDocs.d.ts +0 -5
- package/dist/docs/DatePickerDocs.d.ts.map +0 -1
- package/dist/docs/DatePickerDocs.js +0 -16
- package/dist/docs/DialogDocs.d.ts +0 -4
- package/dist/docs/DialogDocs.d.ts.map +0 -1
- package/dist/docs/DialogDocs.js +0 -13
- package/dist/docs/DocAdmin.d.ts +0 -4
- package/dist/docs/DocAdmin.d.ts.map +0 -1
- package/dist/docs/DocAdmin.js +0 -68
- package/dist/docs/DocsMenu.d.ts +0 -2
- package/dist/docs/DocsMenu.d.ts.map +0 -1
- package/dist/docs/DocsMenu.js +0 -5
- package/dist/docs/DocsRouter.d.ts +0 -4
- package/dist/docs/DocsRouter.d.ts.map +0 -1
- package/dist/docs/DocsRouter.js +0 -39
- package/dist/docs/DropdownMenuDocs.d.ts +0 -4
- package/dist/docs/DropdownMenuDocs.d.ts.map +0 -1
- package/dist/docs/DropdownMenuDocs.js +0 -66
- package/dist/docs/DropdownPanelDocs.d.ts +0 -4
- package/dist/docs/DropdownPanelDocs.d.ts.map +0 -1
- package/dist/docs/DropdownPanelDocs.js +0 -7
- package/dist/docs/ExampleFormDocs.d.ts +0 -4
- package/dist/docs/ExampleFormDocs.d.ts.map +0 -1
- package/dist/docs/ExampleFormDocs.js +0 -153
- package/dist/docs/FilterDocs.d.ts +0 -4
- package/dist/docs/FilterDocs.d.ts.map +0 -1
- package/dist/docs/FilterDocs.js +0 -130
- package/dist/docs/InputDocs.d.ts +0 -4
- package/dist/docs/InputDocs.d.ts.map +0 -1
- package/dist/docs/InputDocs.js +0 -17
- package/dist/docs/LinkButtonDocs.d.ts +0 -4
- package/dist/docs/LinkButtonDocs.d.ts.map +0 -1
- package/dist/docs/LinkButtonDocs.js +0 -7
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.js +0 -47
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.js +0 -34
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.js +0 -66
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.js +0 -7
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts +0 -10
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.js +0 -39
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.js +0 -57
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts +0 -9
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.js +0 -30
- package/dist/docs/LoaderDocs.d.ts +0 -4
- package/dist/docs/LoaderDocs.d.ts.map +0 -1
- package/dist/docs/LoaderDocs.js +0 -33
- package/dist/docs/MenuDocs.d.ts +0 -4
- package/dist/docs/MenuDocs.d.ts.map +0 -1
- package/dist/docs/MenuDocs.js +0 -26
- package/dist/docs/PaginationDocs.d.ts +0 -4
- package/dist/docs/PaginationDocs.d.ts.map +0 -1
- package/dist/docs/PaginationDocs.js +0 -38
- package/dist/docs/RadioButtonGroupDocs.d.ts +0 -4
- package/dist/docs/RadioButtonGroupDocs.d.ts.map +0 -1
- package/dist/docs/RadioButtonGroupDocs.js +0 -46
- package/dist/docs/RoadMapDocs.d.ts +0 -4
- package/dist/docs/RoadMapDocs.d.ts.map +0 -1
- package/dist/docs/RoadMapDocs.js +0 -171
- package/dist/docs/SearchSelectInputDocs.d.ts +0 -4
- package/dist/docs/SearchSelectInputDocs.d.ts.map +0 -1
- package/dist/docs/SearchSelectInputDocs.js +0 -168
- package/dist/docs/SkeletonDocs.d.ts +0 -4
- package/dist/docs/SkeletonDocs.d.ts.map +0 -1
- package/dist/docs/SkeletonDocs.js +0 -7
- package/dist/docs/SnackbarDocs.d.ts +0 -4
- package/dist/docs/SnackbarDocs.d.ts.map +0 -1
- package/dist/docs/SnackbarDocs.js +0 -69
- package/dist/docs/TabsGroupDocs.d.ts +0 -4
- package/dist/docs/TabsGroupDocs.d.ts.map +0 -1
- package/dist/docs/TabsGroupDocs.js +0 -38
- package/dist/docs/ThemeSwitcherDocs.d.ts +0 -4
- package/dist/docs/ThemeSwitcherDocs.d.ts.map +0 -1
- package/dist/docs/ThemeSwitcherDocs.js +0 -11
- package/dist/docs/docMockServices/empresaService.d.ts +0 -38
- package/dist/docs/docMockServices/empresaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/empresaService.js +0 -125
- package/dist/docs/docMockServices/index.d.ts +0 -9
- package/dist/docs/docMockServices/index.d.ts.map +0 -1
- package/dist/docs/docMockServices/index.js +0 -8
- package/dist/docs/docMockServices/initialData.d.ts +0 -6
- package/dist/docs/docMockServices/initialData.d.ts.map +0 -1
- package/dist/docs/docMockServices/initialData.js +0 -132
- package/dist/docs/docMockServices/interfaces.d.ts +0 -38
- package/dist/docs/docMockServices/interfaces.d.ts.map +0 -1
- package/dist/docs/docMockServices/interfaces.js +0 -1
- package/dist/docs/docMockServices/personaEmpresaService.d.ts +0 -43
- package/dist/docs/docMockServices/personaEmpresaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/personaEmpresaService.js +0 -151
- package/dist/docs/docMockServices/personaService.d.ts +0 -39
- package/dist/docs/docMockServices/personaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/personaService.js +0 -190
- package/dist/helpers/currencyFormat.js +0 -3
- package/dist/helpers/getErrorMessage.js +0 -13
- package/dist/helpers/getInitialLetters.js +0 -5
- package/dist/helpers/getQueryString.js +0 -13
- package/dist/helpers/index.js +0 -9
- package/dist/helpers/mappers.js +0 -27
- package/dist/helpers/nameValueArrayToObject.js +0 -3
- package/dist/helpers/objectToQueryString.js +0 -3
- package/dist/helpers/queryStringToObject.js +0 -13
- package/dist/helpers/regularExpressions.js +0 -5
- package/dist/hooks/index.js +0 -6
- package/dist/hooks/useAsyncRequest.js +0 -53
- package/dist/hooks/useBreakpoint.js +0 -59
- package/dist/hooks/useElementScroll.js +0 -58
- package/dist/hooks/useEnum.js +0 -21
- package/dist/hooks/useGlobalThemeStyles.js +0 -40
- package/dist/hooks/useThemeOverride.js +0 -99
- package/dist/interfaces/index.js +0 -1
- package/dist/interfaces/name-value.interface.js +0 -1
- package/dist/interfaces/pagination.interface.js +0 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js +0 -6
- package/dist/services/apiClient.js +0 -216
- package/dist/services/index.js +0 -1
- package/dist/styles.d.ts +0 -2
- package/dist/styles.d.ts.map +0 -1
- package/dist/styles.js +0 -3
- package/dist/templates/forms/ContactForm.js +0 -58
- package/dist/templates/forms/LoginForm.js +0 -36
- package/dist/templates/forms/RegistrationForm.js +0 -54
- package/dist/templates/layouts/DashboardLayout.js +0 -26
- package/dist/templates/layouts/SidebarLayout.js +0 -28
- package/dist/templates/patterns/FormPattern.js +0 -68
package/AI_CONTEXT.md
CHANGED
|
@@ -1,217 +1,1400 @@
|
|
|
1
|
-
# Flysoft React UI - AI Context & Documentation
|
|
2
|
-
|
|
3
|
-
This document serves as the source of truth for AI models (Gemini, Claude, GPT, etc.) when generating code that consumes the `flysoft-react-ui` library.
|
|
4
|
-
|
|
5
|
-
## Library Philosophy
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
1
|
+
# Flysoft React UI - AI Context & Documentation
|
|
2
|
+
|
|
3
|
+
This document serves as the source of truth for AI models (Gemini, Claude, GPT, etc.) when generating code that consumes the `flysoft-react-ui` library.
|
|
4
|
+
|
|
5
|
+
## Library Philosophy
|
|
6
|
+
|
|
7
|
+
`flysoft-react-ui` is a React component library built with TypeScript. It emphasizes a consistent look and feel, ease of use, and "premium" aesthetics out of the box. All components use CSS variables for theming and FontAwesome 5 (light/outlined style) for icons.
|
|
8
|
+
|
|
9
|
+
## Critical Rules for AI
|
|
10
|
+
|
|
11
|
+
1. **Top-Level Imports Only**: Always import from `'flysoft-react-ui'`.
|
|
12
|
+
- CORRECT: `import { Button, Card } from 'flysoft-react-ui';`
|
|
13
|
+
- INCORRECT: `import { Button } from 'flysoft-react-ui/components/Button';`
|
|
14
|
+
2. **TypeScript First**: Use the exported types (e.g., `ButtonProps`, `DataTableColumn<T>`) to ensure type safety.
|
|
15
|
+
3. **Style Import at App Root Only**: Add `import 'flysoft-react-ui/styles';` once at the app root. Never import CSS in individual components.
|
|
16
|
+
4. **Do Not Use Docs Internals**: Never import or reference anything from `docs/*` or `src/docs/*`.
|
|
17
|
+
5. **FontAwesome 5 Only**: Use `fa-*` icon classes. Components normalize to light style (`fal`) automatically. Never use other icon libraries.
|
|
18
|
+
6. **Theme CSS Variables**: Use `var(--color-*)`, `var(--shadow-*)`, `var(--radius-*)`, `var(--font-*)` for custom styling. Never hardcode colors.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Form Controls
|
|
23
|
+
|
|
24
|
+
### Button
|
|
25
|
+
|
|
26
|
+
Customizable button with variants, colors, icons, and ripple effect.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
30
|
+
variant?: "primary" | "outline" | "ghost"; // default: "primary"
|
|
31
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
32
|
+
color?: "primary" | "secondary" | "success" | "warning" | "danger" | "info"; // default: "primary"
|
|
33
|
+
bg?: string; // Custom background color (hex, rgb, rgba, hsl, or color name)
|
|
34
|
+
textColor?: string; // Custom text color
|
|
35
|
+
icon?: string; // FontAwesome icon class (e.g. "fa-save")
|
|
36
|
+
iconPosition?: "left" | "right"; // default: "left"
|
|
37
|
+
loading?: boolean; // Shows spinner, disables button. default: false
|
|
38
|
+
children?: React.ReactNode;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<Button variant="primary" icon="fa-save" loading={isLoading} onClick={handleSave}>
|
|
44
|
+
Guardar
|
|
45
|
+
</Button>
|
|
46
|
+
<Button variant="outline" color="danger" icon="fa-trash">Eliminar</Button>
|
|
47
|
+
<Button variant="ghost" size="sm">Cancelar</Button>
|
|
48
|
+
<Button bg="#8b5cf6" textColor="#fff">Custom Color</Button>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### LinkButton
|
|
52
|
+
|
|
53
|
+
Anchor-styled button that uses React Router `<Link>` for internal routes and `<a>` for external URLs.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
interface LinkButtonProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
57
|
+
to: string; // Route or URL (required)
|
|
58
|
+
target?: string;
|
|
59
|
+
variant?: "primary" | "outline" | "ghost"; // default: "primary"
|
|
60
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
61
|
+
color?: "primary" | "secondary" | "success" | "warning" | "danger" | "info";
|
|
62
|
+
bg?: string;
|
|
63
|
+
textColor?: string;
|
|
64
|
+
icon?: string;
|
|
65
|
+
iconPosition?: "left" | "right"; // default: "left"
|
|
66
|
+
children?: React.ReactNode;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<LinkButton to="/users" icon="fa-users">Ver Usuarios</LinkButton>
|
|
72
|
+
<LinkButton to="https://example.com" target="_blank">Sitio Externo</LinkButton>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Input
|
|
76
|
+
|
|
77
|
+
Text input with labels, icons, error states, and ref forwarding.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
81
|
+
label?: string; // Label text above input
|
|
82
|
+
error?: string; // Error message below input
|
|
83
|
+
icon?: string; // FontAwesome icon class
|
|
84
|
+
iconPosition?: "left" | "right"; // default: "left"
|
|
85
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
86
|
+
children?: React.ReactNode;
|
|
87
|
+
onIconClick?: (event: React.MouseEvent<HTMLElement>) => void; // Makes icon clickable
|
|
88
|
+
readOnly?: boolean; // Read-only without disabled appearance
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<Input label="Email" type="email" icon="fa-envelope" placeholder="usuario@email.com" />
|
|
94
|
+
<Input label="Búsqueda" icon="fa-search" iconPosition="right" onIconClick={handleSearch} />
|
|
95
|
+
<Input label="Nombre" error="Campo requerido" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### AutocompleteInput
|
|
99
|
+
|
|
100
|
+
Searchable dropdown with single and multiple selection support.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface AutocompleteOption {
|
|
104
|
+
label: string;
|
|
105
|
+
value: string;
|
|
106
|
+
description?: string | number;
|
|
107
|
+
icon?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface AutocompleteInputProps<T = AutocompleteOption, K = string>
|
|
111
|
+
extends Omit<InputProps, "onChange" | "value" | "ref"> {
|
|
112
|
+
options: T[]; // Options array (required)
|
|
113
|
+
value?: string | string[]; // String for single, array for multiple
|
|
114
|
+
onChange?: ((value: string | string[]) => void) | React.ChangeEventHandler<HTMLInputElement>;
|
|
115
|
+
onSelectOption?: (option: T, value: K) => void;
|
|
116
|
+
noResultsText?: string; // default: "Sin resultados"
|
|
117
|
+
getOptionLabel?: (item: T) => string;
|
|
118
|
+
getOptionValue?: (item: T) => K;
|
|
119
|
+
getOptionDescription?: (item: T) => string | number | undefined;
|
|
120
|
+
renderOption?: (item: T) => React.ReactNode;
|
|
121
|
+
readOnly?: boolean;
|
|
122
|
+
multiple?: boolean; // Multi-select with checkboxes. default: false
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// Single selection
|
|
128
|
+
<AutocompleteInput
|
|
129
|
+
label="País"
|
|
130
|
+
options={[{ label: "Argentina", value: "AR" }, { label: "Brasil", value: "BR" }]}
|
|
131
|
+
value={selectedCountry}
|
|
132
|
+
onChange={setSelectedCountry}
|
|
133
|
+
/>
|
|
134
|
+
|
|
135
|
+
// Multiple selection
|
|
136
|
+
<AutocompleteInput
|
|
137
|
+
label="Categorías"
|
|
138
|
+
options={categories}
|
|
139
|
+
multiple
|
|
140
|
+
value={selectedCategories}
|
|
141
|
+
onChange={setSelectedCategories}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
// Custom objects
|
|
145
|
+
<AutocompleteInput<User, number>
|
|
146
|
+
label="Usuario"
|
|
147
|
+
options={users}
|
|
148
|
+
getOptionLabel={(u) => u.fullName}
|
|
149
|
+
getOptionValue={(u) => u.id}
|
|
150
|
+
getOptionDescription={(u) => u.email}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### SearchSelectInput
|
|
155
|
+
|
|
156
|
+
Opens a dialog modal for selecting from async search results. Ideal for large datasets.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface SearchSelectOption {
|
|
160
|
+
label: string;
|
|
161
|
+
value?: string;
|
|
162
|
+
description?: string | number;
|
|
163
|
+
icon?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface SearchSelectInputProps<T = SearchSelectOption, K = string>
|
|
167
|
+
extends Omit<InputProps, "onChange" | "value" | "ref"> {
|
|
168
|
+
value?: T | K | string;
|
|
169
|
+
onChange?: ((value: T | K) => void) | React.ChangeEventHandler<HTMLInputElement>;
|
|
170
|
+
onSearchPromiseFn: (text: string) => Promise<Array<T> | PaginationInterface<T>>; // required
|
|
171
|
+
onSingleSearchPromiseFn: (value: K) => Promise<T | undefined>; // required
|
|
172
|
+
onSelectOption?: (option: T, value: K) => void;
|
|
173
|
+
dialogTitle?: string; // default: "Seleccione una opción"
|
|
174
|
+
icon?: string; // default: "fa-search"
|
|
175
|
+
iconPosition?: "left" | "right"; // default: "right"
|
|
176
|
+
noResultsText?: string; // default: "Sin resultados"
|
|
177
|
+
getOptionLabel?: (item: T) => string;
|
|
178
|
+
getOptionValue?: (item: T) => K;
|
|
179
|
+
getOptionDescription?: (item: T) => string | number | undefined;
|
|
180
|
+
renderOption?: (item: T) => React.ReactNode;
|
|
181
|
+
readOnly?: boolean;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<SearchSelectInput<Product, number>
|
|
187
|
+
label="Producto"
|
|
188
|
+
onSearchPromiseFn={(text) => apiClient.get({ url: `/api/products?q=${text}` })}
|
|
189
|
+
onSingleSearchPromiseFn={(id) => apiClient.get({ url: `/api/products/${id}` })}
|
|
190
|
+
getOptionLabel={(p) => p.name}
|
|
191
|
+
getOptionValue={(p) => p.id}
|
|
192
|
+
getOptionDescription={(p) => `$${p.price}`}
|
|
193
|
+
onChange={(value) => setProductId(value)}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### DatePicker
|
|
198
|
+
|
|
199
|
+
Standalone calendar component for date selection.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
interface DatePickerProps {
|
|
203
|
+
value?: Dayjs | null;
|
|
204
|
+
onChange?: (date: Dayjs) => void;
|
|
205
|
+
initialViewDate?: Dayjs; // Initial month/year when value is null
|
|
206
|
+
startWeekOn?: "monday" | "sunday"; // default: "sunday"
|
|
207
|
+
className?: string;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### DateInput
|
|
212
|
+
|
|
213
|
+
Input field with integrated DatePicker dropdown. Accepts manual text and Dayjs objects.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
type DateInputFormat = "dd/mm/yyyy" | "mm/dd/yyyy";
|
|
217
|
+
|
|
218
|
+
interface DateInputProps extends Omit<InputProps, "type" | "value" | "onChange" | "ref"> {
|
|
219
|
+
value?: Dayjs | null | string;
|
|
220
|
+
onChange?: ((date: Dayjs | null) => void) | React.ChangeEventHandler<HTMLInputElement>;
|
|
221
|
+
format?: DateInputFormat; // default: "dd/mm/yyyy"
|
|
222
|
+
datePickerProps?: Omit<DatePickerProps, "value" | "onChange">;
|
|
223
|
+
readOnly?: boolean;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<DateInput label="Fecha de nacimiento" value={birthDate} onChange={setBirthDate} />
|
|
229
|
+
<DateInput label="Start Date" format="mm/dd/yyyy" />
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Checkbox
|
|
233
|
+
|
|
234
|
+
Boolean checkbox with label and error support. Ref forwarding supported.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
|
|
238
|
+
label?: string;
|
|
239
|
+
labelPosition?: "left" | "right"; // default: "right"
|
|
240
|
+
error?: string;
|
|
241
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
242
|
+
readOnly?: boolean;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
<Checkbox label="Acepto los términos" checked={accepted} onChange={handleChange} />
|
|
248
|
+
<Checkbox label="Activo" size="lg" readOnly />
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### RadioButtonGroup
|
|
252
|
+
|
|
253
|
+
Single selection from a group of radio options.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
interface RadioOption {
|
|
257
|
+
label: string;
|
|
258
|
+
value: string | number;
|
|
259
|
+
disabled?: boolean;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
interface RadioButtonGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "children"> {
|
|
263
|
+
options: RadioOption[]; // required
|
|
264
|
+
value?: string | number;
|
|
265
|
+
onChange?: ((value: string | number) => void) | React.ChangeEventHandler<HTMLInputElement>;
|
|
266
|
+
labelPosition?: "left" | "right"; // default: "right"
|
|
267
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
268
|
+
error?: string;
|
|
269
|
+
direction?: "vertical" | "horizontal"; // default: "vertical"
|
|
270
|
+
gap?: "sm" | "md" | "lg"; // default: "md"
|
|
271
|
+
name?: string;
|
|
272
|
+
disabled?: boolean;
|
|
273
|
+
onBlur?: (() => void) | React.FocusEventHandler<HTMLInputElement>;
|
|
274
|
+
readOnly?: boolean;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
<RadioButtonGroup
|
|
280
|
+
options={[
|
|
281
|
+
{ label: "Masculino", value: "M" },
|
|
282
|
+
{ label: "Femenino", value: "F" },
|
|
283
|
+
{ label: "Otro", value: "O" },
|
|
284
|
+
]}
|
|
285
|
+
value={gender}
|
|
286
|
+
onChange={setGender}
|
|
287
|
+
direction="horizontal"
|
|
288
|
+
/>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### CurrencyInput
|
|
292
|
+
|
|
293
|
+
Numeric input with currency formatting (Argentine locale: 1.234,56). Ref forwarding supported.
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
interface CurrencyInputProps extends Omit<InputProps, "value" | "onChange" | "type"> {
|
|
297
|
+
value?: number | null;
|
|
298
|
+
onChange?: (value: any) => void; // Receives parsed numeric value
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
<CurrencyInput label="Monto" value={amount} onChange={setAmount} icon="fa-dollar-sign" />
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Pagination
|
|
307
|
+
|
|
308
|
+
URL-based pagination controls using react-router-dom's `useSearchParams`.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
interface PaginationProps {
|
|
312
|
+
fieldName?: string; // URL param name. default: "pagina"
|
|
313
|
+
page?: number; // default: 1
|
|
314
|
+
pages?: number; // default: 1
|
|
315
|
+
total?: number; // default: 0
|
|
316
|
+
isLoading?: boolean; // default: false
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
<Pagination page={currentPage} pages={totalPages} total={totalItems} />
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Layout Components
|
|
327
|
+
|
|
328
|
+
### Card
|
|
329
|
+
|
|
330
|
+
Generic container with header, content, footer, and variants.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
interface CardProps {
|
|
334
|
+
title?: string | React.ReactNode;
|
|
335
|
+
subtitle?: string | React.ReactNode;
|
|
336
|
+
children?: React.ReactNode;
|
|
337
|
+
className?: string;
|
|
338
|
+
headerActions?: React.ReactNode;
|
|
339
|
+
footer?: React.ReactNode;
|
|
340
|
+
variant?: "default" | "elevated" | "outlined"; // default: "default"
|
|
341
|
+
alwaysDisplayHeaderActions?: boolean; // default: false (shows on hover on lg+)
|
|
342
|
+
headerClassName?: string;
|
|
343
|
+
contentClassName?: string;
|
|
344
|
+
footerClassName?: string;
|
|
345
|
+
compact?: boolean; // Reduced padding. default: false
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
<Card title="Usuarios" headerActions={<Button size="sm" icon="fa-plus">Nuevo</Button>}>
|
|
351
|
+
<p>Contenido</p>
|
|
352
|
+
</Card>
|
|
353
|
+
<Card variant="elevated" compact footer={<Button variant="primary">Guardar</Button>}>
|
|
354
|
+
<Input label="Nombre" />
|
|
355
|
+
</Card>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### AppLayout
|
|
359
|
+
|
|
360
|
+
Main application layout with responsive navbar and sidebar drawer.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
interface AppLayoutProps {
|
|
364
|
+
navbar?: NavbarInterface;
|
|
365
|
+
leftDrawer?: LeftDrawerInterface;
|
|
366
|
+
contentFooter?: React.ReactNode;
|
|
367
|
+
children: React.ReactNode; // required
|
|
368
|
+
className?: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
interface NavbarInterface {
|
|
372
|
+
navBarLeftNode?: React.ReactNode;
|
|
373
|
+
navBarRightNode?: React.ReactNode;
|
|
374
|
+
fullWidthNavbar?: boolean; // Fixed full-width (true) or relative (false)
|
|
375
|
+
height?: string; // default: "64px"
|
|
376
|
+
className?: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
interface LeftDrawerInterface {
|
|
380
|
+
headerNode?: React.ReactNode;
|
|
381
|
+
contentNode?: React.ReactNode;
|
|
382
|
+
footerNode?: React.ReactNode;
|
|
383
|
+
className?: string;
|
|
384
|
+
width?: string; // default: "256px"
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
<AppLayout
|
|
390
|
+
navbar={{
|
|
391
|
+
navBarLeftNode: <h1>Mi App</h1>,
|
|
392
|
+
navBarRightNode: <Avatar text="Admin" />,
|
|
393
|
+
fullWidthNavbar: true,
|
|
394
|
+
}}
|
|
395
|
+
leftDrawer={{
|
|
396
|
+
headerNode: <h2>Menú</h2>,
|
|
397
|
+
contentNode: <nav>...</nav>,
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
<main>Contenido</main>
|
|
401
|
+
</AppLayout>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Behaviors**: Navbar auto-hides/shows on scroll. Mobile drawer with overlay. Responsive breakpoints.
|
|
405
|
+
|
|
406
|
+
### Collection
|
|
407
|
+
|
|
408
|
+
Flex container for rendering lists of items.
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
interface CollectionProps {
|
|
412
|
+
children: React.ReactNode; // required
|
|
413
|
+
gap?: string; // CSS gap value. default: "1rem"
|
|
414
|
+
direction?: "column" | "row"; // default: "column"
|
|
415
|
+
wrap?: boolean; // default: false
|
|
416
|
+
className?: string;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### DataField
|
|
421
|
+
|
|
422
|
+
Label + value pair display for detail views.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
interface DataFieldProps {
|
|
426
|
+
label?: string;
|
|
427
|
+
value?: string | number | React.ReactNode;
|
|
428
|
+
inline?: boolean; // Horizontal layout. default: false
|
|
429
|
+
align?: "left" | "right" | "center"; // default: "left"
|
|
430
|
+
title?: string; // HTML title tooltip
|
|
431
|
+
link?: string; // Opens URL in new tab
|
|
432
|
+
className?: string;
|
|
433
|
+
labelClassName?: string;
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
<DataField label="Nombre" value="Juan Pérez" />
|
|
439
|
+
<DataField label="Email" value="juan@email.com" link="mailto:juan@email.com" inline />
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### TabsGroup / TabPanel
|
|
443
|
+
|
|
444
|
+
Tabbed interfaces with optional URL persistence.
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
interface Tab {
|
|
448
|
+
id: string | number;
|
|
449
|
+
label: string;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
interface TabsGroupProps {
|
|
453
|
+
children?: React.ReactNode;
|
|
454
|
+
tabs: Tab[]; // required
|
|
455
|
+
paramName?: string; // URL search param for persistence
|
|
456
|
+
headerNode?: React.ReactNode; // Right-aligned header content
|
|
457
|
+
onChangeTab?: (selectedTab: string) => void;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
interface TabPanelProps {
|
|
461
|
+
children?: React.ReactNode;
|
|
462
|
+
tabId: string | number; // Must match a Tab.id (required)
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
<TabsGroup tabs={[{ id: "info", label: "Información" }, { id: "history", label: "Historial" }]}>
|
|
468
|
+
<TabPanel tabId="info">
|
|
469
|
+
<p>Información del usuario</p>
|
|
470
|
+
</TabPanel>
|
|
471
|
+
<TabPanel tabId="history">
|
|
472
|
+
<p>Historial de actividad</p>
|
|
473
|
+
</TabPanel>
|
|
474
|
+
</TabsGroup>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### DataTable\<T\>
|
|
478
|
+
|
|
479
|
+
High-performance data table with sorting, formatting, actions, and skeleton loading.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
interface DataTableColumn<T> {
|
|
483
|
+
align?: "left" | "right" | "center"; // Auto-set for date/currency/numeric
|
|
484
|
+
width?: string;
|
|
485
|
+
header?: string | React.ReactNode;
|
|
486
|
+
footer?: string | React.ReactNode;
|
|
487
|
+
value?: string | number | ((row: T) => string | React.ReactNode);
|
|
488
|
+
tooltip?: (row: T) => string | React.ReactNode;
|
|
489
|
+
type?: "text" | "numeric" | "currency" | "date";
|
|
490
|
+
actions?: (row: T) => Array<React.ReactNode>;
|
|
491
|
+
headerActions?: () => Array<React.ReactNode>;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
interface DataTableProps<T> {
|
|
495
|
+
columns: DataTableColumn<T>[]; // required
|
|
496
|
+
rows: T[]; // required
|
|
497
|
+
className?: string;
|
|
498
|
+
maxRows?: number; // Enables sticky header with scroll
|
|
499
|
+
locale?: string; // default: "es-AR"
|
|
500
|
+
isLoading?: boolean; // Shows skeleton rows. default: false
|
|
501
|
+
loadingRows?: number; // default: 5
|
|
502
|
+
rowClassName?: (row: T) => string;
|
|
503
|
+
headerClassName?: string;
|
|
504
|
+
footerClassName?: string;
|
|
505
|
+
headerCellClassName?: string;
|
|
506
|
+
footerCellClassName?: string;
|
|
507
|
+
cellClassName?: string | ((row: T, column: DataTableColumn<T>) => string);
|
|
508
|
+
compact?: boolean; // default: false
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
interface User { id: number; name: string; salary: number; createdAt: string; }
|
|
514
|
+
|
|
515
|
+
const columns: DataTableColumn<User>[] = [
|
|
516
|
+
{ header: "ID", value: "id", width: "60px" },
|
|
517
|
+
{ header: "Nombre", value: (row) => row.name },
|
|
518
|
+
{ header: "Salario", value: "salary", type: "currency" },
|
|
519
|
+
{ header: "Fecha", value: "createdAt", type: "date" },
|
|
520
|
+
{
|
|
521
|
+
header: "Acciones",
|
|
522
|
+
actions: (row) => [
|
|
523
|
+
<Button key="edit" variant="ghost" size="sm" icon="fa-edit" onClick={() => edit(row)}>Editar</Button>,
|
|
524
|
+
<Button key="del" variant="ghost" size="sm" icon="fa-trash" color="danger" onClick={() => del(row)}>Eliminar</Button>,
|
|
525
|
+
],
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
<DataTable<User> columns={columns} rows={users} isLoading={loading} maxRows={10} />
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Type formatting**: `currency` → thousands separator, no symbol. `numeric` → locale formatting. `date` → DD/MM/YYYY.
|
|
533
|
+
|
|
534
|
+
### Accordion
|
|
535
|
+
|
|
536
|
+
Collapsible content section with smooth animation.
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
interface AccordionProps {
|
|
540
|
+
title: string | React.ReactNode; // required
|
|
541
|
+
children: React.ReactNode; // required
|
|
542
|
+
icon?: string; // FontAwesome icon
|
|
543
|
+
rightNode?: React.ReactNode;
|
|
544
|
+
defaultOpen?: boolean; // default: false
|
|
545
|
+
className?: string;
|
|
546
|
+
variant?: "default" | "elevated" | "outlined"; // default: "default"
|
|
547
|
+
onToggle?: (isOpen: boolean) => void;
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
<Accordion title="Detalles" icon="fa-info-circle" defaultOpen>
|
|
553
|
+
<p>Contenido colapsable</p>
|
|
554
|
+
</Accordion>
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Menu
|
|
558
|
+
|
|
559
|
+
Simple menu list for displaying options.
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
interface MenuProps<T = { label: string }> {
|
|
563
|
+
options: T[]; // required
|
|
564
|
+
onOptionSelected: (item: T) => void; // required
|
|
565
|
+
getOptionLabel?: (item: T) => string;
|
|
566
|
+
renderOption?: (item: T) => React.ReactNode;
|
|
567
|
+
className?: string;
|
|
568
|
+
style?: React.CSSProperties;
|
|
569
|
+
itemClassName?: string;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### DropdownMenu
|
|
574
|
+
|
|
575
|
+
Portal-based dropdown menu triggered by a button. Auto-positions above/below.
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
interface DropdownMenuProps<T = { label: string }> {
|
|
579
|
+
options: T[]; // required
|
|
580
|
+
onOptionSelected: (item: T) => void; // required
|
|
581
|
+
renderNode?: React.ReactNode; // Custom trigger (default: ellipsis icon button)
|
|
582
|
+
getOptionLabel?: (item: T) => string;
|
|
583
|
+
renderOption?: (item: T) => React.ReactNode;
|
|
584
|
+
replaceOnSingleOption?: boolean; // Show single option inline. default: false
|
|
585
|
+
openOnHover?: boolean; // default: false
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
```tsx
|
|
590
|
+
<DropdownMenu
|
|
591
|
+
options={[{ label: "Editar" }, { label: "Eliminar" }]}
|
|
592
|
+
onOptionSelected={(item) => handleAction(item.label)}
|
|
593
|
+
renderNode={<Button variant="ghost" icon="fa-cog" size="sm" />}
|
|
594
|
+
/>
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### DropdownPanel
|
|
598
|
+
|
|
599
|
+
Portal-based dropdown that renders arbitrary content (not a list).
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
interface DropdownPanelProps {
|
|
603
|
+
renderNode?: React.ReactNode; // Custom trigger (default: ellipsis icon button)
|
|
604
|
+
children: React.ReactNode; // required
|
|
605
|
+
openOnHover?: boolean; // default: false
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Filter
|
|
610
|
+
|
|
611
|
+
Versatile filtering component with multiple filter types and optional URL persistence.
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
// Discriminated union by filterType
|
|
615
|
+
type FilterProps =
|
|
616
|
+
| TextFilterProps // filterType?: "text" (default)
|
|
617
|
+
| NumberFilterProps // filterType: "number" (+ min?, max?)
|
|
618
|
+
| DateFilterProps // filterType: "date"
|
|
619
|
+
| AutocompleteFilterProps // filterType: "autocomplete" (+ options, multiple?)
|
|
620
|
+
| SearchFilterProps // filterType: "search"
|
|
621
|
+
| SearchSelectFilterProps // filterType: "searchSelect" (+ onSearchPromiseFn, onSingleSearchPromiseFn)
|
|
622
|
+
|
|
623
|
+
// Common props for all filter types:
|
|
624
|
+
interface BaseFilterProps {
|
|
625
|
+
paramName?: string; // URL search param for persistence
|
|
626
|
+
label?: string;
|
|
627
|
+
staticOptions?: Array<{ text: string; value: string }>;
|
|
628
|
+
inputWidth?: string;
|
|
629
|
+
value?: string; // Controlled value
|
|
630
|
+
onChange?: (value: string | undefined) => void;
|
|
631
|
+
hideEmpty?: boolean; // default: false
|
|
632
|
+
disabled?: boolean; // default: false
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
```tsx
|
|
637
|
+
<Filter filterType="text" paramName="nombre" label="Nombre" />
|
|
638
|
+
<Filter filterType="number" paramName="edad" label="Edad" min={0} max={120} />
|
|
639
|
+
<Filter filterType="date" paramName="fecha" label="Fecha" />
|
|
640
|
+
<Filter
|
|
641
|
+
filterType="autocomplete"
|
|
642
|
+
paramName="estado"
|
|
643
|
+
label="Estado"
|
|
644
|
+
options={[{ label: "Activo", value: "1" }, { label: "Inactivo", value: "0" }]}
|
|
645
|
+
/>
|
|
646
|
+
<Filter
|
|
647
|
+
filterType="searchSelect"
|
|
648
|
+
paramName="cliente"
|
|
649
|
+
label="Cliente"
|
|
650
|
+
onSearchPromiseFn={(text) => apiClient.get({ url: `/api/clients?q=${text}` })}
|
|
651
|
+
onSingleSearchPromiseFn={(id) => apiClient.get({ url: `/api/clients/${id}` })}
|
|
652
|
+
/>
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Utility Components
|
|
658
|
+
|
|
659
|
+
### Badge
|
|
660
|
+
|
|
661
|
+
Status/category label with variants and custom colors.
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
interface BadgeProps {
|
|
665
|
+
children: React.ReactNode; // required
|
|
666
|
+
variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "info"; // default: "primary"
|
|
667
|
+
size?: "sm" | "md" | "lg"; // default: "md"
|
|
668
|
+
rounded?: boolean; // Full border radius. default: false
|
|
669
|
+
className?: string;
|
|
670
|
+
icon?: string;
|
|
671
|
+
iconPosition?: "left" | "right"; // default: "left"
|
|
672
|
+
iconLabel?: string; // aria-label for icon
|
|
673
|
+
bg?: string; // Custom background color
|
|
674
|
+
textColor?: string; // Custom text color
|
|
675
|
+
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
```tsx
|
|
680
|
+
<Badge variant="success" icon="fa-check">Activo</Badge>
|
|
681
|
+
<Badge variant="danger" rounded>3</Badge>
|
|
682
|
+
<Badge bg="#8b5cf6" textColor="#fff">Custom</Badge>
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Avatar
|
|
686
|
+
|
|
687
|
+
User profile display with initials fallback when image fails.
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
interface AvatarProps {
|
|
691
|
+
text: string; // Name for initials extraction (required)
|
|
692
|
+
image?: string; // Image URL
|
|
693
|
+
bgColor?: string; // default: "#4b5563"
|
|
694
|
+
textColor?: string; // default: "#ffffff"
|
|
695
|
+
size?: "sm" | "md" | "lg"; // default: "md" (sm=32px, md=40px, lg=48px)
|
|
696
|
+
className?: string;
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
<Avatar text="Juan Pérez" image="/avatars/juan.jpg" />
|
|
702
|
+
<Avatar text="Admin User" bgColor="#3b82f6" size="lg" />
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
### RoadMap
|
|
706
|
+
|
|
707
|
+
Progress/stage visualization with connected circles and gradient lines.
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
interface RoadMapStage {
|
|
711
|
+
name: string; // required
|
|
712
|
+
description?: string;
|
|
713
|
+
icon?: string;
|
|
714
|
+
disabled?: boolean; // Grayed out at 50% opacity
|
|
715
|
+
variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "info";
|
|
716
|
+
bg?: string; // Custom color (overrides variant)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
interface RoadMapProps {
|
|
720
|
+
stages: RoadMapStage[]; // required
|
|
721
|
+
className?: string;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
```tsx
|
|
726
|
+
<RoadMap stages={[
|
|
727
|
+
{ name: "Creado", icon: "fa-plus", variant: "info" },
|
|
728
|
+
{ name: "En Proceso", icon: "fa-cog", variant: "warning" },
|
|
729
|
+
{ name: "Completado", icon: "fa-check", variant: "success" },
|
|
730
|
+
{ name: "Archivado", icon: "fa-archive", disabled: true },
|
|
731
|
+
]} />
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### Dialog
|
|
735
|
+
|
|
736
|
+
Modal window with overlay, escape-to-close, and scroll lock.
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
interface DialogProps {
|
|
740
|
+
isOpen: boolean; // required
|
|
741
|
+
title: React.ReactNode; // required
|
|
742
|
+
children: React.ReactNode; // required
|
|
743
|
+
footer?: React.ReactNode;
|
|
744
|
+
onClose?: () => void;
|
|
745
|
+
closeOnOverlayClick?: boolean; // default: false
|
|
746
|
+
compact?: boolean; // default: false
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
```tsx
|
|
751
|
+
<Dialog isOpen={showDialog} title="Confirmar" onClose={() => setShowDialog(false)}
|
|
752
|
+
footer={
|
|
753
|
+
<>
|
|
754
|
+
<Button variant="ghost" onClick={() => setShowDialog(false)}>Cancelar</Button>
|
|
755
|
+
<Button variant="primary" color="danger" onClick={handleDelete}>Eliminar</Button>
|
|
756
|
+
</>
|
|
757
|
+
}
|
|
758
|
+
>
|
|
759
|
+
<p>¿Está seguro que desea eliminar este registro?</p>
|
|
760
|
+
</Dialog>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Loader
|
|
764
|
+
|
|
765
|
+
Loading indicator with progress bar. Can wrap content with overlay.
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
interface LoaderProps {
|
|
769
|
+
isLoading?: boolean; // default: false
|
|
770
|
+
text?: string; // Text below progress bar
|
|
771
|
+
children?: React.ReactNode;
|
|
772
|
+
keepContentWhileLoading?: boolean; // Show content faded at 50% opacity
|
|
773
|
+
contentLoadingNode?: React.ReactNode; // Custom loading content
|
|
774
|
+
overlayClassName?: string; // default: "bg-black/50 backdrop-blur-sm"
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
```tsx
|
|
779
|
+
<Loader isLoading={loading} text="Cargando datos...">
|
|
780
|
+
<DataTable ... />
|
|
781
|
+
</Loader>
|
|
782
|
+
<Loader isLoading={loading} keepContentWhileLoading>
|
|
783
|
+
<Card>...</Card>
|
|
784
|
+
</Loader>
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### FiltersDialog
|
|
788
|
+
|
|
789
|
+
Dialog that groups multiple Filter components. Syncs values from/to URL search params.
|
|
790
|
+
|
|
791
|
+
```typescript
|
|
792
|
+
interface FilterConfig {
|
|
793
|
+
filterType: "text" | "number" | "date" | "autocomplete";
|
|
794
|
+
paramName: string; // required
|
|
795
|
+
label?: string;
|
|
796
|
+
staticOptions?: Array<{ text: string; value: string }>;
|
|
797
|
+
inputWidth?: string;
|
|
798
|
+
min?: number; // For number filters
|
|
799
|
+
max?: number; // For number filters
|
|
800
|
+
options?: any[]; // For autocomplete
|
|
801
|
+
getOptionLabel?: (item: any) => string;
|
|
802
|
+
getOptionValue?: (item: any) => any;
|
|
803
|
+
renderOption?: (item: any) => React.ReactNode;
|
|
804
|
+
noResultsText?: string;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
interface FiltersDialogProps {
|
|
808
|
+
filters: FilterConfig[]; // required
|
|
809
|
+
}
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
```tsx
|
|
813
|
+
<FiltersDialog filters={[
|
|
814
|
+
{ filterType: "text", paramName: "nombre", label: "Nombre" },
|
|
815
|
+
{ filterType: "number", paramName: "edad", label: "Edad", min: 0, max: 120 },
|
|
816
|
+
{ filterType: "date", paramName: "fecha", label: "Fecha" },
|
|
817
|
+
{ filterType: "autocomplete", paramName: "estado", label: "Estado",
|
|
818
|
+
options: [{ label: "Activo", value: "1" }, { label: "Inactivo", value: "0" }] },
|
|
819
|
+
]} />
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Snackbar / SnackbarContainer
|
|
823
|
+
|
|
824
|
+
Toast notification system. SnackbarContainer must be at the app root.
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
interface SnackbarContainerProps {
|
|
828
|
+
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center"; // default: "top-right"
|
|
829
|
+
maxSnackbars?: number; // default: 5
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Usage via hook (not direct Snackbar component):
|
|
833
|
+
const { showSnackbar } = useSnackbar();
|
|
834
|
+
showSnackbar("Operación exitosa", "success");
|
|
835
|
+
showSnackbar("Error al guardar", "danger", { duration: 5000, icon: "fa-exclamation" });
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
**Variants**: `"primary"` | `"secondary"` | `"success"` | `"warning"` | `"danger"` | `"info"`
|
|
839
|
+
**Default icons**: success=fa-check-circle, danger=fa-times-circle, warning=fa-exclamation-triangle, info/primary/secondary=fa-info-circle
|
|
840
|
+
|
|
841
|
+
### Skeleton
|
|
842
|
+
|
|
843
|
+
Loading placeholder with pulse animation. Fully customizable via className.
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
interface SkeletonProps {
|
|
847
|
+
className?: string; // Tailwind classes to control width, height, shape
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
```tsx
|
|
852
|
+
<Skeleton className="h-4 w-3/4" /> {/* Text line */}
|
|
853
|
+
<Skeleton className="h-10 w-full" /> {/* Input placeholder */}
|
|
854
|
+
<Skeleton className="h-32 w-32 rounded-full" /> {/* Avatar placeholder */}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### ThemeSwitcher
|
|
858
|
+
|
|
859
|
+
Self-contained theme toggle. No props. Displays available themes with switch buttons and current theme info.
|
|
860
|
+
|
|
861
|
+
```tsx
|
|
862
|
+
<ThemeSwitcher />
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## Contexts & State Management
|
|
868
|
+
|
|
869
|
+
### ThemeProvider / useTheme
|
|
870
|
+
|
|
871
|
+
Manages application theme with CSS variable injection, presets, and localStorage persistence.
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
// Provider props
|
|
875
|
+
interface ThemeProviderProps {
|
|
876
|
+
children: ReactNode;
|
|
877
|
+
initialTheme?: string | Theme; // default: "light"
|
|
878
|
+
storageKey?: string; // localStorage key. default: "flysoft-theme"
|
|
879
|
+
forceInitialTheme?: boolean; // Ignore localStorage. default: false
|
|
880
|
+
onThemeChange?: (theme: Theme) => void;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Hook return
|
|
884
|
+
interface ThemeContextType {
|
|
885
|
+
theme: Theme; // Current theme object
|
|
886
|
+
setTheme: (theme: Theme | string) => void; // Switch theme by name or object
|
|
887
|
+
updateTheme: (updates: Partial<Theme> | ((prev: Theme) => Theme)) => void;
|
|
888
|
+
currentThemeName: string;
|
|
889
|
+
availableThemes: string[]; // ["light", "dark", "blue", "green"]
|
|
890
|
+
resetToDefault: () => void;
|
|
891
|
+
isDark: boolean;
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
```tsx
|
|
896
|
+
// App root
|
|
897
|
+
<ThemeProvider initialTheme="light">
|
|
898
|
+
<App />
|
|
899
|
+
</ThemeProvider>
|
|
900
|
+
|
|
901
|
+
// In components
|
|
902
|
+
const { theme, setTheme, isDark } = useTheme();
|
|
903
|
+
<Button onClick={() => setTheme(isDark ? "light" : "dark")}>Toggle Theme</Button>
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**Preset themes**: `lightTheme`, `darkTheme`, `blueTheme`, `greenTheme` (importable).
|
|
907
|
+
|
|
908
|
+
### AuthProvider / AuthContext
|
|
909
|
+
|
|
910
|
+
Manages authentication with automatic token validation and refresh.
|
|
911
|
+
|
|
912
|
+
```typescript
|
|
913
|
+
interface AuthProviderProps {
|
|
914
|
+
children: React.ReactNode;
|
|
915
|
+
getToken: (username: string, password: string) => Promise<AuthTokenInterface>; // required
|
|
916
|
+
getUserData: (auth: AuthTokenInterface) => Promise<AuthContextUserInterface>; // required
|
|
917
|
+
refreshToken?: (auth: AuthTokenInterface) => Promise<AuthTokenInterface>;
|
|
918
|
+
removeToken?: (auth: AuthTokenInterface) => Promise<void>;
|
|
919
|
+
showLog?: boolean; // default: false
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
interface AuthContextType {
|
|
923
|
+
user: AuthContextUserInterface | null;
|
|
924
|
+
login: (username: string, password: string) => Promise<void>;
|
|
925
|
+
logout: () => void;
|
|
926
|
+
isAuthenticated: boolean;
|
|
927
|
+
isLoading: boolean;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
interface AuthContextUserInterface {
|
|
931
|
+
id?: number | string;
|
|
932
|
+
name?: string;
|
|
933
|
+
aditionalData?: any;
|
|
934
|
+
token?: AuthTokenInterface;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
interface AuthTokenInterface {
|
|
938
|
+
accessToken?: string;
|
|
939
|
+
expires?: string; // ISO 8601
|
|
940
|
+
tokenType?: string;
|
|
941
|
+
refreshToken?: string;
|
|
942
|
+
aditionalData?: any;
|
|
943
|
+
}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
```tsx
|
|
947
|
+
<AuthProvider
|
|
948
|
+
getToken={async (user, pass) => {
|
|
949
|
+
const res = await apiClient.post({ url: "/auth/login", body: { user, pass } });
|
|
950
|
+
return res.token;
|
|
951
|
+
}}
|
|
952
|
+
getUserData={async (auth) => {
|
|
953
|
+
return await apiClient.get({ url: "/auth/me" });
|
|
954
|
+
}}
|
|
955
|
+
refreshToken={async (auth) => {
|
|
956
|
+
return await apiClient.post({ url: "/auth/refresh", body: { token: auth.refreshToken } });
|
|
957
|
+
}}
|
|
958
|
+
>
|
|
959
|
+
<App />
|
|
960
|
+
</AuthProvider>
|
|
961
|
+
|
|
962
|
+
// In components
|
|
963
|
+
const { user, login, logout, isAuthenticated } = useContext(AuthContext);
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
**Behaviors**: Validates token on mount. Checks expiration every 60s. Auto-refreshes if `refreshToken` provided. Stores in localStorage as `"auth"`.
|
|
967
|
+
|
|
968
|
+
### CrudProvider / useCrud\<T\>
|
|
969
|
+
|
|
970
|
+
Generic CRUD context with automatic pagination, URL parameter sync, and snackbar notifications.
|
|
971
|
+
|
|
972
|
+
```typescript
|
|
973
|
+
interface CrudProviderProps<T> {
|
|
974
|
+
children: ReactNode;
|
|
975
|
+
getPromise?: Function | { execute: Function; successMessage?: string; errorMessage?: string | ((error: any) => string) };
|
|
976
|
+
getItemPromise?: Function | { execute: Function; successMessage?: string; errorMessage?: string | ((error: any) => string) };
|
|
977
|
+
postPromise?: Function | { execute: Function; successMessage?: string; errorMessage?: string | ((error: any) => string) };
|
|
978
|
+
putPromise?: Function | { execute: Function; successMessage?: string; errorMessage?: string | ((error: any) => string) };
|
|
979
|
+
deletePromise?: Function | { execute: Function; successMessage?: string; errorMessage?: string | ((error: any) => string) };
|
|
980
|
+
urlParams?: Array<string>; // URL params to watch. default: []
|
|
981
|
+
limit?: number; // Items per page. default: 15
|
|
982
|
+
pageParam?: string; // URL page param. default: "pagina"
|
|
983
|
+
singleItemId?: string | number;
|
|
984
|
+
extraData?: Record<string, any>;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
interface CrudContextType<T> {
|
|
988
|
+
list: Array<T> | undefined;
|
|
989
|
+
item: T | undefined;
|
|
990
|
+
page: number;
|
|
991
|
+
pages: number;
|
|
992
|
+
total: number;
|
|
993
|
+
limit: number;
|
|
994
|
+
isLoading: boolean;
|
|
995
|
+
pagination: ReactNode; // Pre-built Pagination component
|
|
996
|
+
params: Record<string, any>;
|
|
997
|
+
extraData?: Record<string, any>;
|
|
998
|
+
setExtraData: Dispatch<SetStateAction<Record<string, any> | undefined>>;
|
|
999
|
+
fetchItems: { execute: (params?: Record<string, any>) => Promise<void>; isLoading: boolean };
|
|
1000
|
+
fetchItem: { execute: (params?: Record<string, any> | string | number) => Promise<T | undefined>; isLoading: boolean };
|
|
1001
|
+
createItem: { execute: (item: T) => Promise<T | undefined | null>; isLoading: boolean };
|
|
1002
|
+
updateItem: { execute: (item: T) => Promise<T | undefined | null>; isLoading: boolean };
|
|
1003
|
+
deleteItem: { execute: (item: T) => Promise<void>; isLoading: boolean };
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
```tsx
|
|
1008
|
+
<CrudProvider<User>
|
|
1009
|
+
getPromise={(params) => apiClient.get({ url: "/api/users", params })}
|
|
1010
|
+
getItemPromise={(id) => apiClient.get({ url: `/api/users/${id}` })}
|
|
1011
|
+
postPromise={{ execute: (item) => apiClient.post({ url: "/api/users", body: item }), successMessage: "Usuario creado" }}
|
|
1012
|
+
putPromise={{ execute: (item) => apiClient.put({ url: `/api/users/${item.id}`, body: item }), successMessage: "Usuario actualizado" }}
|
|
1013
|
+
deletePromise={{ execute: (item) => apiClient.del({ url: `/api/users/${item.id}` }), successMessage: "Usuario eliminado" }}
|
|
1014
|
+
urlParams={["nombre", "estado"]}
|
|
1015
|
+
limit={20}
|
|
1016
|
+
>
|
|
1017
|
+
<UserList />
|
|
1018
|
+
</CrudProvider>
|
|
1019
|
+
|
|
1020
|
+
// In child components
|
|
1021
|
+
const { list, isLoading, pagination, createItem, deleteItem } = useCrud<User>();
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
**Behaviors**: Auto-fetches when URL params change. Resets pagination on filter change. Shows snackbar on success/error.
|
|
1025
|
+
|
|
1026
|
+
### SnackbarProvider / useSnackbar
|
|
1027
|
+
|
|
1028
|
+
Manages toast notifications.
|
|
1029
|
+
|
|
1030
|
+
```typescript
|
|
1031
|
+
interface SnackbarActionsType {
|
|
1032
|
+
showSnackbar: (
|
|
1033
|
+
message: string,
|
|
1034
|
+
variant?: SnackbarVariant,
|
|
1035
|
+
options?: { duration?: number; icon?: string; iconLabel?: string }
|
|
1036
|
+
) => void;
|
|
1037
|
+
removeSnackbar: (id: string) => void;
|
|
1038
|
+
}
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
```tsx
|
|
1042
|
+
// App root
|
|
1043
|
+
<SnackbarProvider>
|
|
1044
|
+
<SnackbarContainer position="bottom-right" maxSnackbars={3} />
|
|
1045
|
+
<App />
|
|
1046
|
+
</SnackbarProvider>
|
|
1047
|
+
|
|
1048
|
+
// In components
|
|
1049
|
+
const { showSnackbar } = useSnackbar();
|
|
1050
|
+
showSnackbar("Guardado exitosamente", "success");
|
|
1051
|
+
showSnackbar("Error de conexión", "danger", { duration: 5000 });
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
### AppLayoutProvider / useAppLayout
|
|
1055
|
+
|
|
1056
|
+
Combines ThemeProvider + SnackbarProvider + AppLayout into a single provider.
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
interface AppLayoutProviderProps {
|
|
1060
|
+
children: ReactNode;
|
|
1061
|
+
initialTheme?: string | Theme;
|
|
1062
|
+
storageKey?: string;
|
|
1063
|
+
forceInitialTheme?: boolean;
|
|
1064
|
+
initialNavbar?: NavbarInterface;
|
|
1065
|
+
initialLeftDrawer?: LeftDrawerInterface;
|
|
1066
|
+
initialContentFooter?: ReactNode;
|
|
1067
|
+
className?: string;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
interface AppLayoutContextType extends ThemeContextType {
|
|
1071
|
+
navbar: NavbarInterface | undefined;
|
|
1072
|
+
leftDrawer: LeftDrawerInterface | undefined;
|
|
1073
|
+
contentFooter: ReactNode | undefined;
|
|
1074
|
+
className: string;
|
|
1075
|
+
setNavbar: Dispatch<SetStateAction<NavbarInterface | undefined>>;
|
|
1076
|
+
setLeftDrawer: Dispatch<SetStateAction<LeftDrawerInterface | undefined>>;
|
|
1077
|
+
setContentFooter: (node: ReactNode | undefined) => void;
|
|
1078
|
+
setClassName: (className: string) => void;
|
|
1079
|
+
setNavBarLeftNode: (node: ReactNode | undefined) => void;
|
|
1080
|
+
setNavbarRightNode: (node: ReactNode | undefined) => void;
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
```tsx
|
|
1085
|
+
<AppLayoutProvider
|
|
1086
|
+
initialTheme="light"
|
|
1087
|
+
initialNavbar={{ navBarLeftNode: <h1>Mi App</h1>, fullWidthNavbar: true }}
|
|
1088
|
+
initialLeftDrawer={{ contentNode: <nav>...</nav> }}
|
|
1089
|
+
>
|
|
1090
|
+
<Routes />
|
|
1091
|
+
</AppLayoutProvider>
|
|
1092
|
+
|
|
1093
|
+
// In pages - dynamically update layout
|
|
1094
|
+
const { setNavBarLeftNode, setNavbarRightNode } = useAppLayout();
|
|
1095
|
+
useEffect(() => {
|
|
1096
|
+
setNavBarLeftNode(<h1>Dashboard</h1>);
|
|
1097
|
+
}, []);
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
---
|
|
1101
|
+
|
|
1102
|
+
## Hooks
|
|
1103
|
+
|
|
1104
|
+
### useThemeOverride
|
|
1105
|
+
|
|
1106
|
+
Applies granular CSS variable overrides without changing the entire theme.
|
|
1107
|
+
|
|
1108
|
+
```typescript
|
|
1109
|
+
function useThemeOverride(options?: {
|
|
1110
|
+
scope?: "global" | "local"; // default: "global"
|
|
1111
|
+
element?: HTMLElement | null;
|
|
1112
|
+
prefix?: string; // default: "flysoft"
|
|
1113
|
+
}): {
|
|
1114
|
+
applyOverride: (overrides: Record<string, string | number>) => void;
|
|
1115
|
+
revertOverride: (keys: string[]) => void;
|
|
1116
|
+
revertAllOverrides: () => void;
|
|
1117
|
+
getCSSVariable: (key: string) => string | null;
|
|
1118
|
+
isOverrideApplied: (key: string) => boolean;
|
|
1119
|
+
appliedOverridesCount: number;
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### useTemporaryOverride
|
|
1124
|
+
|
|
1125
|
+
Applies CSS variable overrides that auto-revert after a duration.
|
|
1126
|
+
|
|
1127
|
+
```typescript
|
|
1128
|
+
function useTemporaryOverride(
|
|
1129
|
+
overrides: Record<string, string | number>,
|
|
1130
|
+
duration?: number, // default: 3000
|
|
1131
|
+
options?: { scope?: "global" | "local"; element?: HTMLElement | null; prefix?: string }
|
|
1132
|
+
): { applyTemporaryOverride: () => Function }
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
### useBreakpoint
|
|
1136
|
+
|
|
1137
|
+
Returns current viewport breakpoint and device type.
|
|
1138
|
+
|
|
1139
|
+
```typescript
|
|
1140
|
+
type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
1141
|
+
|
|
1142
|
+
function useBreakpoint(): {
|
|
1143
|
+
breakpoint: Breakpoint;
|
|
1144
|
+
windowSize: { width: number; height: number };
|
|
1145
|
+
isMobile: boolean; // xs or sm
|
|
1146
|
+
isTablet: boolean; // md
|
|
1147
|
+
isDesktop: boolean; // lg, xl, or 2xl
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
### useElementScroll
|
|
1152
|
+
|
|
1153
|
+
Tracks scroll position and direction with requestAnimationFrame optimization.
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
function useElementScroll(elementRef: React.RefObject<HTMLElement | null>): {
|
|
1157
|
+
scrollY: number;
|
|
1158
|
+
scrollDirection: "up" | "down" | null;
|
|
1159
|
+
}
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
### useAsyncRequest
|
|
1163
|
+
|
|
1164
|
+
Manages async operations with loading state and snackbar notifications.
|
|
1165
|
+
|
|
1166
|
+
```typescript
|
|
1167
|
+
interface AsyncRequestOptions {
|
|
1168
|
+
successMessage?: string;
|
|
1169
|
+
errorMessage?: string | ((error: any) => string);
|
|
1170
|
+
successVariant?: SnackbarVariant; // default: "success"
|
|
1171
|
+
errorVariant?: SnackbarVariant; // default: "danger"
|
|
1172
|
+
onSuccess?: (data: any) => void;
|
|
1173
|
+
onError?: (error: any) => void;
|
|
1174
|
+
onFinally?: () => void;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function useAsyncRequest(options?: AsyncRequestOptions): {
|
|
1178
|
+
isLoading: boolean;
|
|
1179
|
+
execute: <T>(requestFn: () => Promise<T>) => Promise<T | undefined>;
|
|
1180
|
+
setLoading: (loading: boolean) => void;
|
|
1181
|
+
}
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
```tsx
|
|
1185
|
+
const { execute, isLoading } = useAsyncRequest({
|
|
1186
|
+
successMessage: "Guardado exitosamente",
|
|
1187
|
+
errorMessage: (err) => getErrorMessage(err),
|
|
1188
|
+
});
|
|
1189
|
+
await execute(() => apiClient.post({ url: "/api/data", body: formData }));
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
### useEnum
|
|
1193
|
+
|
|
1194
|
+
Converts TypeScript enums to arrays for form select options.
|
|
1195
|
+
|
|
1196
|
+
```typescript
|
|
1197
|
+
function useEnum(baseEnum: any): {
|
|
1198
|
+
getArray: () => Array<NameValueInterface<number>>;
|
|
1199
|
+
getInstance: (id: number) => NameValueInterface<number> | undefined;
|
|
1200
|
+
}
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
### useGlobalThemeStyles
|
|
1204
|
+
|
|
1205
|
+
Applies theme colors to `<body>` and `<html>` for full-page theming. No return value.
|
|
1206
|
+
|
|
1207
|
+
```tsx
|
|
1208
|
+
function useGlobalThemeStyles(): void;
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
---
|
|
1212
|
+
|
|
1213
|
+
## Services
|
|
1214
|
+
|
|
1215
|
+
### apiClient
|
|
1216
|
+
|
|
1217
|
+
Singleton HTTP client (Axios-based) with automatic Bearer token injection.
|
|
1218
|
+
|
|
1219
|
+
```typescript
|
|
1220
|
+
// Main methods
|
|
1221
|
+
apiClient.get<T>(options: { url: string; params?: Record<string, unknown>; headers?: Record<string, string> }): Promise<T>;
|
|
1222
|
+
apiClient.post<T>(options: { url: string; body?: unknown; headers?: Record<string, string> }): Promise<T>;
|
|
1223
|
+
apiClient.put<T>(options: { url: string; body?: unknown; headers?: Record<string, string> }): Promise<T>;
|
|
1224
|
+
apiClient.del<T>(options: { url: string; headers?: Record<string, string> }): Promise<T>;
|
|
1225
|
+
|
|
1226
|
+
// File operations
|
|
1227
|
+
apiClient.getFile(options): Promise<{ data: Blob; headers: any }>;
|
|
1228
|
+
apiClient.getFileAsUrl(options): Promise<string>;
|
|
1229
|
+
apiClient.openFile(options): Promise<void>;
|
|
1230
|
+
apiClient.downloadFile(options): Promise<void>;
|
|
1231
|
+
apiClient.uploadFile<T>(options: { url: string; files: FileList | File[]; headers?: { paramName?: string } }): Promise<T>;
|
|
1232
|
+
|
|
1233
|
+
// Token management
|
|
1234
|
+
setApiClientTokenProvider(provider?: () => string | undefined): void;
|
|
1235
|
+
clearApiClientTokenProvider(): void;
|
|
1236
|
+
|
|
1237
|
+
// Create isolated instances
|
|
1238
|
+
createApiClient(config?: { baseURL?: string; timeout?: number; headers?: Record<string, string> }): ApiClientService;
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
```tsx
|
|
1242
|
+
// Setup token globally
|
|
1243
|
+
setApiClientTokenProvider(() => user?.token?.accessToken);
|
|
1244
|
+
|
|
1245
|
+
// API calls
|
|
1246
|
+
const users = await apiClient.get<User[]>({ url: "/api/users", params: { page: 1 } });
|
|
1247
|
+
await apiClient.post({ url: "/api/users", body: { name: "Juan" } });
|
|
1248
|
+
await apiClient.downloadFile({ url: "/api/reports/pdf" });
|
|
1249
|
+
await apiClient.uploadFile({ url: "/api/upload", files: fileInput.files });
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
---
|
|
1253
|
+
|
|
1254
|
+
## Helpers
|
|
1255
|
+
|
|
1256
|
+
| Function | Signature | Description |
|
|
1257
|
+
|----------|-----------|-------------|
|
|
1258
|
+
| `currencyFormat` | `(value: number) => string` | Formats as `"1.234,56"` (es-AR locale) |
|
|
1259
|
+
| `getErrorMessage` | `(error: any) => string` | Extracts message from AxiosError. Default: `"Ha ocurrido un error"` |
|
|
1260
|
+
| `getInitialLetters` | `(text: string) => string` | `"Juan Pérez"` → `"JP"` |
|
|
1261
|
+
| `getQueryString` | `(params: URLSearchParams, newParams: any) => string` | Merges params, returns `"?key=value"` |
|
|
1262
|
+
| `objectToQueryString` | `(source: any) => string` | Object to `"a=1&b=2"` (no leading `?`) |
|
|
1263
|
+
| `queryStringToObject` | `(params: string) => Record<string, string>` | `"a=1&b=2"` → `{a: "1", b: "2"}` |
|
|
1264
|
+
| `nameValueArrayToObject` | `<T>(arr: NameValueInterface<T>[]) => Record<string, T>` | Array of {name, value} to object |
|
|
1265
|
+
| `promiseMapper` | `<T, K>(promise, mapper) => Promise<K \| K[] \| PaginationInterface<K>>` | Maps promise results (arrays, pagination, single) |
|
|
1266
|
+
| `RegularExpressions` | Object | `.email`, `.dateString`, `.password(config)` regex patterns |
|
|
1267
|
+
|
|
1268
|
+
## Interfaces
|
|
1269
|
+
|
|
1270
|
+
```typescript
|
|
1271
|
+
interface NameValueInterface<T> {
|
|
1272
|
+
name: string;
|
|
1273
|
+
value: T;
|
|
1274
|
+
extras?: any;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
interface PaginationInterface<T> {
|
|
1278
|
+
list: Array<T>;
|
|
1279
|
+
limit: number;
|
|
1280
|
+
page: number;
|
|
1281
|
+
pages: number;
|
|
1282
|
+
total: number;
|
|
1283
|
+
}
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
---
|
|
1287
|
+
|
|
1288
|
+
## Templates
|
|
1289
|
+
|
|
1290
|
+
### LoginForm
|
|
1291
|
+
|
|
1292
|
+
```typescript
|
|
1293
|
+
interface LoginFormProps {
|
|
1294
|
+
onSubmit?: (data: { email: string; password: string }) => void;
|
|
1295
|
+
loading?: boolean;
|
|
1296
|
+
error?: string;
|
|
1297
|
+
className?: string;
|
|
1298
|
+
}
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### RegistrationForm
|
|
1302
|
+
|
|
1303
|
+
```typescript
|
|
1304
|
+
interface RegistrationFormProps {
|
|
1305
|
+
onSubmit?: (data: { firstName: string; lastName: string; email: string; password: string; confirmPassword: string }) => void;
|
|
1306
|
+
loading?: boolean;
|
|
1307
|
+
error?: string;
|
|
1308
|
+
className?: string;
|
|
1309
|
+
}
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
### ContactForm
|
|
1313
|
+
|
|
1314
|
+
```typescript
|
|
1315
|
+
interface ContactFormProps {
|
|
1316
|
+
onSubmit?: (data: { name: string; email: string; subject: string; message: string }) => void;
|
|
1317
|
+
loading?: boolean;
|
|
1318
|
+
success?: boolean;
|
|
1319
|
+
error?: string;
|
|
1320
|
+
className?: string;
|
|
1321
|
+
}
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
### DashboardLayout
|
|
1325
|
+
|
|
1326
|
+
```typescript
|
|
1327
|
+
interface DashboardStat {
|
|
1328
|
+
title: string;
|
|
1329
|
+
value: string | number;
|
|
1330
|
+
change?: string; // e.g. "+12%"
|
|
1331
|
+
changeType?: "positive" | "negative" | "neutral";
|
|
1332
|
+
icon?: string;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
interface DashboardLayoutProps {
|
|
1336
|
+
title: string; // required
|
|
1337
|
+
subtitle?: string;
|
|
1338
|
+
stats?: DashboardStat[];
|
|
1339
|
+
actions?: React.ReactNode;
|
|
1340
|
+
children: React.ReactNode; // required
|
|
1341
|
+
className?: string;
|
|
1342
|
+
}
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
### SidebarLayout
|
|
1346
|
+
|
|
1347
|
+
```typescript
|
|
1348
|
+
interface MenuItem {
|
|
1349
|
+
label: string;
|
|
1350
|
+
icon: string;
|
|
1351
|
+
href: string;
|
|
1352
|
+
badge?: string | number;
|
|
1353
|
+
children?: MenuItem[];
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
interface User {
|
|
1357
|
+
name: string;
|
|
1358
|
+
email?: string;
|
|
1359
|
+
avatar?: string;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
interface SidebarLayoutProps {
|
|
1363
|
+
title: string; // required
|
|
1364
|
+
menuItems: MenuItem[]; // required
|
|
1365
|
+
user: User; // required
|
|
1366
|
+
children: React.ReactNode; // required
|
|
1367
|
+
className?: string;
|
|
1368
|
+
onLogout?: () => void;
|
|
1369
|
+
}
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
### FormPattern
|
|
1373
|
+
|
|
1374
|
+
```typescript
|
|
1375
|
+
interface FormField {
|
|
1376
|
+
name: string;
|
|
1377
|
+
label: string;
|
|
1378
|
+
type?: string; // default: "text"
|
|
1379
|
+
placeholder?: string;
|
|
1380
|
+
icon?: string;
|
|
1381
|
+
required?: boolean;
|
|
1382
|
+
validation?: (value: string) => string | undefined;
|
|
1383
|
+
multiline?: boolean;
|
|
1384
|
+
rows?: number; // default: 4
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
interface FormPatternProps {
|
|
1388
|
+
title: string; // required
|
|
1389
|
+
subtitle?: string;
|
|
1390
|
+
fields: FormField[]; // required
|
|
1391
|
+
onSubmit: (data: Record<string, string>) => void; // required
|
|
1392
|
+
submitText?: string; // default: "Enviar"
|
|
1393
|
+
submitIcon?: string; // default: "fa-paper-plane"
|
|
1394
|
+
loading?: boolean;
|
|
1395
|
+
error?: string;
|
|
1396
|
+
success?: boolean;
|
|
1397
|
+
className?: string;
|
|
1398
|
+
gridCols?: 1 | 2; // default: 1
|
|
1399
|
+
}
|
|
1400
|
+
```
|