flysoft-react-ui 1.3.2 → 1.4.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.
@@ -1,359 +1,359 @@
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
- 8. To change a form-control's background (e.g. inside a same-color Card), pass a `bg-*` via `className` — form-controls merge classes with `twMerge` so it overrides the default. Ex: `<Input className="bg-[var(--color-bg-secondary)]" />`. For `Filter`, use its `bgColor` prop instead.
74
-
75
- ## Available Components
76
-
77
- ### Form Controls
78
- - `Button` — variant: "primary"|"outline"|"ghost", size: "sm"|"md"|"lg", color: "primary"|"secondary"|"success"|"warning"|"danger"|"info", icon, loading, bg, textColor
79
- - `LinkButton` — Same as Button but renders as link. Props: to (route/URL), target, variant, size, color, icon
80
- - `Input` — label, error, icon, iconPosition, size, onIconClick, readOnly. Extends HTML input attributes
81
- - `AutocompleteInput<T,K>` — Searchable dropdown. Props: options, value, onChange, multiple, getOptionLabel, getOptionValue, renderOption, noResultsText
82
- - `SearchSelectInput<T,K>` — Dialog-based async search. Props: onSearchPromiseFn, onSingleSearchPromiseFn, dialogTitle, getOptionLabel, getOptionValue
83
- - `DatePicker` — Standalone calendar. Props: value (Dayjs), onChange, startWeekOn
84
- - `DateInput` — Input with DatePicker dropdown. Props: value (Dayjs|string), onChange, format ("dd/mm/yyyy"|"mm/dd/yyyy")
85
- - `Checkbox` — Props: label, labelPosition, error, size, readOnly
86
- - `RadioButtonGroup` — Props: options ({label,value,disabled}[]), value, onChange, direction ("vertical"|"horizontal"), gap, size
87
- - `CurrencyInput` — Numeric input formatted as currency (es-AR: 1.234,56). Props: value (number), onChange
88
- - `Pagination` — URL-based pagination. Props: page, pages, total, isLoading, fieldName
89
-
90
- ### Layout & Data
91
- - `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
92
- - `AppLayout` — Main layout. Props: navbar (NavbarInterface), leftDrawer (LeftDrawerInterface), children, isLeftDrawerOpen / onLeftDrawerOpenChange (controlled mobile drawer). Anything inside can close the drawer with useLeftDrawer()
93
- - `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)
94
- - `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)
95
- - `TabsGroup` + `TabPanel` — Tabbed interface. TabsGroup: tabs ({id,label}[]), paramName (URL sync). TabPanel: tabId
96
- - `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
97
- - DataTableColumn: header, value (key or function), type ("text"|"numeric"|"currency"|"date"), actions, width, align, tooltip, footer
98
- - `Accordion` — Collapsible section. Props: title, icon, rightNode, defaultOpen, variant, headerClassName, contentClassName, onToggle
99
- - `Menu<T>` — Simple menu list. Props: options, onOptionSelected, getOptionLabel, renderOption
100
- - `DropdownMenu<T>` — Portal dropdown. Props: options, onOptionSelected, renderNode, openOnHover, replaceOnSingleOption
101
- - `DropdownPanel` — Portal dropdown with arbitrary content. Props: children, renderNode, openOnHover
102
- - `Filter` — Versatile filter by type. filterType: "text"|"number"|"date"|"autocomplete"|"search"|"searchSelect". Props: paramName (URL sync), label, value, onChange
103
-
104
- ### Utility & Feedback
105
- - `Badge` — Props: variant, size, rounded, icon, iconPosition, bg, textColor, onClick
106
- - `Avatar` — Props: text (for initials), image, bgColor, textColor, size
107
- - `RoadMap` — Stage visualization. Props: stages ({name, description?, icon?, variant?, bg?, disabled?}[])
108
- - `Dialog` — Modal density-aware. Props: isOpen, title, children, footer, onClose, closeOnOverlayClick, compact (override local de densidad), bodyWidth
109
- - `Loader` — Loading indicator. Props: isLoading, text, keepContentWhileLoading, contentLoadingNode, overlayClassName
110
- - `FiltersDialog` — Groups multiple filters in dialog. Props: filters (FilterConfig[])
111
- - `Snackbar` — (Used internally) Toast notification
112
- - `SnackbarContainer` — Place at root. Props: position, maxSnackbars
113
- - `Skeleton` — Pulse placeholder. Props: className
114
- - `ThemeSwitcher` — No props. Self-contained theme toggle
115
-
116
- ### Templates
117
- - `LoginForm` — Props: onSubmit, loading, error, className
118
- - `RegistrationForm` — Props: onSubmit, loading, error, className
119
- - `ContactForm` — Props: onSubmit, loading, success, error, className
120
- - `DashboardLayout` — Props: title, subtitle, stats (DashboardStat[]), actions, children
121
- - `SidebarLayout` — Props: title, menuItems (MenuItem[]), user (User), onLogout, children
122
- - `FormPattern` — Dynamic form builder. Props: title, fields (FormField[]), onSubmit, gridCols, submitText, submitIcon, loading, error, success
123
- - `ListPattern<T>` — List page with Card + search + filters + pagination + DataTable. Props: title, columns, rows, searchParamName, onAdd, addButtonText, filtersNode, page, pages, total, isLoading, compact
124
-
125
- ### Contexts & Hooks
126
- - `ThemeProvider` — initialTheme, storageKey, forceInitialTheme, onThemeChange, density ("comfortable" | "compact" | "dense"), densityStorageKey, forceInitialDensity, onDensityChange
127
- - `useTheme()` — Returns: theme, setTheme, updateTheme, currentThemeName, availableThemes, isDark, resetToDefault, density, setDensity
128
- - `AuthProvider` — getToken, getUserData, refreshToken, removeToken
129
- - `AuthContext` — user, login, logout, isAuthenticated, isLoading
130
- - `CrudProvider<T>` — getPromise, getItemPromise, postPromise, putPromise, deletePromise, urlParams, limit, pageParam
131
- - `useCrud<T>()` — Returns: list, item, isLoading, pagination, fetchItems, fetchItem, createItem, updateItem, deleteItem, params, page, pages, total
132
- - `SnackbarProvider` + `useSnackbar()` — showSnackbar(message, variant?, options?), removeSnackbar(id)
133
- - `AppLayoutProvider` — Combines Theme + Snackbar + AppLayout. Accepts density/densityStorageKey/forceInitialDensity/onDensityChange (propagated to ThemeProvider) in addition to theme props. useAppLayout() to set navbar/drawer dynamically and to open/close the left drawer (isLeftDrawerOpen, openLeftDrawer, closeLeftDrawer, toggleLeftDrawer)
134
- - `useLeftDrawer()` — Returns: isLeftDrawerOpen, isLeftDrawerCollapsible, openLeftDrawer, closeLeftDrawer, toggleLeftDrawer. Available to anything rendered inside AppLayout; use it to close the mobile drawer from a menu link's onClick. `useOptionalLeftDrawer()` returns undefined instead of throwing outside AppLayout
135
- - `useAsyncRequest(options)` — Returns: execute(fn), isLoading. Options: successMessage, errorMessage, onSuccess, onError
136
- - `useBreakpoint()` — Returns: breakpoint, windowSize, isMobile, isTablet, isDesktop
137
- - `useThemeOverride(options)` — Returns: applyOverride, revertOverride, revertAllOverrides
138
- - `useElementScroll(ref)` — Returns: scrollY, scrollDirection
139
- - `useEnum(enum)` — Returns: getArray(), getInstance(id)
140
-
141
- ### Services
142
- - `apiClient` — HTTP client. Methods: get<T>, post<T>, put<T>, del<T>, getFile, downloadFile, uploadFile, openFile
143
- - `setApiClientTokenProvider(fn)` — Set auth token globally
144
- - `createApiClient(config)` — Create isolated API client instance
145
-
146
- ### Helpers
147
- - `currencyFormat(n)` → "1.234,56"
148
- - `getErrorMessage(error)` → user-friendly error string
149
- - `getInitialLetters(text)` → "JP" from "Juan Pérez"
150
- - `objectToQueryString(obj)` → "a=1&b=2"
151
- - `queryStringToObject(str)` → {a: "1", b: "2"}
152
- - `promiseMapper(promise, mapperFn)` — Maps paginated/array/single results
153
- - `RegularExpressions` — .email, .dateString, .password(config)
154
-
155
- ### Interfaces
156
- - `NameValueInterface<T>` — { name: string; value: T; extras?: any }
157
- - `PaginationInterface<T>` — { list: T[]; limit: number; page: number; pages: number; total: number }
158
-
159
- ## Common Patterns
160
-
161
- ### CRUD List Page
162
- ​```tsx
163
- <CrudProvider<User>
164
- getPromise={(params) => apiClient.get({ url: "/api/users", params })}
165
- deletePromise={{ execute: (u) => apiClient.del({ url: `/api/users/${u.id}` }), successMessage: "Eliminado" }}
166
- urlParams={["nombre"]}
167
- >
168
- <UserList />
169
- </CrudProvider>
170
-
171
- function UserList() {
172
- const { list, isLoading, pagination, deleteItem } = useCrud<User>();
173
- const columns: DataTableColumn<User>[] = [
174
- { header: "Nombre", value: "name" },
175
- { header: "Email", value: "email" },
176
- { actions: (row) => [
177
- <Button key="del" variant="ghost" size="sm" icon="fa-trash" color="danger"
178
- loading={deleteItem.isLoading} onClick={() => deleteItem.execute(row)}>
179
- Eliminar
180
- </Button>
181
- ]}
182
- ];
183
- return (
184
- <Card title="Usuarios">
185
- <Filter filterType="text" paramName="nombre" label="Nombre" />
186
- <DataTable columns={columns} rows={list ?? []} isLoading={isLoading} />
187
- {pagination}
188
- </Card>
189
- );
190
- }
191
- ​```
192
-
193
- ### Form with Async Save
194
- ​```tsx
195
- const { execute, isLoading } = useAsyncRequest({
196
- successMessage: "Guardado exitosamente",
197
- errorMessage: (err) => getErrorMessage(err),
198
- });
199
-
200
- <Card title="Nuevo Usuario">
201
- <Input label="Nombre" value={name} onChange={(e) => setName(e.target.value)} icon="fa-user" />
202
- <Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} icon="fa-envelope" />
203
- <Button variant="primary" icon="fa-save" loading={isLoading}
204
- onClick={() => execute(() => apiClient.post({ url: "/api/users", body: { name, email } }))}>
205
- Guardar
206
- </Button>
207
- </Card>
208
- ​```
209
-
210
- ### Dialog Confirmation
211
- ​```tsx
212
- <Dialog isOpen={showConfirm} title="Confirmar eliminación" onClose={() => setShowConfirm(false)}
213
- footer={<>
214
- <Button variant="ghost" onClick={() => setShowConfirm(false)}>Cancelar</Button>
215
- <Button variant="primary" color="danger" icon="fa-trash" onClick={handleDelete}>Eliminar</Button>
216
- </>}
217
- >
218
- <p>¿Está seguro?</p>
219
- </Dialog>
220
- ​```
221
-
222
- ### List Page (without CrudContext)
223
- Full implementation of Card + search + filters + pagination + DataTable. Uses URL query params for filters and pagination.
224
- ​```tsx
225
- import { useState, useEffect } from "react";
226
- import { useSearchParams } from "react-router-dom";
227
- import {
228
- Card, Button, Collection, Filter, Pagination, DataTable, Dialog,
229
- } from "flysoft-react-ui";
230
- import type { DataTableColumn } from "flysoft-react-ui";
231
-
232
- interface User { id: number; name: string; email: string; role: string; }
233
-
234
- function UserListPage() {
235
- const [searchParams] = useSearchParams();
236
- const [users, setUsers] = useState<User[]>([]);
237
- const [isLoading, setIsLoading] = useState(false);
238
- const [page, setPage] = useState(1);
239
- const [pages, setPages] = useState(1);
240
- const [total, setTotal] = useState(0);
241
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
242
- const [selectedUser, setSelectedUser] = useState<User>();
243
-
244
- // Read filters from URL
245
- const search = searchParams.get("buscar") || "";
246
- const role = searchParams.get("rol") || "";
247
- const currentPage = Number(searchParams.get("pagina") || "1");
248
-
249
- useEffect(() => {
250
- setIsLoading(true);
251
- fetchUsers({ search, role, page: currentPage }).then((res) => {
252
- setUsers(res.list);
253
- setPage(res.page);
254
- setPages(res.pages);
255
- setTotal(res.total);
256
- setIsLoading(false);
257
- });
258
- }, [search, role, currentPage]);
259
-
260
- const columns: DataTableColumn<User>[] = [
261
- { header: "Nombre", value: (row) => row.name },
262
- { header: "Email", value: (row) => row.email },
263
- { header: "Rol", value: (row) => row.role },
264
- {
265
- align: "center",
266
- actions: (row) => [
267
- <Button key="edit" size="sm" variant="ghost" icon="fa-edit"
268
- onClick={() => handleEdit(row)}>Editar</Button>,
269
- <Button key="del" size="sm" variant="ghost" icon="fa-trash"
270
- onClick={() => { setSelectedUser(row); setShowDeleteDialog(true); }}>
271
- Eliminar
272
- </Button>,
273
- ],
274
- },
275
- ];
276
-
277
- return (
278
- <>
279
- <Card
280
- title="Usuarios"
281
- alwaysDisplayHeaderActions
282
- headerActions={
283
- <Button icon="fa-plus" onClick={() => handleAdd()}>
284
- Nuevo Usuario
285
- </Button>
286
- }
287
- >
288
- <div className="flex justify-between items-center my-2">
289
- <Collection direction="row" wrap>
290
- <Filter paramName="buscar" label="Buscar" filterType="search" />
291
- <Filter paramName="rol" label="Rol" filterType="autocomplete"
292
- options={[
293
- { label: "Admin", value: "admin" },
294
- { label: "Editor", value: "editor" },
295
- ]}
296
- />
297
- </Collection>
298
- <Collection direction="row" wrap>
299
- <Pagination page={page} pages={pages} total={total}
300
- fieldName="pagina" isLoading={isLoading} />
301
- </Collection>
302
- </div>
303
- <DataTable columns={columns} rows={users}
304
- isLoading={isLoading} loadingRows={10} maxRows={10} />
305
- </Card>
306
-
307
- <Dialog isOpen={showDeleteDialog} title="Eliminar usuario"
308
- onClose={() => setShowDeleteDialog(false)}
309
- footer={<>
310
- <Button variant="outline" onClick={() => setShowDeleteDialog(false)}>Cancelar</Button>
311
- <Button variant="primary" color="danger" icon="fa-trash"
312
- onClick={() => handleDelete(selectedUser)}>Eliminar</Button>
313
- </>}
314
- >
315
- <p>¿Está seguro de eliminar a {selectedUser?.name}?</p>
316
- </Dialog>
317
- </>
318
- );
319
- }
320
- ​```
321
-
322
- ### List Page (using ListPattern template)
323
- Same pattern as above but using the `ListPattern` template component for less boilerplate:
324
- ​```tsx
325
- import { ListPattern, Filter } from "flysoft-react-ui";
326
- import type { DataTableColumn } from "flysoft-react-ui";
327
-
328
- const columns: DataTableColumn<User>[] = [
329
- { header: "Nombre", value: (row) => row.name },
330
- { header: "Email", value: (row) => row.email },
331
- { actions: (row) => [
332
- <Button key="edit" size="sm" variant="ghost" icon="fa-edit" onClick={() => edit(row)}>Editar</Button>,
333
- ]},
334
- ];
335
-
336
- <ListPattern<User>
337
- title="Usuarios"
338
- columns={columns}
339
- rows={users}
340
- searchParamName="buscar"
341
- addButtonText="Nuevo"
342
- onAdd={() => setShowForm(true)}
343
- filtersNode={
344
- <Filter paramName="rol" label="Rol" filterType="autocomplete"
345
- options={[{ label: "Admin", value: "admin" }]} />
346
- }
347
- page={page} pages={pages} total={total}
348
- isLoading={isLoading}
349
- />
350
- ​```
351
- ```
352
-
353
- ---
354
-
355
- ## 4) Notes for library maintainers
356
-
357
- - `src/docs/**` is local demo/dev-only and is not part of public API.
358
- - Public API must be exported from `src/index.ts`.
359
- - 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
+ 8. To change a form-control's background (e.g. inside a same-color Card), pass a `bg-*` via `className` — form-controls merge classes with `twMerge` so it overrides the default. Ex: `<Input className="bg-[var(--color-bg-secondary)]" />`. For `Filter`, use its `bgColor` prop instead.
74
+
75
+ ## Available Components
76
+
77
+ ### Form Controls
78
+ - `Button` — variant: "primary"|"outline"|"ghost", size: "sm"|"md"|"lg", color: "primary"|"secondary"|"success"|"warning"|"danger"|"info", icon, loading, bg, textColor
79
+ - `LinkButton` — Same as Button but renders as link. Props: to (route/URL), target, variant, size, color, icon
80
+ - `Input` — label, error, icon, iconPosition, size, onIconClick, readOnly. Extends HTML input attributes
81
+ - `AutocompleteInput<T,K>` — Searchable dropdown. Props: options, value, onChange, multiple, getOptionLabel, getOptionValue, renderOption, noResultsText
82
+ - `SearchSelectInput<T,K>` — Dialog-based async search. Props: onSearchPromiseFn, onSingleSearchPromiseFn, dialogTitle, getOptionLabel, getOptionValue
83
+ - `DatePicker` — Standalone calendar. Props: value (Dayjs), onChange, startWeekOn
84
+ - `DateInput` — Input with DatePicker dropdown. Props: value (Dayjs|string), onChange, format ("dd/mm/yyyy"|"mm/dd/yyyy")
85
+ - `Checkbox` — Props: label, labelPosition, error, size, readOnly
86
+ - `RadioButtonGroup` — Props: options ({label,value,disabled}[]), value, onChange, direction ("vertical"|"horizontal"), gap, size
87
+ - `CurrencyInput` — Numeric input formatted as currency (es-AR: 1.234,56). Props: value (number), onChange
88
+ - `Pagination` — URL-based pagination. Props: page, pages, total, isLoading, fieldName
89
+
90
+ ### Layout & Data
91
+ - `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
92
+ - `AppLayout` — Main layout. Props: navbar (NavbarInterface), leftDrawer (LeftDrawerInterface), children, isLeftDrawerOpen / onLeftDrawerOpenChange (controlled mobile drawer). Anything inside can close the drawer with useLeftDrawer()
93
+ - `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)
94
+ - `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)
95
+ - `TabsGroup` + `TabPanel` — Tabbed interface. TabsGroup: tabs ({id,label}[]), paramName (URL sync). TabPanel: tabId
96
+ - `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
97
+ - DataTableColumn: header, value (key or function), type ("text"|"numeric"|"currency"|"date"), actions, width, align, tooltip, footer
98
+ - `Accordion` — Collapsible section. Props: title, icon, rightNode, defaultOpen, variant, headerClassName, contentClassName, onToggle
99
+ - `Menu<T>` — Simple menu list. Props: options, onOptionSelected, getOptionLabel, renderOption
100
+ - `DropdownMenu<T>` — Portal dropdown. Props: options, onOptionSelected, renderNode, openOnHover, replaceOnSingleOption
101
+ - `DropdownPanel` — Portal dropdown with arbitrary content. Props: children, renderNode, openOnHover
102
+ - `Filter` — Versatile filter by type. filterType: "text"|"number"|"date"|"autocomplete"|"search"|"searchSelect". Props: paramName (URL sync), label, value, onChange
103
+
104
+ ### Utility & Feedback
105
+ - `Badge` — Props: variant, size, rounded, icon, iconPosition, bg, textColor, onClick
106
+ - `Avatar` — Props: text (for initials), image, bgColor, textColor, size
107
+ - `RoadMap` — Stage visualization. Props: stages ({name, description?, icon?, variant?, bg?, disabled?}[])
108
+ - `Dialog` — Modal density-aware. Props: isOpen, title, children, footer, onClose, closeOnOverlayClick, compact (override local de densidad), bodyWidth
109
+ - `Loader` — Loading indicator. Props: isLoading, text, keepContentWhileLoading, contentLoadingNode, overlayClassName
110
+ - `FiltersDialog` — Groups multiple filters in dialog. Props: filters (FilterConfig[])
111
+ - `Snackbar` — (Used internally) Toast notification
112
+ - `SnackbarContainer` — Place at root. Props: position, maxSnackbars
113
+ - `Skeleton` — Pulse placeholder. Props: className
114
+ - `ThemeSwitcher` — No props. Self-contained theme toggle
115
+
116
+ ### Templates
117
+ - `LoginForm` — Props: onSubmit, loading, error, className
118
+ - `RegistrationForm` — Props: onSubmit, loading, error, className
119
+ - `ContactForm` — Props: onSubmit, loading, success, error, className
120
+ - `DashboardLayout` — Props: title, subtitle, stats (DashboardStat[]), actions, children
121
+ - `SidebarLayout` — Props: title, menuItems (MenuItem[]), user (User), onLogout, children
122
+ - `FormPattern` — Dynamic form builder. Props: title, fields (FormField[]), onSubmit, gridCols, submitText, submitIcon, loading, error, success
123
+ - `ListPattern<T>` — List page with Card + search + filters + pagination + DataTable. Props: title, columns, rows, searchParamName, onAdd, addButtonText, filtersNode, page, pages, total, isLoading, compact
124
+
125
+ ### Contexts & Hooks
126
+ - `ThemeProvider` — initialTheme, storageKey, forceInitialTheme, onThemeChange, density ("comfortable" | "compact" | "dense"), densityStorageKey, forceInitialDensity, onDensityChange
127
+ - `useTheme()` — Returns: theme, setTheme, updateTheme, currentThemeName, availableThemes, isDark, resetToDefault, density, setDensity
128
+ - `AuthProvider` — getToken, getUserData, refreshToken, removeToken
129
+ - `AuthContext` — user, login, logout, isAuthenticated, isLoading
130
+ - `CrudProvider<T>` — getPromise, getItemPromise, postPromise, putPromise, deletePromise, urlParams, limit, pageParam
131
+ - `useCrud<T>()` — Returns: list, item, isLoading, pagination, fetchItems, fetchItem, createItem, updateItem, deleteItem, params, page, pages, total
132
+ - `SnackbarProvider` + `useSnackbar()` — showSnackbar(message, variant?, options?), removeSnackbar(id)
133
+ - `AppLayoutProvider` — Combines Theme + Snackbar + AppLayout. Accepts density/densityStorageKey/forceInitialDensity/onDensityChange (propagated to ThemeProvider) in addition to theme props. useAppLayout() to set navbar/drawer dynamically and to open/close the left drawer (isLeftDrawerOpen, openLeftDrawer, closeLeftDrawer, toggleLeftDrawer)
134
+ - `useLeftDrawer()` — Returns: isLeftDrawerOpen, isLeftDrawerCollapsible, openLeftDrawer, closeLeftDrawer, toggleLeftDrawer. Available to anything rendered inside AppLayout; use it to close the mobile drawer from a menu link's onClick. `useOptionalLeftDrawer()` returns undefined instead of throwing outside AppLayout
135
+ - `useAsyncRequest(options)` — Returns: execute(fn), isLoading. Options: successMessage, errorMessage, onSuccess, onError
136
+ - `useBreakpoint()` — Returns: breakpoint, windowSize, isMobile, isTablet, isDesktop
137
+ - `useThemeOverride(options)` — Returns: applyOverride, revertOverride, revertAllOverrides
138
+ - `useElementScroll(ref)` — Returns: scrollY, scrollDirection
139
+ - `useEnum(enum)` — Returns: getArray(), getInstance(id)
140
+
141
+ ### Services
142
+ - `apiClient` — HTTP client. Methods: get<T>, post<T>, put<T>, patch<T>, del<T>, getFile, downloadFile, uploadFile, openFile
143
+ - `setApiClientTokenProvider(fn)` — Set auth token globally
144
+ - `createApiClient(config)` — Create isolated API client instance
145
+
146
+ ### Helpers
147
+ - `currencyFormat(n)` → "1.234,56"
148
+ - `getErrorMessage(error)` → user-friendly error string
149
+ - `getInitialLetters(text)` → "JP" from "Juan Pérez"
150
+ - `objectToQueryString(obj)` → "a=1&b=2"
151
+ - `queryStringToObject(str)` → {a: "1", b: "2"}
152
+ - `promiseMapper(promise, mapperFn)` — Maps paginated/array/single results
153
+ - `RegularExpressions` — .email, .dateString, .password(config)
154
+
155
+ ### Interfaces
156
+ - `NameValueInterface<T>` — { name: string; value: T; extras?: any }
157
+ - `PaginationInterface<T>` — { list: T[]; limit: number; page: number; pages: number; total: number }
158
+
159
+ ## Common Patterns
160
+
161
+ ### CRUD List Page
162
+ ​```tsx
163
+ <CrudProvider<User>
164
+ getPromise={(params) => apiClient.get({ url: "/api/users", params })}
165
+ deletePromise={{ execute: (u) => apiClient.del({ url: `/api/users/${u.id}` }), successMessage: "Eliminado" }}
166
+ urlParams={["nombre"]}
167
+ >
168
+ <UserList />
169
+ </CrudProvider>
170
+
171
+ function UserList() {
172
+ const { list, isLoading, pagination, deleteItem } = useCrud<User>();
173
+ const columns: DataTableColumn<User>[] = [
174
+ { header: "Nombre", value: "name" },
175
+ { header: "Email", value: "email" },
176
+ { actions: (row) => [
177
+ <Button key="del" variant="ghost" size="sm" icon="fa-trash" color="danger"
178
+ loading={deleteItem.isLoading} onClick={() => deleteItem.execute(row)}>
179
+ Eliminar
180
+ </Button>
181
+ ]}
182
+ ];
183
+ return (
184
+ <Card title="Usuarios">
185
+ <Filter filterType="text" paramName="nombre" label="Nombre" />
186
+ <DataTable columns={columns} rows={list ?? []} isLoading={isLoading} />
187
+ {pagination}
188
+ </Card>
189
+ );
190
+ }
191
+ ​```
192
+
193
+ ### Form with Async Save
194
+ ​```tsx
195
+ const { execute, isLoading } = useAsyncRequest({
196
+ successMessage: "Guardado exitosamente",
197
+ errorMessage: (err) => getErrorMessage(err),
198
+ });
199
+
200
+ <Card title="Nuevo Usuario">
201
+ <Input label="Nombre" value={name} onChange={(e) => setName(e.target.value)} icon="fa-user" />
202
+ <Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} icon="fa-envelope" />
203
+ <Button variant="primary" icon="fa-save" loading={isLoading}
204
+ onClick={() => execute(() => apiClient.post({ url: "/api/users", body: { name, email } }))}>
205
+ Guardar
206
+ </Button>
207
+ </Card>
208
+ ​```
209
+
210
+ ### Dialog Confirmation
211
+ ​```tsx
212
+ <Dialog isOpen={showConfirm} title="Confirmar eliminación" onClose={() => setShowConfirm(false)}
213
+ footer={<>
214
+ <Button variant="ghost" onClick={() => setShowConfirm(false)}>Cancelar</Button>
215
+ <Button variant="primary" color="danger" icon="fa-trash" onClick={handleDelete}>Eliminar</Button>
216
+ </>}
217
+ >
218
+ <p>¿Está seguro?</p>
219
+ </Dialog>
220
+ ​```
221
+
222
+ ### List Page (without CrudContext)
223
+ Full implementation of Card + search + filters + pagination + DataTable. Uses URL query params for filters and pagination.
224
+ ​```tsx
225
+ import { useState, useEffect } from "react";
226
+ import { useSearchParams } from "react-router-dom";
227
+ import {
228
+ Card, Button, Collection, Filter, Pagination, DataTable, Dialog,
229
+ } from "flysoft-react-ui";
230
+ import type { DataTableColumn } from "flysoft-react-ui";
231
+
232
+ interface User { id: number; name: string; email: string; role: string; }
233
+
234
+ function UserListPage() {
235
+ const [searchParams] = useSearchParams();
236
+ const [users, setUsers] = useState<User[]>([]);
237
+ const [isLoading, setIsLoading] = useState(false);
238
+ const [page, setPage] = useState(1);
239
+ const [pages, setPages] = useState(1);
240
+ const [total, setTotal] = useState(0);
241
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
242
+ const [selectedUser, setSelectedUser] = useState<User>();
243
+
244
+ // Read filters from URL
245
+ const search = searchParams.get("buscar") || "";
246
+ const role = searchParams.get("rol") || "";
247
+ const currentPage = Number(searchParams.get("pagina") || "1");
248
+
249
+ useEffect(() => {
250
+ setIsLoading(true);
251
+ fetchUsers({ search, role, page: currentPage }).then((res) => {
252
+ setUsers(res.list);
253
+ setPage(res.page);
254
+ setPages(res.pages);
255
+ setTotal(res.total);
256
+ setIsLoading(false);
257
+ });
258
+ }, [search, role, currentPage]);
259
+
260
+ const columns: DataTableColumn<User>[] = [
261
+ { header: "Nombre", value: (row) => row.name },
262
+ { header: "Email", value: (row) => row.email },
263
+ { header: "Rol", value: (row) => row.role },
264
+ {
265
+ align: "center",
266
+ actions: (row) => [
267
+ <Button key="edit" size="sm" variant="ghost" icon="fa-edit"
268
+ onClick={() => handleEdit(row)}>Editar</Button>,
269
+ <Button key="del" size="sm" variant="ghost" icon="fa-trash"
270
+ onClick={() => { setSelectedUser(row); setShowDeleteDialog(true); }}>
271
+ Eliminar
272
+ </Button>,
273
+ ],
274
+ },
275
+ ];
276
+
277
+ return (
278
+ <>
279
+ <Card
280
+ title="Usuarios"
281
+ alwaysDisplayHeaderActions
282
+ headerActions={
283
+ <Button icon="fa-plus" onClick={() => handleAdd()}>
284
+ Nuevo Usuario
285
+ </Button>
286
+ }
287
+ >
288
+ <div className="flex justify-between items-center my-2">
289
+ <Collection direction="row" wrap>
290
+ <Filter paramName="buscar" label="Buscar" filterType="search" />
291
+ <Filter paramName="rol" label="Rol" filterType="autocomplete"
292
+ options={[
293
+ { label: "Admin", value: "admin" },
294
+ { label: "Editor", value: "editor" },
295
+ ]}
296
+ />
297
+ </Collection>
298
+ <Collection direction="row" wrap>
299
+ <Pagination page={page} pages={pages} total={total}
300
+ fieldName="pagina" isLoading={isLoading} />
301
+ </Collection>
302
+ </div>
303
+ <DataTable columns={columns} rows={users}
304
+ isLoading={isLoading} loadingRows={10} maxRows={10} />
305
+ </Card>
306
+
307
+ <Dialog isOpen={showDeleteDialog} title="Eliminar usuario"
308
+ onClose={() => setShowDeleteDialog(false)}
309
+ footer={<>
310
+ <Button variant="outline" onClick={() => setShowDeleteDialog(false)}>Cancelar</Button>
311
+ <Button variant="primary" color="danger" icon="fa-trash"
312
+ onClick={() => handleDelete(selectedUser)}>Eliminar</Button>
313
+ </>}
314
+ >
315
+ <p>¿Está seguro de eliminar a {selectedUser?.name}?</p>
316
+ </Dialog>
317
+ </>
318
+ );
319
+ }
320
+ ​```
321
+
322
+ ### List Page (using ListPattern template)
323
+ Same pattern as above but using the `ListPattern` template component for less boilerplate:
324
+ ​```tsx
325
+ import { ListPattern, Filter } from "flysoft-react-ui";
326
+ import type { DataTableColumn } from "flysoft-react-ui";
327
+
328
+ const columns: DataTableColumn<User>[] = [
329
+ { header: "Nombre", value: (row) => row.name },
330
+ { header: "Email", value: (row) => row.email },
331
+ { actions: (row) => [
332
+ <Button key="edit" size="sm" variant="ghost" icon="fa-edit" onClick={() => edit(row)}>Editar</Button>,
333
+ ]},
334
+ ];
335
+
336
+ <ListPattern<User>
337
+ title="Usuarios"
338
+ columns={columns}
339
+ rows={users}
340
+ searchParamName="buscar"
341
+ addButtonText="Nuevo"
342
+ onAdd={() => setShowForm(true)}
343
+ filtersNode={
344
+ <Filter paramName="rol" label="Rol" filterType="autocomplete"
345
+ options={[{ label: "Admin", value: "admin" }]} />
346
+ }
347
+ page={page} pages={pages} total={total}
348
+ isLoading={isLoading}
349
+ />
350
+ ​```
351
+ ```
352
+
353
+ ---
354
+
355
+ ## 4) Notes for library maintainers
356
+
357
+ - `src/docs/**` is local demo/dev-only and is not part of public API.
358
+ - Public API must be exported from `src/index.ts`.
359
+ - See `AI_CONTEXT.md` for complete prop interfaces and detailed documentation.