@soyfri/shared-library 2.0.0-beta.2 → 2.0.0-beta.4

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 (187) hide show
  1. package/.dockerignore +8 -0
  2. package/.github/workflows/publish.yml +107 -0
  3. package/.prettierrc +3 -0
  4. package/.storybook/main.ts +19 -0
  5. package/.storybook/preview.ts +14 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/Dockerfile +37 -0
  8. package/build.js +102 -0
  9. package/chromatic.config.json +5 -0
  10. package/cleanDirectories.js +40 -0
  11. package/dist/README.md +243 -0
  12. package/dist/components/Icon/Icon.js +1 -1
  13. package/dist/components/Table/Table.js +1 -1
  14. package/dist/index.cjs +24 -0
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +7 -1
  17. package/dist/mui.d.ts +1 -0
  18. package/dist/package.json +197 -0
  19. package/package.json +4 -32
  20. package/rollup.config.cjs +87 -0
  21. package/src/components/ActionMenu/ActionMenu.stories.tsx +230 -0
  22. package/src/components/ActionMenu/ActionMenu.tsx +174 -0
  23. package/src/components/ActionMenu/index.ts +2 -0
  24. package/src/components/AppBar/AppBar.stories.tsx +272 -0
  25. package/src/components/AppBar/AppBar.sx.ts +32 -0
  26. package/src/components/AppBar/AppBar.tsx +123 -0
  27. package/src/components/AppBar/AppBarBrand.tsx +120 -0
  28. package/src/components/AppBar/AppBarContext.ts +25 -0
  29. package/src/components/AppBar/AppBarMenuToggle.tsx +90 -0
  30. package/src/components/AppBar/AppBarUserMenu.tsx +217 -0
  31. package/src/components/AppBar/index.ts +25 -0
  32. package/src/components/Autocomplete/Autocomplete.definitions.ts +477 -0
  33. package/src/components/Autocomplete/Autocomplete.helpers.ts +60 -0
  34. package/src/components/Autocomplete/Autocomplete.stories.tsx +748 -0
  35. package/src/components/Autocomplete/Autocomplete.sx.ts +30 -0
  36. package/src/components/Autocomplete/Autocomplete.tsx +361 -0
  37. package/src/components/Autocomplete/Autocomplete.types.ts +13 -0
  38. package/src/components/Autocomplete/_parts/AutocompleteChips.tsx +55 -0
  39. package/src/components/Autocomplete/_parts/AutocompleteLoader.tsx +17 -0
  40. package/src/components/Autocomplete/_parts/AutocompleteOption.tsx +31 -0
  41. package/src/components/Autocomplete/index.ts +12 -0
  42. package/src/components/Avatar/Avatar.definitions.ts +162 -0
  43. package/src/components/Avatar/Avatar.stories.tsx +258 -0
  44. package/src/components/Avatar/Avatar.tsx +206 -0
  45. package/src/components/Avatar/index.ts +1 -0
  46. package/src/components/Button/Button.definition.ts +97 -0
  47. package/src/components/Button/Button.stories.tsx +285 -0
  48. package/src/components/Button/Button.tsx +67 -0
  49. package/src/components/Button/index.ts +1 -0
  50. package/src/components/Card/Card.definition.ts +5 -0
  51. package/src/components/Card/Card.stories.tsx +221 -0
  52. package/src/components/Card/Card.sx.ts +104 -0
  53. package/src/components/Card/Card.tsx +200 -0
  54. package/src/components/Card/index.ts +9 -0
  55. package/src/components/Chip/Chip.definitions.ts +167 -0
  56. package/src/components/Chip/Chip.stories.tsx +265 -0
  57. package/src/components/Chip/Chip.tsx +61 -0
  58. package/src/components/Chip/index.ts +1 -0
  59. package/src/components/Column/Column.tsx +29 -0
  60. package/src/components/Column/index.ts +1 -0
  61. package/src/components/DatePicker/DatePicker.definitions.ts +228 -0
  62. package/src/components/DatePicker/DatePicker.helpers.ts +24 -0
  63. package/src/components/DatePicker/DatePicker.stories.tsx +309 -0
  64. package/src/components/DatePicker/DatePicker.sx.ts +33 -0
  65. package/src/components/DatePicker/DatePicker.tsx +189 -0
  66. package/src/components/DatePicker/DatePicker.types.ts +10 -0
  67. package/src/components/DatePicker/index.ts +9 -0
  68. package/src/components/DateRangePicker/DateRangePicker.definitions.ts +191 -0
  69. package/src/components/DateRangePicker/DateRangePicker.stories.tsx +252 -0
  70. package/src/components/DateRangePicker/DateRangePicker.tsx +56 -0
  71. package/src/components/DateRangePicker/index.ts +1 -0
  72. package/src/components/DateTimePicker/DateTimePicker.definitions.ts +256 -0
  73. package/src/components/DateTimePicker/DateTimePicker.helpers.ts +38 -0
  74. package/src/components/DateTimePicker/DateTimePicker.stories.tsx +418 -0
  75. package/src/components/DateTimePicker/DateTimePicker.sx.ts +30 -0
  76. package/src/components/DateTimePicker/DateTimePicker.tsx +225 -0
  77. package/src/components/DateTimePicker/DateTimePicker.types.ts +10 -0
  78. package/src/components/DateTimePicker/index.ts +9 -0
  79. package/src/components/Drawer/Drawer.stories.tsx +270 -0
  80. package/src/components/Drawer/Drawer.sx.ts +106 -0
  81. package/src/components/Drawer/Drawer.tsx +214 -0
  82. package/src/components/Drawer/DrawerContext.ts +26 -0
  83. package/src/components/Drawer/DrawerItem.tsx +110 -0
  84. package/src/components/Drawer/index.ts +10 -0
  85. package/src/components/Flyout/Flyout.stories.tsx +282 -0
  86. package/src/components/Flyout/Flyout.tsx +122 -0
  87. package/src/components/Flyout/index.ts +1 -0
  88. package/src/components/Gallery/Gallery.definition.tsx +37 -0
  89. package/src/components/Gallery/Gallery.stories.tsx +82 -0
  90. package/src/components/Gallery/Gallery.tsx +118 -0
  91. package/src/components/Gallery/GalleryLightbox.tsx +170 -0
  92. package/src/components/Gallery/GalleryMain.tsx +84 -0
  93. package/src/components/Gallery/GalleryThumbnails.tsx +106 -0
  94. package/src/components/Gallery/index.ts +1 -0
  95. package/src/components/Icon/Icon.stories.tsx +121 -0
  96. package/src/components/Icon/Icon.tsx +175 -0
  97. package/src/components/Icon/index.ts +2 -0
  98. package/src/components/Input/Input.definitions.ts +324 -0
  99. package/src/components/Input/Input.helpers.ts +49 -0
  100. package/src/components/Input/Input.stories.tsx +499 -0
  101. package/src/components/Input/Input.sx.ts +42 -0
  102. package/src/components/Input/Input.tsx +141 -0
  103. package/src/components/Input/Input.types.ts +10 -0
  104. package/src/components/Input/index.ts +9 -0
  105. package/src/components/InputGroup/InputGroup.definitions.ts +158 -0
  106. package/src/components/InputGroup/InputGroup.stories.tsx +267 -0
  107. package/src/components/InputGroup/InputGroup.tsx +179 -0
  108. package/src/components/InputGroup/index.ts +1 -0
  109. package/src/components/MenuButton/MenuButton.stories.tsx +197 -0
  110. package/src/components/MenuButton/MenuButton.tsx +100 -0
  111. package/src/components/MenuButton/index.ts +1 -0
  112. package/src/components/Modal/Modal.stories.tsx +721 -0
  113. package/src/components/Modal/Modal.tsx +355 -0
  114. package/src/components/Modal/ModalBody.tsx +16 -0
  115. package/src/components/Modal/ModalFooter.tsx +71 -0
  116. package/src/components/Modal/ModalHeader.tsx +18 -0
  117. package/src/components/Modal/index.ts +6 -0
  118. package/src/components/PageLoader/PageLoader.stories.tsx +217 -0
  119. package/src/components/PageLoader/PageLoader.tsx +96 -0
  120. package/src/components/PageLoader/index.ts +2 -0
  121. package/src/components/ScrollTopButton/ScrollTopButton.stories.tsx +158 -0
  122. package/src/components/ScrollTopButton/ScrollTopButton.tsx +135 -0
  123. package/src/components/ScrollTopButton/index.ts +8 -0
  124. package/src/components/ScrollTopButton/scrollToTop.ts +37 -0
  125. package/src/components/Select/Select.definitions.ts +602 -0
  126. package/src/components/Select/Select.helpers.ts +71 -0
  127. package/src/components/Select/Select.stories.tsx +687 -0
  128. package/src/components/Select/Select.sx.ts +14 -0
  129. package/src/components/Select/Select.tsx +429 -0
  130. package/src/components/Select/Select.types.ts +15 -0
  131. package/src/components/Select/_parts/SelectMenuItem.tsx +40 -0
  132. package/src/components/Select/_parts/SelectSearchHeader.tsx +51 -0
  133. package/src/components/Select/_parts/SelectValue.tsx +96 -0
  134. package/src/components/Select/index.ts +14 -0
  135. package/src/components/Stat/Stat.stories.tsx +85 -0
  136. package/src/components/Stat/Stat.tsx +117 -0
  137. package/src/components/Stat/index.ts +2 -0
  138. package/src/components/StatusMessage/StatusMessage.stories.tsx +130 -0
  139. package/src/components/StatusMessage/StatusMessage.tsx +162 -0
  140. package/src/components/StatusMessage/index.ts +2 -0
  141. package/src/components/Stepper/Step.tsx +21 -0
  142. package/src/components/Stepper/Stepper.definition.ts +75 -0
  143. package/src/components/Stepper/Stepper.stories.tsx +122 -0
  144. package/src/components/Stepper/Stepper.tsx +75 -0
  145. package/src/components/Stepper/index.ts +2 -0
  146. package/src/components/Table/EmptyTable.png +0 -0
  147. package/src/components/Table/Table.definition.ts +580 -0
  148. package/src/components/Table/Table.stories.tsx +853 -0
  149. package/src/components/Table/Table.tsx +495 -0
  150. package/src/components/Table/data.ts +134 -0
  151. package/src/components/Table/exportsUtils.ts +195 -0
  152. package/src/components/Table/index.ts +3 -0
  153. package/src/components/Table/types.ts +34 -0
  154. package/src/components/Tabs/Tab.definition.ts +53 -0
  155. package/src/components/Tabs/Tab.tsx +19 -0
  156. package/src/components/Tabs/Tabs.stories.tsx +118 -0
  157. package/src/components/Tabs/Tabs.tsx +99 -0
  158. package/src/components/Tabs/_tabUtils.tsx +4 -0
  159. package/src/components/Tabs/index.ts +2 -0
  160. package/src/components/Timeline/Timeline.definition.ts +43 -0
  161. package/src/components/Timeline/Timeline.stories.tsx +108 -0
  162. package/src/components/Timeline/Timeline.tsx +49 -0
  163. package/src/components/Timeline/TimelineItem.tsx +31 -0
  164. package/src/components/Timeline/index.ts +2 -0
  165. package/src/components/Tooltip/Tooltip.stories.tsx +129 -0
  166. package/src/components/Tooltip/Tooltip.tsx +58 -0
  167. package/src/components/Tooltip/index.ts +1 -0
  168. package/src/components/_shared/formField.sx.ts +118 -0
  169. package/src/components/_shared/resolvePreset.ts +35 -0
  170. package/src/hooks/ClipBoard/ClipBoard.stories.tsx +168 -0
  171. package/src/hooks/ClipBoard/ClipBoard.tsx +131 -0
  172. package/src/hooks/ClipBoard/ClipboardUnifiedDemo.tsx +111 -0
  173. package/src/hooks/ClipBoard/index.ts +1 -0
  174. package/src/hooks/Wizard/Wizard.stories.tsx +301 -0
  175. package/src/hooks/Wizard/WizardContext.tsx +166 -0
  176. package/src/hooks/Wizard/index.ts +6 -0
  177. package/src/hooks/Wizard/useWizard.ts +13 -0
  178. package/src/index.ts +17 -0
  179. package/src/mui.ts +54 -0
  180. package/src/styles.css +3 -0
  181. package/src/theme/componentStyles.ts +47 -0
  182. package/src/theme/tokens.ts +43 -0
  183. package/tailwind.config.js +10 -0
  184. package/tsconfig.json +48 -0
  185. package/tsup.config.js +41 -0
  186. package/vite.config.js +132 -0
  187. package/vitest.config.ts +35 -0
@@ -0,0 +1,477 @@
1
+ // =============================================================================
2
+ // SIMPLE
3
+ // =============================================================================
4
+ export const SimpleAutocompleteDefinition = `
5
+ import React, { useState } from 'react';
6
+ import Autocomplete from './Autocomplete';
7
+ import { Box, Typography } from '@mui/material';
8
+
9
+ const options = [
10
+ { value: '10', label: '10' },
11
+ { value: '25', label: '25' },
12
+ ];
13
+
14
+ export const SimpleExample = () => {
15
+ const [value, setValue] = useState(options[1]);
16
+
17
+ return (
18
+ <Box sx={{ width: 250 }}>
19
+ <Autocomplete
20
+ label="Registros por página"
21
+ options={options}
22
+ value={value}
23
+ onChange={(val) => {
24
+ setValue(val as string);
25
+ }}
26
+ />
27
+ <Typography sx={{ mt: 2 }}>
28
+ Valor: {value}
29
+ </Typography>
30
+ </Box>
31
+ );
32
+ };
33
+ `;
34
+
35
+ // =============================================================================
36
+ // PLACEHOLDER
37
+ // =============================================================================
38
+ export const WithPlaceholderAutocompleteDefinition = `
39
+ import React, { useState } from 'react';
40
+ import Autocomplete from './Autocomplete';
41
+ import { Box, Typography } from '@mui/material';
42
+
43
+ export const WithPlaceholderExample = () => {
44
+ const [value, setValue] = useState(null);
45
+
46
+ return (
47
+ <Box sx={{ width: 300 }}>
48
+ <Autocomplete
49
+ label="Seleccione una opción"
50
+ options={[]}
51
+ value={value}
52
+ onChange={(val) => {
53
+ setValue(val as string);
54
+ }}
55
+ placeholder="Ninguna opción seleccionada"
56
+ />
57
+ <Typography sx={{ mt: 2 }}>
58
+ {value || 'Ninguno'}
59
+ </Typography>
60
+ </Box>
61
+ );
62
+ };
63
+ `;
64
+
65
+ // =============================================================================
66
+ // MULTIPLE
67
+ // =============================================================================
68
+ export const MultipleAutocompleteDefinition = `
69
+ import React, { useState } from 'react';
70
+ import Autocomplete from './Autocomplete';
71
+ import { Box } from '@mui/material';
72
+
73
+ const options = [
74
+ { value: 'pending', label: 'Pendiente' },
75
+ { value: 'approved', label: 'Aprobado' },
76
+ ];
77
+
78
+ export const MultipleExample = () => {
79
+ const [value, setValue] = useState([]);
80
+
81
+ return (
82
+ <Box sx={{ width: 400 }}>
83
+ <Autocomplete
84
+ multiple
85
+ options={options}
86
+ value={value}
87
+ onChange={(val) => {
88
+ setValue(val as string[]);
89
+ }}
90
+ />
91
+ </Box>
92
+ );
93
+ };
94
+ `;
95
+
96
+ // =============================================================================
97
+ // MULTIPLE LIMIT
98
+ // =============================================================================
99
+ export const MultipleWithLimitAutocompleteDefinition = `
100
+ import React, { useState } from 'react';
101
+ import Autocomplete from './Autocomplete';
102
+
103
+ export const MultipleWithLimitExample = () => {
104
+ const [value, setValue] = useState([]);
105
+
106
+ return (
107
+ <Autocomplete
108
+ multiple
109
+ maxChipsToShow={2}
110
+ options={[]}
111
+ value={value}
112
+ onChange={(val) => {
113
+ setValue(val as string[]);
114
+ }}
115
+ />
116
+ );
117
+ };
118
+ `;
119
+
120
+ // =============================================================================
121
+ // CUSTOM OPTION
122
+ // =============================================================================
123
+ export const CustomRenderOptionAutocompleteDefinition = `
124
+ import React, { useState } from 'react';
125
+ import Autocomplete from './Autocomplete';
126
+ import { Avatar, Box } from '@mui/material';
127
+
128
+ const users = [
129
+ { value: 'admin', label: 'Administrador', img: '...' },
130
+ ];
131
+
132
+ export const CustomRenderExample = () => {
133
+ const [value, setValue] = useState([]);
134
+
135
+ return (
136
+ <Autocomplete
137
+ multiple
138
+ options={users}
139
+ value={value}
140
+ onChange={(val) => {
141
+ setValue(val as string[]);
142
+ }}
143
+ renderOptionItem={(item) => (
144
+ <Box sx={{ display: 'flex', gap: 1 }}>
145
+ <Avatar src={item.img} />
146
+ {item.label}
147
+ </Box>
148
+ )}
149
+ />
150
+ );
151
+ };
152
+ `;
153
+
154
+ // =============================================================================
155
+ // CUSTOM CHIP
156
+ // =============================================================================
157
+ export const CustomChipRenderAutocompleteDefinition = `
158
+ import React, { useState } from 'react';
159
+ import Autocomplete from './Autocomplete';
160
+ import { Typography } from '@mui/material';
161
+
162
+ export const CustomChipExample = () => {
163
+ const [value, setValue] = useState([]);
164
+
165
+ return (
166
+ <Autocomplete
167
+ multiple
168
+ options={[]}
169
+ value={value}
170
+ onChange={(val) => {
171
+ setValue(val as string[]);
172
+ }}
173
+ renderChipLabel={(item) => (
174
+ <Typography variant="caption">
175
+ {item.label.charAt(0)}
176
+ </Typography>
177
+ )}
178
+ />
179
+ );
180
+ };
181
+ `;
182
+
183
+ // =============================================================================
184
+ // LOADING
185
+ // =============================================================================
186
+ export const LoadingAutocompleteDefinition = `
187
+ import React, { useState } from 'react';
188
+ import Autocomplete from './Autocomplete';
189
+
190
+ export const LoadingExample = () => {
191
+ const [value, setValue] = useState(null);
192
+
193
+ return (
194
+ <Autocomplete
195
+ loading
196
+ options={[]}
197
+ value={value}
198
+ onChange={(val) => {
199
+ setValue(val as string);
200
+ }}
201
+ />
202
+ );
203
+ };
204
+ `;
205
+
206
+ // =============================================================================
207
+ // EMPTY
208
+ // =============================================================================
209
+ export const EmptyOptionsAutocompleteDefinition = `
210
+ import React, { useState } from 'react';
211
+ import Autocomplete from './Autocomplete';
212
+
213
+ export const EmptyExample = () => {
214
+ const [value, setValue] = useState(null);
215
+
216
+ return (
217
+ <Autocomplete
218
+ options={[]}
219
+ value={value}
220
+ onChange={(val) => {
221
+ setValue(val as string);
222
+ }}
223
+ placeholder="No hay opciones"
224
+ />
225
+ );
226
+ };
227
+ `;
228
+
229
+ // =============================================================================
230
+ // MANY
231
+ // =============================================================================
232
+ export const ManyOptionsAutocompleteDefinition = `
233
+ import React, { useState } from 'react';
234
+ import Autocomplete from './Autocomplete';
235
+
236
+ const options = Array.from({ length: 50 }, (_, i) => ({
237
+ value: i,
238
+ label: 'Opción ' + i,
239
+ }));
240
+
241
+ export const ManyOptionsExample = () => {
242
+ const [value, setValue] = useState(null);
243
+
244
+ return (
245
+ <Autocomplete
246
+ options={options}
247
+ value={value}
248
+ onChange={(val) => {
249
+ setValue(val as string);
250
+ }}
251
+ />
252
+ );
253
+ };
254
+ `;
255
+
256
+ // =============================================================================
257
+ // WITH ERROR / HELPER TEXT
258
+ // =============================================================================
259
+ export const WithErrorAutocompleteDefinition = `
260
+ import React, { useState } from 'react';
261
+ import Autocomplete from './Autocomplete';
262
+ import { Box } from '@mui/material';
263
+
264
+ const options = [
265
+ { value: '10', label: '10' },
266
+ { value: '25', label: '25' },
267
+ ];
268
+
269
+ export const WithErrorExample = () => {
270
+ const [value, setValue] = useState<string | null>(null);
271
+ return (
272
+ <Box sx={{ width: 300 }}>
273
+ <Autocomplete
274
+ label="Cantidad"
275
+ options={options}
276
+ value={value}
277
+ onChange={(val) => setValue(val as string | null)}
278
+ error={\!value}
279
+ helperText={\!value ? 'Debes seleccionar una cantidad' : ' '}
280
+ />
281
+ </Box>
282
+ );
283
+ };
284
+ `;
285
+
286
+ // =============================================================================
287
+ // LABEL POSITION
288
+ // =============================================================================
289
+ export const LabelPositionAutocompleteDefinition = `
290
+ import React, { useState } from 'react';
291
+ import Autocomplete from './Autocomplete';
292
+ import { Box, Stack } from '@mui/material';
293
+
294
+ const options = [
295
+ { value: 'a', label: 'Opción A' },
296
+ { value: 'b', label: 'Opción B' },
297
+ ];
298
+
299
+ export const LabelPositionExample = () => {
300
+ const [a, setA] = useState<string | null>(null);
301
+ const [b, setB] = useState<string | null>(null);
302
+ return (
303
+ <Stack spacing={3} sx={{ width: 320 }}>
304
+ <Autocomplete
305
+ label="Outside (default)"
306
+ labelPosition="outside"
307
+ options={options}
308
+ value={a}
309
+ onChange={(val) => setA(val as string | null)}
310
+ />
311
+ <Autocomplete
312
+ label="Floating"
313
+ labelPosition="floating"
314
+ options={options}
315
+ value={b}
316
+ onChange={(val) => setB(val as string | null)}
317
+ />
318
+ </Stack>
319
+ );
320
+ };
321
+ `;
322
+
323
+ // =============================================================================
324
+ // CUSTOM BORDER RADIUS
325
+ // =============================================================================
326
+ export const CustomBorderRadiusAutocompleteDefinition = `
327
+ import React, { useState } from 'react';
328
+ import Autocomplete from './Autocomplete';
329
+ import { Box, Stack } from '@mui/material';
330
+
331
+ const options = [
332
+ { value: '10', label: '10' },
333
+ { value: '25', label: '25' },
334
+ ];
335
+
336
+ export const CustomBorderRadiusExample = () => {
337
+ const [a, setA] = useState<string | null>(null);
338
+ const [b, setB] = useState<string | null>(null);
339
+ const [c, setC] = useState<string | null>(null);
340
+ return (
341
+ <Stack spacing={2} sx={{ width: 300 }}>
342
+ <Autocomplete label="0" borderRadius={0} options={options} value={a} onChange={(v) => setA(v as string | null)} />
343
+ <Autocomplete label="10 (default)" borderRadius={10} options={options} value={b} onChange={(v) => setB(v as string | null)} />
344
+ <Autocomplete label="24 (pill)" borderRadius={24} options={options} value={c} onChange={(v) => setC(v as string | null)} />
345
+ </Stack>
346
+ );
347
+ };
348
+ `;
349
+
350
+ // =============================================================================
351
+ // EMPTY WITH PLACEHOLDER
352
+ // =============================================================================
353
+ export const EmptyWithPlaceholderAutocompleteDefinition = `
354
+ import React, { useState } from 'react';
355
+ import Autocomplete from './Autocomplete';
356
+ import { Box } from '@mui/material';
357
+
358
+ const options = [
359
+ { value: 'mx', label: 'México' },
360
+ { value: 'co', label: 'Colombia' },
361
+ { value: 'ar', label: 'Argentina' },
362
+ ];
363
+
364
+ export const EmptyWithPlaceholderExample = () => {
365
+ const [value, setValue] = useState<string | null>(null);
366
+ return (
367
+ <Box sx={{ width: 300 }}>
368
+ <Autocomplete
369
+ label="País"
370
+ placeholder="Buscar país..."
371
+ options={options}
372
+ value={value}
373
+ onChange={(val) => setValue(val as string | null)}
374
+ />
375
+ </Box>
376
+ );
377
+ };
378
+ `;
379
+
380
+ // =============================================================================
381
+ // REACT HOOK FORM
382
+ // =============================================================================
383
+ export const RHFAutocompleteDefinition = `
384
+ import React from 'react';
385
+ import { useForm } from 'react-hook-form';
386
+ import Autocomplete from './Autocomplete';
387
+ import { Box, Button, Stack } from '@mui/material';
388
+
389
+ const options = [
390
+ { value: 'mx', label: 'México' },
391
+ { value: 'co', label: 'Colombia' },
392
+ { value: 'ar', label: 'Argentina' },
393
+ ];
394
+
395
+ type FormValues = { country: string | null };
396
+
397
+ export const RHFExample = () => {
398
+ const { control, handleSubmit, watch } = useForm<FormValues>({
399
+ defaultValues: { country: null },
400
+ });
401
+
402
+ return (
403
+ <Box component="form" onSubmit={handleSubmit((data) => console.log(data))}>
404
+ <Stack spacing={2} sx={{ width: 320 }}>
405
+ <Autocomplete
406
+ name="country"
407
+ control={control}
408
+ validation={{ required: 'Campo requerido' }}
409
+ label="País"
410
+ placeholder="Seleccione..."
411
+ options={options}
412
+ />
413
+ <Button type="submit" variant="contained">Guardar</Button>
414
+ <Box>Valor actual: {JSON.stringify(watch('country'))}</Box>
415
+ </Stack>
416
+ </Box>
417
+ );
418
+ };
419
+ `;
420
+
421
+ // =============================================================================
422
+ // ASYNC — búsqueda remota contra un servicio
423
+ // =============================================================================
424
+ export const AsyncServiceAutocompleteDefinition = [
425
+ 'import React, { useState, useEffect, useMemo } from ', "'react'", ';',
426
+ 'import Autocomplete from ', "'./Autocomplete'", ';',
427
+ 'import { Box } from ', "'@mui/material'", ';',
428
+ 'import debounce from ', "'lodash/debounce'", ';',
429
+ '',
430
+ '// Supón que tienes un servicio:',
431
+ '// await fetch(`/api/users?q=${query}`).then(r => r.json())',
432
+ 'async function fetchUsers(query: string) {',
433
+ ' const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`);',
434
+ ' const data = await res.json();',
435
+ ' return data.map((u: any) => ({ value: u.id, label: u.name }));',
436
+ '}',
437
+ '',
438
+ 'export const AsyncServiceExample = () => {',
439
+ ' const [value, setValue] = useState<string | null>(null);',
440
+ ' const [input, setInput] = useState("");',
441
+ ' const [options, setOptions] = useState<any[]>([]);',
442
+ ' const [loading, setLoading] = useState(false);',
443
+ '',
444
+ ' const search = useMemo(',
445
+ ' () =>',
446
+ ' debounce(async (q: string) => {',
447
+ ' if (\!q) { setOptions([]); return; }',
448
+ ' setLoading(true);',
449
+ ' try {',
450
+ ' setOptions(await fetchUsers(q));',
451
+ ' } finally {',
452
+ ' setLoading(false);',
453
+ ' }',
454
+ ' }, 300),',
455
+ ' [],',
456
+ ' );',
457
+ '',
458
+ ' useEffect(() => { search(input); }, [input, search]);',
459
+ '',
460
+ ' return (',
461
+ ' <Box sx={{ width: 320 }}>',
462
+ ' <Autocomplete',
463
+ ' label="Usuario"',
464
+ ' placeholder="Buscar usuario..."',
465
+ ' options={options}',
466
+ ' value={value}',
467
+ ' onChange={(v) => setValue(v as string | null)}',
468
+ ' inputValue={input}',
469
+ ' onInputChange={(_, v) => setInput(v)}',
470
+ ' loading={loading}',
471
+ ' // Desactiva el filtro cliente: el servicio ya filtra.',
472
+ ' filterOptions={(x) => x}',
473
+ ' />',
474
+ ' </Box>',
475
+ ' );',
476
+ '};',
477
+ ].join('\n');
@@ -0,0 +1,60 @@
1
+ import type { SxProps, Theme } from '@mui/material/styles';
2
+ import type { SelectOption } from './Autocomplete';
3
+
4
+ /** Resuelve un value único a su SelectOption dentro del pool. */
5
+ export const resolveSingleValue = (
6
+ options: SelectOption[],
7
+ value: SelectOption['value'] | null | undefined,
8
+ ): SelectOption | null => options.find((o) => o.value === value) ?? null;
9
+
10
+ /** Resuelve un array de values a sus SelectOption dentro del pool. */
11
+ export const resolveMultipleValue = (
12
+ options: SelectOption[],
13
+ value: SelectOption['value'][] | null | undefined,
14
+ ): SelectOption[] => {
15
+ if (!Array.isArray(value)) return [];
16
+ return options.filter((o) => value.includes(o.value));
17
+ };
18
+
19
+ /** Merge de sx base + extra del consumer, respetando el contrato de MUI. */
20
+ export const mergeSx = (
21
+ base: SxProps<Theme>,
22
+ extra?: SxProps<Theme>,
23
+ ): SxProps<Theme> => {
24
+ if (!extra) return base;
25
+ return [base, ...(Array.isArray(extra) ? extra : [extra])] as SxProps<Theme>;
26
+ };
27
+
28
+ /**
29
+ * Compara dos valores resueltos (single o multiple) por value para decidir
30
+ * si son estructuralmente iguales. Se usa para estabilizar la referencia que
31
+ * se pasa a MUI Autocomplete (MUI compara value por referencia con `!==`,
32
+ * y una nueva referencia — aunque el contenido sea igual — dispara
33
+ * `resetInputValue` y borra el texto mientras el usuario tipea).
34
+ */
35
+ export const areResolvedValuesEqual = (
36
+ prev: SelectOption | SelectOption[] | null,
37
+ next: SelectOption | SelectOption[] | null,
38
+ multiple: boolean,
39
+ ): boolean => {
40
+ if (multiple) {
41
+ if (!Array.isArray(prev) || !Array.isArray(next)) return false;
42
+ if (prev.length !== next.length) return false;
43
+ return prev.every((o, i) => o.value === next[i]?.value);
44
+ }
45
+ return (
46
+ (prev as SelectOption | null)?.value ===
47
+ (next as SelectOption | null)?.value
48
+ );
49
+ };
50
+
51
+ /** Determina si un valor resuelto está vacío (para placeholder/shrink). */
52
+ export const isResolvedValueEmpty = (
53
+ resolvedValue: unknown,
54
+ multiple: boolean,
55
+ ): boolean => {
56
+ if (multiple) {
57
+ return !Array.isArray(resolvedValue) || resolvedValue.length === 0;
58
+ }
59
+ return !resolvedValue;
60
+ };