flysoft-react-ui 1.2.9 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AI_CONTEXT.md +1518 -1400
  2. package/AI_INTEGRATION_GUIDE.md +357 -343
  3. package/README.md +484 -464
  4. package/dist/components/form-controls/Button.d.ts.map +1 -1
  5. package/dist/components/form-controls/Checkbox.d.ts.map +1 -1
  6. package/dist/components/form-controls/DatePicker.d.ts.map +1 -1
  7. package/dist/components/form-controls/Input.d.ts.map +1 -1
  8. package/dist/components/form-controls/LinkButton.d.ts.map +1 -1
  9. package/dist/components/form-controls/Pagination.d.ts.map +1 -1
  10. package/dist/components/form-controls/RadioButtonGroup.d.ts.map +1 -1
  11. package/dist/components/layout/Accordion.d.ts.map +1 -1
  12. package/dist/components/layout/Card.d.ts +5 -0
  13. package/dist/components/layout/Card.d.ts.map +1 -1
  14. package/dist/components/layout/Collection.d.ts +17 -1
  15. package/dist/components/layout/Collection.d.ts.map +1 -1
  16. package/dist/components/layout/DataField.d.ts +19 -0
  17. package/dist/components/layout/DataField.d.ts.map +1 -1
  18. package/dist/components/layout/DataTable.d.ts.map +1 -1
  19. package/dist/components/layout/DropdownMenu.d.ts.map +1 -1
  20. package/dist/components/layout/DropdownPanel.d.ts.map +1 -1
  21. package/dist/components/layout/Filter.d.ts.map +1 -1
  22. package/dist/components/layout/Menu.d.ts.map +1 -1
  23. package/dist/components/layout/TabsGroup.d.ts.map +1 -1
  24. package/dist/components/utils/Avatar.d.ts.map +1 -1
  25. package/dist/components/utils/Badge.d.ts.map +1 -1
  26. package/dist/components/utils/Dialog.d.ts +1 -0
  27. package/dist/components/utils/Dialog.d.ts.map +1 -1
  28. package/dist/components/utils/Loader.d.ts.map +1 -1
  29. package/dist/components/utils/RoadMap.d.ts.map +1 -1
  30. package/dist/components/utils/Snackbar.d.ts.map +1 -1
  31. package/dist/contexts/AppLayoutContext.d.ts +5 -1
  32. package/dist/contexts/AppLayoutContext.d.ts.map +1 -1
  33. package/dist/contexts/ThemeContext.d.ts +11 -1
  34. package/dist/contexts/ThemeContext.d.ts.map +1 -1
  35. package/dist/contexts/index.d.ts +2 -2
  36. package/dist/contexts/index.d.ts.map +1 -1
  37. package/dist/contexts/presets.d.ts +5 -1
  38. package/dist/contexts/presets.d.ts.map +1 -1
  39. package/dist/contexts/types.d.ts +51 -0
  40. package/dist/contexts/types.d.ts.map +1 -1
  41. package/dist/index.css +1 -1
  42. package/dist/index.js +4922 -4379
  43. package/dist/index.js.map +1 -1
  44. package/package.json +1 -1
@@ -1,343 +1,357 @@
1
- # AI Integration Guide (Consumer Projects)
2
-
3
- This file is designed to be copied into any client project that consumes `flysoft-react-ui`. It helps AI agents understand the complete API surface and generate correct code.
4
-
5
- ## 1) Install
6
-
7
- ```bash
8
- npm install flysoft-react-ui
9
- ```
10
-
11
- ## 2) Required Runtime Setup
12
-
13
- At app root:
14
-
15
- ```tsx
16
- import { ThemeProvider } from "flysoft-react-ui";
17
- import "flysoft-react-ui/styles";
18
-
19
- export function AppRoot() {
20
- return <ThemeProvider initialTheme="light">{/* app */}</ThemeProvider>;
21
- }
22
- ```
23
-
24
- For full app layout with navbar, sidebar, and snackbars:
25
-
26
- ```tsx
27
- import { AppLayoutProvider } from "flysoft-react-ui";
28
- import "flysoft-react-ui/styles";
29
-
30
- export function AppRoot() {
31
- return (
32
- <AppLayoutProvider
33
- initialTheme="light"
34
- initialNavbar={{ navBarLeftNode: <h1>Mi App</h1>, fullWidthNavbar: true }}
35
- >
36
- {/* routes */}
37
- </AppLayoutProvider>
38
- );
39
- }
40
- ```
41
-
42
- ## 3) Copy-paste prompt for AI agents
43
-
44
- Copy the following block into `.cursorrules`, `AGENTS.md`, `copilot-instructions.md`, `CLAUDE.md`, or your AI system prompt:
45
-
46
- ---
47
-
48
- ```md
49
- This project uses `flysoft-react-ui` as the default UI library.
50
-
51
- ## Rules
52
- 1. Always import from `flysoft-react-ui` (top-level only). Never from internal paths.
53
- 2. Prefer existing library components before creating custom UI. Never duplicate Button, Input, Card, Badge, Dialog, etc.
54
- 3. Use exported TypeScript types for type safety (e.g. ButtonProps, DataTableColumn<T>).
55
- 4. Keep style import at app root only: `import "flysoft-react-ui/styles";`
56
- 5. Wrap app with `ThemeProvider` (or `AppLayoutProvider` for full layout).
57
- 6. Use FontAwesome 5 icon classes (`fa-*`). Components auto-normalize to light style (fal). Never use other icon libraries.
58
- 7. Use theme CSS variables for custom styling: `var(--color-primary)`, `var(--color-bg-default)`, etc.
59
-
60
- ## Available Components
61
-
62
- ### Form Controls
63
- - `Button` variant: "primary"|"outline"|"ghost", size: "sm"|"md"|"lg", color: "primary"|"secondary"|"success"|"warning"|"danger"|"info", icon, loading, bg, textColor
64
- - `LinkButton` — Same as Button but renders as link. Props: to (route/URL), target, variant, size, color, icon
65
- - `Input` — label, error, icon, iconPosition, size, onIconClick, readOnly. Extends HTML input attributes
66
- - `AutocompleteInput<T,K>` Searchable dropdown. Props: options, value, onChange, multiple, getOptionLabel, getOptionValue, renderOption, noResultsText
67
- - `SearchSelectInput<T,K>` Dialog-based async search. Props: onSearchPromiseFn, onSingleSearchPromiseFn, dialogTitle, getOptionLabel, getOptionValue
68
- - `DatePicker` Standalone calendar. Props: value (Dayjs), onChange, startWeekOn
69
- - `DateInput` Input with DatePicker dropdown. Props: value (Dayjs|string), onChange, format ("dd/mm/yyyy"|"mm/dd/yyyy")
70
- - `Checkbox` Props: label, labelPosition, error, size, readOnly
71
- - `RadioButtonGroup` Props: options ({label,value,disabled}[]), value, onChange, direction ("vertical"|"horizontal"), gap, size
72
- - `CurrencyInput` Numeric input formatted as currency (es-AR: 1.234,56). Props: value (number), onChange
73
- - `Pagination` — URL-based pagination. Props: page, pages, total, isLoading, fieldName
74
-
75
- ### Layout & Data
76
- - `Card` — Props: title, subtitle, headerActions, footer, variant ("default"|"elevated"|"outlined"), compact, alwaysDisplayHeaderActions
77
- - `AppLayout` — Main layout. Props: navbar (NavbarInterface), leftDrawer (LeftDrawerInterface), children
78
- - `Collection` — Flex container. Props: gap, direction, wrap
79
- - `DataField` — Label+value pair. Props: label, value, inline, align, link
80
- - `TabsGroup` + `TabPanel` Tabbed interface. TabsGroup: tabs ({id,label}[]), paramName (URL sync). TabPanel: tabId
81
- - `DataTable<T>` — Data table. Props: columns (DataTableColumn<T>[]), rows, maxRows, isLoading, loadingRows, compact, locale
82
- - DataTableColumn: header, value (key or function), type ("text"|"numeric"|"currency"|"date"), actions, width, align, tooltip, footer
83
- - `Accordion` — Collapsible section. Props: title, icon, rightNode, defaultOpen, variant, onToggle
84
- - `Menu<T>`Simple menu list. Props: options, onOptionSelected, getOptionLabel, renderOption
85
- - `DropdownMenu<T>`Portal dropdown. Props: options, onOptionSelected, renderNode, openOnHover, replaceOnSingleOption
86
- - `DropdownPanel` — Portal dropdown with arbitrary content. Props: children, renderNode, openOnHover
87
- - `Filter` — Versatile filter by type. filterType: "text"|"number"|"date"|"autocomplete"|"search"|"searchSelect". Props: paramName (URL sync), label, value, onChange
88
-
89
- ### Utility & Feedback
90
- - `Badge` — Props: variant, size, rounded, icon, iconPosition, bg, textColor, onClick
91
- - `Avatar` — Props: text (for initials), image, bgColor, textColor, size
92
- - `RoadMap` — Stage visualization. Props: stages ({name, description?, icon?, variant?, bg?, disabled?}[])
93
- - `Dialog` — Modal. Props: isOpen, title, children, footer, onClose, closeOnOverlayClick, compact
94
- - `Loader` — Loading indicator. Props: isLoading, text, keepContentWhileLoading, contentLoadingNode, overlayClassName
95
- - `FiltersDialog`Groups multiple filters in dialog. Props: filters (FilterConfig[])
96
- - `Snackbar` (Used internally) Toast notification
97
- - `SnackbarContainer` — Place at root. Props: position, maxSnackbars
98
- - `Skeleton`Pulse placeholder. Props: className
99
- - `ThemeSwitcher`No props. Self-contained theme toggle
100
-
101
- ### Templates
102
- - `LoginForm` — Props: onSubmit, loading, error, className
103
- - `RegistrationForm` Props: onSubmit, loading, error, className
104
- - `ContactForm` — Props: onSubmit, loading, success, error, className
105
- - `DashboardLayout` — Props: title, subtitle, stats (DashboardStat[]), actions, children
106
- - `SidebarLayout` — Props: title, menuItems (MenuItem[]), user (User), onLogout, children
107
- - `FormPattern` — Dynamic form builder. Props: title, fields (FormField[]), onSubmit, gridCols, submitText, submitIcon, loading, error, success
108
- - `ListPattern<T>`List page with Card + search + filters + pagination + DataTable. Props: title, columns, rows, searchParamName, onAdd, addButtonText, filtersNode, page, pages, total, isLoading, compact
109
-
110
- ### Contexts & Hooks
111
- - `ThemeProvider` — initialTheme, storageKey, forceInitialTheme, onThemeChange
112
- - `useTheme()` — Returns: theme, setTheme, updateTheme, currentThemeName, availableThemes, isDark, resetToDefault
113
- - `AuthProvider` — getToken, getUserData, refreshToken, removeToken
114
- - `AuthContext` — user, login, logout, isAuthenticated, isLoading
115
- - `CrudProvider<T>` — getPromise, getItemPromise, postPromise, putPromise, deletePromise, urlParams, limit, pageParam
116
- - `useCrud<T>()` — Returns: list, item, isLoading, pagination, fetchItems, fetchItem, createItem, updateItem, deleteItem, params, page, pages, total
117
- - `SnackbarProvider` + `useSnackbar()` showSnackbar(message, variant?, options?), removeSnackbar(id)
118
- - `AppLayoutProvider` — Combines Theme + Snackbar + AppLayout. useAppLayout() to set navbar/drawer dynamically
119
- - `useAsyncRequest(options)` — Returns: execute(fn), isLoading. Options: successMessage, errorMessage, onSuccess, onError
120
- - `useBreakpoint()` — Returns: breakpoint, windowSize, isMobile, isTablet, isDesktop
121
- - `useThemeOverride(options)` — Returns: applyOverride, revertOverride, revertAllOverrides
122
- - `useElementScroll(ref)`Returns: scrollY, scrollDirection
123
- - `useEnum(enum)` — Returns: getArray(), getInstance(id)
124
-
125
- ### Services
126
- - `apiClient` — HTTP client. Methods: get<T>, post<T>, put<T>, del<T>, getFile, downloadFile, uploadFile, openFile
127
- - `setApiClientTokenProvider(fn)` — Set auth token globally
128
- - `createApiClient(config)` — Create isolated API client instance
129
-
130
- ### Helpers
131
- - `currencyFormat(n)` "1.234,56"
132
- - `getErrorMessage(error)` user-friendly error string
133
- - `getInitialLetters(text)` "JP" from "Juan Pérez"
134
- - `objectToQueryString(obj)` "a=1&b=2"
135
- - `queryStringToObject(str)` {a: "1", b: "2"}
136
- - `promiseMapper(promise, mapperFn)` — Maps paginated/array/single results
137
- - `RegularExpressions` — .email, .dateString, .password(config)
138
-
139
- ### Interfaces
140
- - `NameValueInterface<T>`{ name: string; value: T; extras?: any }
141
- - `PaginationInterface<T>`{ list: T[]; limit: number; page: number; pages: number; total: number }
142
-
143
- ## Common Patterns
144
-
145
- ### CRUD List Page
146
- ​```tsx
147
- <CrudProvider<User>
148
- getPromise={(params) => apiClient.get({ url: "/api/users", params })}
149
- deletePromise={{ execute: (u) => apiClient.del({ url: `/api/users/${u.id}` }), successMessage: "Eliminado" }}
150
- urlParams={["nombre"]}
151
- >
152
- <UserList />
153
- </CrudProvider>
154
-
155
- function UserList() {
156
- const { list, isLoading, pagination, deleteItem } = useCrud<User>();
157
- const columns: DataTableColumn<User>[] = [
158
- { header: "Nombre", value: "name" },
159
- { header: "Email", value: "email" },
160
- { actions: (row) => [
161
- <Button key="del" variant="ghost" size="sm" icon="fa-trash" color="danger"
162
- loading={deleteItem.isLoading} onClick={() => deleteItem.execute(row)}>
163
- Eliminar
164
- </Button>
165
- ]}
166
- ];
167
- return (
168
- <Card title="Usuarios">
169
- <Filter filterType="text" paramName="nombre" label="Nombre" />
170
- <DataTable columns={columns} rows={list ?? []} isLoading={isLoading} />
171
- {pagination}
172
- </Card>
173
- );
174
- }
175
- ​```
176
-
177
- ### Form with Async Save
178
- ​```tsx
179
- const { execute, isLoading } = useAsyncRequest({
180
- successMessage: "Guardado exitosamente",
181
- errorMessage: (err) => getErrorMessage(err),
182
- });
183
-
184
- <Card title="Nuevo Usuario">
185
- <Input label="Nombre" value={name} onChange={(e) => setName(e.target.value)} icon="fa-user" />
186
- <Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} icon="fa-envelope" />
187
- <Button variant="primary" icon="fa-save" loading={isLoading}
188
- onClick={() => execute(() => apiClient.post({ url: "/api/users", body: { name, email } }))}>
189
- Guardar
190
- </Button>
191
- </Card>
192
- ​```
193
-
194
- ### Dialog Confirmation
195
- ​```tsx
196
- <Dialog isOpen={showConfirm} title="Confirmar eliminación" onClose={() => setShowConfirm(false)}
197
- footer={<>
198
- <Button variant="ghost" onClick={() => setShowConfirm(false)}>Cancelar</Button>
199
- <Button variant="primary" color="danger" icon="fa-trash" onClick={handleDelete}>Eliminar</Button>
200
- </>}
201
- >
202
- <p>¿Está seguro?</p>
203
- </Dialog>
204
- ​```
205
-
206
- ### List Page (without CrudContext)
207
- Full implementation of Card + search + filters + pagination + DataTable. Uses URL query params for filters and pagination.
208
- ​```tsx
209
- import { useState, useEffect } from "react";
210
- import { useSearchParams } from "react-router-dom";
211
- import {
212
- Card, Button, Collection, Filter, Pagination, DataTable, Dialog,
213
- } from "flysoft-react-ui";
214
- import type { DataTableColumn } from "flysoft-react-ui";
215
-
216
- interface User { id: number; name: string; email: string; role: string; }
217
-
218
- function UserListPage() {
219
- const [searchParams] = useSearchParams();
220
- const [users, setUsers] = useState<User[]>([]);
221
- const [isLoading, setIsLoading] = useState(false);
222
- const [page, setPage] = useState(1);
223
- const [pages, setPages] = useState(1);
224
- const [total, setTotal] = useState(0);
225
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
226
- const [selectedUser, setSelectedUser] = useState<User>();
227
-
228
- // Read filters from URL
229
- const search = searchParams.get("buscar") || "";
230
- const role = searchParams.get("rol") || "";
231
- const currentPage = Number(searchParams.get("pagina") || "1");
232
-
233
- useEffect(() => {
234
- setIsLoading(true);
235
- fetchUsers({ search, role, page: currentPage }).then((res) => {
236
- setUsers(res.list);
237
- setPage(res.page);
238
- setPages(res.pages);
239
- setTotal(res.total);
240
- setIsLoading(false);
241
- });
242
- }, [search, role, currentPage]);
243
-
244
- const columns: DataTableColumn<User>[] = [
245
- { header: "Nombre", value: (row) => row.name },
246
- { header: "Email", value: (row) => row.email },
247
- { header: "Rol", value: (row) => row.role },
248
- {
249
- align: "center",
250
- actions: (row) => [
251
- <Button key="edit" size="sm" variant="ghost" icon="fa-edit"
252
- onClick={() => handleEdit(row)}>Editar</Button>,
253
- <Button key="del" size="sm" variant="ghost" icon="fa-trash"
254
- onClick={() => { setSelectedUser(row); setShowDeleteDialog(true); }}>
255
- Eliminar
256
- </Button>,
257
- ],
258
- },
259
- ];
260
-
261
- return (
262
- <>
263
- <Card
264
- title="Usuarios"
265
- alwaysDisplayHeaderActions
266
- headerActions={
267
- <Button icon="fa-plus" onClick={() => handleAdd()}>
268
- Nuevo Usuario
269
- </Button>
270
- }
271
- >
272
- <div className="flex justify-between items-center my-2">
273
- <Collection direction="row" wrap>
274
- <Filter paramName="buscar" label="Buscar" filterType="search" />
275
- <Filter paramName="rol" label="Rol" filterType="autocomplete"
276
- options={[
277
- { label: "Admin", value: "admin" },
278
- { label: "Editor", value: "editor" },
279
- ]}
280
- />
281
- </Collection>
282
- <Collection direction="row" wrap>
283
- <Pagination page={page} pages={pages} total={total}
284
- fieldName="pagina" isLoading={isLoading} />
285
- </Collection>
286
- </div>
287
- <DataTable columns={columns} rows={users}
288
- isLoading={isLoading} loadingRows={10} maxRows={10} />
289
- </Card>
290
-
291
- <Dialog isOpen={showDeleteDialog} title="Eliminar usuario"
292
- onClose={() => setShowDeleteDialog(false)}
293
- footer={<>
294
- <Button variant="outline" onClick={() => setShowDeleteDialog(false)}>Cancelar</Button>
295
- <Button variant="primary" color="danger" icon="fa-trash"
296
- onClick={() => handleDelete(selectedUser)}>Eliminar</Button>
297
- </>}
298
- >
299
- <p>¿Está seguro de eliminar a {selectedUser?.name}?</p>
300
- </Dialog>
301
- </>
302
- );
303
- }
304
- ​```
305
-
306
- ### List Page (using ListPattern template)
307
- Same pattern as above but using the `ListPattern` template component for less boilerplate:
308
- ​```tsx
309
- import { ListPattern, Filter } from "flysoft-react-ui";
310
- import type { DataTableColumn } from "flysoft-react-ui";
311
-
312
- const columns: DataTableColumn<User>[] = [
313
- { header: "Nombre", value: (row) => row.name },
314
- { header: "Email", value: (row) => row.email },
315
- { actions: (row) => [
316
- <Button key="edit" size="sm" variant="ghost" icon="fa-edit" onClick={() => edit(row)}>Editar</Button>,
317
- ]},
318
- ];
319
-
320
- <ListPattern<User>
321
- title="Usuarios"
322
- columns={columns}
323
- rows={users}
324
- searchParamName="buscar"
325
- addButtonText="Nuevo"
326
- onAdd={() => setShowForm(true)}
327
- filtersNode={
328
- <Filter paramName="rol" label="Rol" filterType="autocomplete"
329
- options={[{ label: "Admin", value: "admin" }]} />
330
- }
331
- page={page} pages={pages} total={total}
332
- isLoading={isLoading}
333
- />
334
- ​```
335
- ```
336
-
337
- ---
338
-
339
- ## 4) Notes for library maintainers
340
-
341
- - `src/docs/**` is local demo/dev-only and is not part of public API.
342
- - Public API must be exported from `src/index.ts`.
343
- - See `AI_CONTEXT.md` for complete prop interfaces and detailed documentation.
1
+ # AI Integration Guide (Consumer Projects)
2
+
3
+ This file is designed to be copied into any client project that consumes `flysoft-react-ui`. It helps AI agents understand the complete API surface and generate correct code.
4
+
5
+ ## 1) Install
6
+
7
+ ```bash
8
+ npm install flysoft-react-ui
9
+ ```
10
+
11
+ ## 2) Required Runtime Setup
12
+
13
+ At app root:
14
+
15
+ ```tsx
16
+ import { ThemeProvider } from "flysoft-react-ui";
17
+ import "flysoft-react-ui/styles";
18
+
19
+ export function AppRoot() {
20
+ return <ThemeProvider initialTheme="light">{/* app */}</ThemeProvider>;
21
+ }
22
+ ```
23
+
24
+ For data-heavy apps (admin panels, dashboards, CRUDs with lots of info per
25
+ screen), set a tighter global `density`:
26
+
27
+ ```tsx
28
+ <ThemeProvider initialTheme="light" density="dense">
29
+ {/* app */}
30
+ </ThemeProvider>
31
+ ```
32
+
33
+ Density is a global axis (`"comfortable"` | `"compact"` | `"dense"`) that
34
+ adjusts padding, gaps, typography and control heights via CSS variables.
35
+ Per-component overrides (`compact`, `size`) keep working on top of it.
36
+
37
+ For full app layout with navbar, sidebar, and snackbars:
38
+
39
+ ```tsx
40
+ import { AppLayoutProvider } from "flysoft-react-ui";
41
+ import "flysoft-react-ui/styles";
42
+
43
+ export function AppRoot() {
44
+ return (
45
+ <AppLayoutProvider
46
+ initialTheme="light"
47
+ density="dense" // optional: comfortable | compact | dense
48
+ initialNavbar={{ navBarLeftNode: <h1>Mi App</h1>, fullWidthNavbar: true }}
49
+ >
50
+ {/* routes */}
51
+ </AppLayoutProvider>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ## 3) Copy-paste prompt for AI agents
57
+
58
+ Copy the following block into `.cursorrules`, `AGENTS.md`, `copilot-instructions.md`, `CLAUDE.md`, or your AI system prompt:
59
+
60
+ ---
61
+
62
+ ```md
63
+ This project uses `flysoft-react-ui` as the default UI library.
64
+
65
+ ## Rules
66
+ 1. Always import from `flysoft-react-ui` (top-level only). Never from internal paths.
67
+ 2. Prefer existing library components before creating custom UI. Never duplicate Button, Input, Card, Badge, Dialog, etc.
68
+ 3. Use exported TypeScript types for type safety (e.g. ButtonProps, DataTableColumn<T>).
69
+ 4. Keep style import at app root only: `import "flysoft-react-ui/styles";`
70
+ 5. Wrap app with `ThemeProvider` (or `AppLayoutProvider` for full layout).
71
+ 6. Use FontAwesome 5 icon classes (`fa-*`). Components auto-normalize to light style (fal). Never use other icon libraries.
72
+ 7. Use theme CSS variables for custom styling: `var(--color-primary)`, `var(--color-bg-default)`, etc.
73
+
74
+ ## Available Components
75
+
76
+ ### Form Controls
77
+ - `Button` — variant: "primary"|"outline"|"ghost", size: "sm"|"md"|"lg", color: "primary"|"secondary"|"success"|"warning"|"danger"|"info", icon, loading, bg, textColor
78
+ - `LinkButton` — Same as Button but renders as link. Props: to (route/URL), target, variant, size, color, icon
79
+ - `Input` — label, error, icon, iconPosition, size, onIconClick, readOnly. Extends HTML input attributes
80
+ - `AutocompleteInput<T,K>`Searchable dropdown. Props: options, value, onChange, multiple, getOptionLabel, getOptionValue, renderOption, noResultsText
81
+ - `SearchSelectInput<T,K>` — Dialog-based async search. Props: onSearchPromiseFn, onSingleSearchPromiseFn, dialogTitle, getOptionLabel, getOptionValue
82
+ - `DatePicker` Standalone calendar. Props: value (Dayjs), onChange, startWeekOn
83
+ - `DateInput` — Input with DatePicker dropdown. Props: value (Dayjs|string), onChange, format ("dd/mm/yyyy"|"mm/dd/yyyy")
84
+ - `Checkbox` — Props: label, labelPosition, error, size, readOnly
85
+ - `RadioButtonGroup` — Props: options ({label,value,disabled}[]), value, onChange, direction ("vertical"|"horizontal"), gap, size
86
+ - `CurrencyInput` — Numeric input formatted as currency (es-AR: 1.234,56). Props: value (number), onChange
87
+ - `Pagination` — URL-based pagination. Props: page, pages, total, isLoading, fieldName
88
+
89
+ ### Layout & Data
90
+ - `Card` — Props: title, subtitle, headerActions, footer, variant ("default"|"elevated"|"outlined"), compact (override local de densidad → fuerza preset compact en `--flysoft-density-*` dentro de la card y descendientes), alwaysDisplayHeaderActions
91
+ - `AppLayout` — Main layout. Props: navbar (NavbarInterface), leftDrawer (LeftDrawerInterface), children
92
+ - `Collection` — Flex container density-aware. Props: gap ("tight"|"sm"|"md"|"lg"|string), direction, wrap, density ("comfortable"|"compact"|"dense", override local que redefine `--flysoft-density-*` para esta collection y descendientes)
93
+ - `DataField` — Label+value pair density-aware. Props: label, value, inline, align, link, size ("sm"|"md", "sm" baja un nivel de tipografía), gap ("tight"|"sm"|"md", separación label/value en stack), hideColon (oculta `:` en modo inline)
94
+ - `TabsGroup` + `TabPanel` Tabbed interface. TabsGroup: tabs ({id,label}[]), paramName (URL sync). TabPanel: tabId
95
+ - `DataTable<T>`Data table density-aware. Props: columns (DataTableColumn<T>[]), rows, maxRows, isLoading, loadingRows, compact (override local de densidad → fuerza preset compact en `--flysoft-density-*` dentro de la tabla y sus DropdownMenu de acciones), locale
96
+ - DataTableColumn: header, value (key or function), type ("text"|"numeric"|"currency"|"date"), actions, width, align, tooltip, footer
97
+ - `Accordion` — Collapsible section. Props: title, icon, rightNode, defaultOpen, variant, onToggle
98
+ - `Menu<T>`Simple menu list. Props: options, onOptionSelected, getOptionLabel, renderOption
99
+ - `DropdownMenu<T>`Portal dropdown. Props: options, onOptionSelected, renderNode, openOnHover, replaceOnSingleOption
100
+ - `DropdownPanel` — Portal dropdown with arbitrary content. Props: children, renderNode, openOnHover
101
+ - `Filter` — Versatile filter by type. filterType: "text"|"number"|"date"|"autocomplete"|"search"|"searchSelect". Props: paramName (URL sync), label, value, onChange
102
+
103
+ ### Utility & Feedback
104
+ - `Badge` — Props: variant, size, rounded, icon, iconPosition, bg, textColor, onClick
105
+ - `Avatar` — Props: text (for initials), image, bgColor, textColor, size
106
+ - `RoadMap` — Stage visualization. Props: stages ({name, description?, icon?, variant?, bg?, disabled?}[])
107
+ - `Dialog` — Modal density-aware. Props: isOpen, title, children, footer, onClose, closeOnOverlayClick, compact (override local de densidad), bodyWidth
108
+ - `Loader`Loading indicator. Props: isLoading, text, keepContentWhileLoading, contentLoadingNode, overlayClassName
109
+ - `FiltersDialog` — Groups multiple filters in dialog. Props: filters (FilterConfig[])
110
+ - `Snackbar` (Used internally) Toast notification
111
+ - `SnackbarContainer` — Place at root. Props: position, maxSnackbars
112
+ - `Skeleton` — Pulse placeholder. Props: className
113
+ - `ThemeSwitcher` — No props. Self-contained theme toggle
114
+
115
+ ### Templates
116
+ - `LoginForm` — Props: onSubmit, loading, error, className
117
+ - `RegistrationForm` Props: onSubmit, loading, error, className
118
+ - `ContactForm` — Props: onSubmit, loading, success, error, className
119
+ - `DashboardLayout` — Props: title, subtitle, stats (DashboardStat[]), actions, children
120
+ - `SidebarLayout` — Props: title, menuItems (MenuItem[]), user (User), onLogout, children
121
+ - `FormPattern` — Dynamic form builder. Props: title, fields (FormField[]), onSubmit, gridCols, submitText, submitIcon, loading, error, success
122
+ - `ListPattern<T>`List page with Card + search + filters + pagination + DataTable. Props: title, columns, rows, searchParamName, onAdd, addButtonText, filtersNode, page, pages, total, isLoading, compact
123
+
124
+ ### Contexts & Hooks
125
+ - `ThemeProvider` — initialTheme, storageKey, forceInitialTheme, onThemeChange, density ("comfortable" | "compact" | "dense"), densityStorageKey, forceInitialDensity, onDensityChange
126
+ - `useTheme()` — Returns: theme, setTheme, updateTheme, currentThemeName, availableThemes, isDark, resetToDefault, density, setDensity
127
+ - `AuthProvider` — getToken, getUserData, refreshToken, removeToken
128
+ - `AuthContext` — user, login, logout, isAuthenticated, isLoading
129
+ - `CrudProvider<T>` — getPromise, getItemPromise, postPromise, putPromise, deletePromise, urlParams, limit, pageParam
130
+ - `useCrud<T>()` — Returns: list, item, isLoading, pagination, fetchItems, fetchItem, createItem, updateItem, deleteItem, params, page, pages, total
131
+ - `SnackbarProvider` + `useSnackbar()` showSnackbar(message, variant?, options?), removeSnackbar(id)
132
+ - `AppLayoutProvider` — Combines Theme + Snackbar + AppLayout. Accepts density/densityStorageKey/forceInitialDensity/onDensityChange (propagated to ThemeProvider) in addition to theme props. useAppLayout() to set navbar/drawer dynamically
133
+ - `useAsyncRequest(options)` Returns: execute(fn), isLoading. Options: successMessage, errorMessage, onSuccess, onError
134
+ - `useBreakpoint()` Returns: breakpoint, windowSize, isMobile, isTablet, isDesktop
135
+ - `useThemeOverride(options)` Returns: applyOverride, revertOverride, revertAllOverrides
136
+ - `useElementScroll(ref)` — Returns: scrollY, scrollDirection
137
+ - `useEnum(enum)` — Returns: getArray(), getInstance(id)
138
+
139
+ ### Services
140
+ - `apiClient`HTTP client. Methods: get<T>, post<T>, put<T>, del<T>, getFile, downloadFile, uploadFile, openFile
141
+ - `setApiClientTokenProvider(fn)`Set auth token globally
142
+ - `createApiClient(config)` — Create isolated API client instance
143
+
144
+ ### Helpers
145
+ - `currencyFormat(n)` "1.234,56"
146
+ - `getErrorMessage(error)` → user-friendly error string
147
+ - `getInitialLetters(text)` → "JP" from "Juan Pérez"
148
+ - `objectToQueryString(obj)` "a=1&b=2"
149
+ - `queryStringToObject(str)` {a: "1", b: "2"}
150
+ - `promiseMapper(promise, mapperFn)` — Maps paginated/array/single results
151
+ - `RegularExpressions` — .email, .dateString, .password(config)
152
+
153
+ ### Interfaces
154
+ - `NameValueInterface<T>` — { name: string; value: T; extras?: any }
155
+ - `PaginationInterface<T>` { list: T[]; limit: number; page: number; pages: number; total: number }
156
+
157
+ ## Common Patterns
158
+
159
+ ### CRUD List Page
160
+ ​```tsx
161
+ <CrudProvider<User>
162
+ getPromise={(params) => apiClient.get({ url: "/api/users", params })}
163
+ deletePromise={{ execute: (u) => apiClient.del({ url: `/api/users/${u.id}` }), successMessage: "Eliminado" }}
164
+ urlParams={["nombre"]}
165
+ >
166
+ <UserList />
167
+ </CrudProvider>
168
+
169
+ function UserList() {
170
+ const { list, isLoading, pagination, deleteItem } = useCrud<User>();
171
+ const columns: DataTableColumn<User>[] = [
172
+ { header: "Nombre", value: "name" },
173
+ { header: "Email", value: "email" },
174
+ { actions: (row) => [
175
+ <Button key="del" variant="ghost" size="sm" icon="fa-trash" color="danger"
176
+ loading={deleteItem.isLoading} onClick={() => deleteItem.execute(row)}>
177
+ Eliminar
178
+ </Button>
179
+ ]}
180
+ ];
181
+ return (
182
+ <Card title="Usuarios">
183
+ <Filter filterType="text" paramName="nombre" label="Nombre" />
184
+ <DataTable columns={columns} rows={list ?? []} isLoading={isLoading} />
185
+ {pagination}
186
+ </Card>
187
+ );
188
+ }
189
+ ​```
190
+
191
+ ### Form with Async Save
192
+ ​```tsx
193
+ const { execute, isLoading } = useAsyncRequest({
194
+ successMessage: "Guardado exitosamente",
195
+ errorMessage: (err) => getErrorMessage(err),
196
+ });
197
+
198
+ <Card title="Nuevo Usuario">
199
+ <Input label="Nombre" value={name} onChange={(e) => setName(e.target.value)} icon="fa-user" />
200
+ <Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} icon="fa-envelope" />
201
+ <Button variant="primary" icon="fa-save" loading={isLoading}
202
+ onClick={() => execute(() => apiClient.post({ url: "/api/users", body: { name, email } }))}>
203
+ Guardar
204
+ </Button>
205
+ </Card>
206
+ ​```
207
+
208
+ ### Dialog Confirmation
209
+ ​```tsx
210
+ <Dialog isOpen={showConfirm} title="Confirmar eliminación" onClose={() => setShowConfirm(false)}
211
+ footer={<>
212
+ <Button variant="ghost" onClick={() => setShowConfirm(false)}>Cancelar</Button>
213
+ <Button variant="primary" color="danger" icon="fa-trash" onClick={handleDelete}>Eliminar</Button>
214
+ </>}
215
+ >
216
+ <p>¿Está seguro?</p>
217
+ </Dialog>
218
+ ​```
219
+
220
+ ### List Page (without CrudContext)
221
+ Full implementation of Card + search + filters + pagination + DataTable. Uses URL query params for filters and pagination.
222
+ ​```tsx
223
+ import { useState, useEffect } from "react";
224
+ import { useSearchParams } from "react-router-dom";
225
+ import {
226
+ Card, Button, Collection, Filter, Pagination, DataTable, Dialog,
227
+ } from "flysoft-react-ui";
228
+ import type { DataTableColumn } from "flysoft-react-ui";
229
+
230
+ interface User { id: number; name: string; email: string; role: string; }
231
+
232
+ function UserListPage() {
233
+ const [searchParams] = useSearchParams();
234
+ const [users, setUsers] = useState<User[]>([]);
235
+ const [isLoading, setIsLoading] = useState(false);
236
+ const [page, setPage] = useState(1);
237
+ const [pages, setPages] = useState(1);
238
+ const [total, setTotal] = useState(0);
239
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
240
+ const [selectedUser, setSelectedUser] = useState<User>();
241
+
242
+ // Read filters from URL
243
+ const search = searchParams.get("buscar") || "";
244
+ const role = searchParams.get("rol") || "";
245
+ const currentPage = Number(searchParams.get("pagina") || "1");
246
+
247
+ useEffect(() => {
248
+ setIsLoading(true);
249
+ fetchUsers({ search, role, page: currentPage }).then((res) => {
250
+ setUsers(res.list);
251
+ setPage(res.page);
252
+ setPages(res.pages);
253
+ setTotal(res.total);
254
+ setIsLoading(false);
255
+ });
256
+ }, [search, role, currentPage]);
257
+
258
+ const columns: DataTableColumn<User>[] = [
259
+ { header: "Nombre", value: (row) => row.name },
260
+ { header: "Email", value: (row) => row.email },
261
+ { header: "Rol", value: (row) => row.role },
262
+ {
263
+ align: "center",
264
+ actions: (row) => [
265
+ <Button key="edit" size="sm" variant="ghost" icon="fa-edit"
266
+ onClick={() => handleEdit(row)}>Editar</Button>,
267
+ <Button key="del" size="sm" variant="ghost" icon="fa-trash"
268
+ onClick={() => { setSelectedUser(row); setShowDeleteDialog(true); }}>
269
+ Eliminar
270
+ </Button>,
271
+ ],
272
+ },
273
+ ];
274
+
275
+ return (
276
+ <>
277
+ <Card
278
+ title="Usuarios"
279
+ alwaysDisplayHeaderActions
280
+ headerActions={
281
+ <Button icon="fa-plus" onClick={() => handleAdd()}>
282
+ Nuevo Usuario
283
+ </Button>
284
+ }
285
+ >
286
+ <div className="flex justify-between items-center my-2">
287
+ <Collection direction="row" wrap>
288
+ <Filter paramName="buscar" label="Buscar" filterType="search" />
289
+ <Filter paramName="rol" label="Rol" filterType="autocomplete"
290
+ options={[
291
+ { label: "Admin", value: "admin" },
292
+ { label: "Editor", value: "editor" },
293
+ ]}
294
+ />
295
+ </Collection>
296
+ <Collection direction="row" wrap>
297
+ <Pagination page={page} pages={pages} total={total}
298
+ fieldName="pagina" isLoading={isLoading} />
299
+ </Collection>
300
+ </div>
301
+ <DataTable columns={columns} rows={users}
302
+ isLoading={isLoading} loadingRows={10} maxRows={10} />
303
+ </Card>
304
+
305
+ <Dialog isOpen={showDeleteDialog} title="Eliminar usuario"
306
+ onClose={() => setShowDeleteDialog(false)}
307
+ footer={<>
308
+ <Button variant="outline" onClick={() => setShowDeleteDialog(false)}>Cancelar</Button>
309
+ <Button variant="primary" color="danger" icon="fa-trash"
310
+ onClick={() => handleDelete(selectedUser)}>Eliminar</Button>
311
+ </>}
312
+ >
313
+ <p>¿Está seguro de eliminar a {selectedUser?.name}?</p>
314
+ </Dialog>
315
+ </>
316
+ );
317
+ }
318
+ ​```
319
+
320
+ ### List Page (using ListPattern template)
321
+ Same pattern as above but using the `ListPattern` template component for less boilerplate:
322
+ ​```tsx
323
+ import { ListPattern, Filter } from "flysoft-react-ui";
324
+ import type { DataTableColumn } from "flysoft-react-ui";
325
+
326
+ const columns: DataTableColumn<User>[] = [
327
+ { header: "Nombre", value: (row) => row.name },
328
+ { header: "Email", value: (row) => row.email },
329
+ { actions: (row) => [
330
+ <Button key="edit" size="sm" variant="ghost" icon="fa-edit" onClick={() => edit(row)}>Editar</Button>,
331
+ ]},
332
+ ];
333
+
334
+ <ListPattern<User>
335
+ title="Usuarios"
336
+ columns={columns}
337
+ rows={users}
338
+ searchParamName="buscar"
339
+ addButtonText="Nuevo"
340
+ onAdd={() => setShowForm(true)}
341
+ filtersNode={
342
+ <Filter paramName="rol" label="Rol" filterType="autocomplete"
343
+ options={[{ label: "Admin", value: "admin" }]} />
344
+ }
345
+ page={page} pages={pages} total={total}
346
+ isLoading={isLoading}
347
+ />
348
+ ​```
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 4) Notes for library maintainers
354
+
355
+ - `src/docs/**` is local demo/dev-only and is not part of public API.
356
+ - Public API must be exported from `src/index.ts`.
357
+ - See `AI_CONTEXT.md` for complete prop interfaces and detailed documentation.