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

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 (181) hide show
  1. package/dist/components/Icon/Icon.js +1 -1
  2. package/dist/components/Table/Table.js +1 -1
  3. package/package.json +32 -4
  4. package/.dockerignore +0 -8
  5. package/.github/workflows/publish.yml +0 -107
  6. package/.prettierrc +0 -3
  7. package/.storybook/main.ts +0 -19
  8. package/.storybook/preview.ts +0 -14
  9. package/.storybook/vitest.setup.ts +0 -9
  10. package/Dockerfile +0 -37
  11. package/build.js +0 -102
  12. package/chromatic.config.json +0 -5
  13. package/cleanDirectories.js +0 -40
  14. package/rollup.config.cjs +0 -87
  15. package/src/components/ActionMenu/ActionMenu.stories.tsx +0 -230
  16. package/src/components/ActionMenu/ActionMenu.tsx +0 -174
  17. package/src/components/ActionMenu/index.ts +0 -2
  18. package/src/components/AppBar/AppBar.stories.tsx +0 -272
  19. package/src/components/AppBar/AppBar.sx.ts +0 -32
  20. package/src/components/AppBar/AppBar.tsx +0 -123
  21. package/src/components/AppBar/AppBarBrand.tsx +0 -120
  22. package/src/components/AppBar/AppBarContext.ts +0 -25
  23. package/src/components/AppBar/AppBarMenuToggle.tsx +0 -90
  24. package/src/components/AppBar/AppBarUserMenu.tsx +0 -217
  25. package/src/components/AppBar/index.ts +0 -25
  26. package/src/components/Autocomplete/Autocomplete.definitions.ts +0 -477
  27. package/src/components/Autocomplete/Autocomplete.helpers.ts +0 -60
  28. package/src/components/Autocomplete/Autocomplete.stories.tsx +0 -748
  29. package/src/components/Autocomplete/Autocomplete.sx.ts +0 -30
  30. package/src/components/Autocomplete/Autocomplete.tsx +0 -361
  31. package/src/components/Autocomplete/Autocomplete.types.ts +0 -13
  32. package/src/components/Autocomplete/_parts/AutocompleteChips.tsx +0 -55
  33. package/src/components/Autocomplete/_parts/AutocompleteLoader.tsx +0 -17
  34. package/src/components/Autocomplete/_parts/AutocompleteOption.tsx +0 -31
  35. package/src/components/Autocomplete/index.ts +0 -12
  36. package/src/components/Avatar/Avatar.definitions.ts +0 -162
  37. package/src/components/Avatar/Avatar.stories.tsx +0 -258
  38. package/src/components/Avatar/Avatar.tsx +0 -206
  39. package/src/components/Avatar/index.ts +0 -1
  40. package/src/components/Button/Button.definition.ts +0 -97
  41. package/src/components/Button/Button.stories.tsx +0 -285
  42. package/src/components/Button/Button.tsx +0 -67
  43. package/src/components/Button/index.ts +0 -1
  44. package/src/components/Card/Card.definition.ts +0 -5
  45. package/src/components/Card/Card.stories.tsx +0 -221
  46. package/src/components/Card/Card.sx.ts +0 -104
  47. package/src/components/Card/Card.tsx +0 -200
  48. package/src/components/Card/index.ts +0 -9
  49. package/src/components/Chip/Chip.definitions.ts +0 -167
  50. package/src/components/Chip/Chip.stories.tsx +0 -265
  51. package/src/components/Chip/Chip.tsx +0 -61
  52. package/src/components/Chip/index.ts +0 -1
  53. package/src/components/Column/Column.tsx +0 -29
  54. package/src/components/Column/index.ts +0 -1
  55. package/src/components/DatePicker/DatePicker.definitions.ts +0 -228
  56. package/src/components/DatePicker/DatePicker.helpers.ts +0 -24
  57. package/src/components/DatePicker/DatePicker.stories.tsx +0 -309
  58. package/src/components/DatePicker/DatePicker.sx.ts +0 -33
  59. package/src/components/DatePicker/DatePicker.tsx +0 -189
  60. package/src/components/DatePicker/DatePicker.types.ts +0 -10
  61. package/src/components/DatePicker/index.ts +0 -9
  62. package/src/components/DateRangePicker/DateRangePicker.definitions.ts +0 -191
  63. package/src/components/DateRangePicker/DateRangePicker.stories.tsx +0 -252
  64. package/src/components/DateRangePicker/DateRangePicker.tsx +0 -56
  65. package/src/components/DateRangePicker/index.ts +0 -1
  66. package/src/components/DateTimePicker/DateTimePicker.definitions.ts +0 -256
  67. package/src/components/DateTimePicker/DateTimePicker.helpers.ts +0 -38
  68. package/src/components/DateTimePicker/DateTimePicker.stories.tsx +0 -418
  69. package/src/components/DateTimePicker/DateTimePicker.sx.ts +0 -30
  70. package/src/components/DateTimePicker/DateTimePicker.tsx +0 -225
  71. package/src/components/DateTimePicker/DateTimePicker.types.ts +0 -10
  72. package/src/components/DateTimePicker/index.ts +0 -9
  73. package/src/components/Drawer/Drawer.stories.tsx +0 -270
  74. package/src/components/Drawer/Drawer.sx.ts +0 -106
  75. package/src/components/Drawer/Drawer.tsx +0 -214
  76. package/src/components/Drawer/DrawerContext.ts +0 -26
  77. package/src/components/Drawer/DrawerItem.tsx +0 -110
  78. package/src/components/Drawer/index.ts +0 -10
  79. package/src/components/Flyout/Flyout.stories.tsx +0 -282
  80. package/src/components/Flyout/Flyout.tsx +0 -122
  81. package/src/components/Flyout/index.ts +0 -1
  82. package/src/components/Gallery/Gallery.definition.tsx +0 -37
  83. package/src/components/Gallery/Gallery.stories.tsx +0 -82
  84. package/src/components/Gallery/Gallery.tsx +0 -118
  85. package/src/components/Gallery/GalleryLightbox.tsx +0 -170
  86. package/src/components/Gallery/GalleryMain.tsx +0 -84
  87. package/src/components/Gallery/GalleryThumbnails.tsx +0 -106
  88. package/src/components/Gallery/index.ts +0 -1
  89. package/src/components/Icon/Icon.stories.tsx +0 -121
  90. package/src/components/Icon/Icon.tsx +0 -175
  91. package/src/components/Icon/index.ts +0 -2
  92. package/src/components/Input/Input.definitions.ts +0 -324
  93. package/src/components/Input/Input.helpers.ts +0 -49
  94. package/src/components/Input/Input.stories.tsx +0 -499
  95. package/src/components/Input/Input.sx.ts +0 -42
  96. package/src/components/Input/Input.tsx +0 -141
  97. package/src/components/Input/Input.types.ts +0 -10
  98. package/src/components/Input/index.ts +0 -9
  99. package/src/components/InputGroup/InputGroup.definitions.ts +0 -158
  100. package/src/components/InputGroup/InputGroup.stories.tsx +0 -267
  101. package/src/components/InputGroup/InputGroup.tsx +0 -179
  102. package/src/components/InputGroup/index.ts +0 -1
  103. package/src/components/MenuButton/MenuButton.stories.tsx +0 -197
  104. package/src/components/MenuButton/MenuButton.tsx +0 -100
  105. package/src/components/MenuButton/index.ts +0 -1
  106. package/src/components/Modal/Modal.stories.tsx +0 -721
  107. package/src/components/Modal/Modal.tsx +0 -355
  108. package/src/components/Modal/ModalBody.tsx +0 -16
  109. package/src/components/Modal/ModalFooter.tsx +0 -71
  110. package/src/components/Modal/ModalHeader.tsx +0 -18
  111. package/src/components/Modal/index.ts +0 -6
  112. package/src/components/PageLoader/PageLoader.stories.tsx +0 -217
  113. package/src/components/PageLoader/PageLoader.tsx +0 -96
  114. package/src/components/PageLoader/index.ts +0 -2
  115. package/src/components/ScrollTopButton/ScrollTopButton.stories.tsx +0 -158
  116. package/src/components/ScrollTopButton/ScrollTopButton.tsx +0 -135
  117. package/src/components/ScrollTopButton/index.ts +0 -8
  118. package/src/components/ScrollTopButton/scrollToTop.ts +0 -37
  119. package/src/components/Select/Select.definitions.ts +0 -602
  120. package/src/components/Select/Select.helpers.ts +0 -71
  121. package/src/components/Select/Select.stories.tsx +0 -687
  122. package/src/components/Select/Select.sx.ts +0 -14
  123. package/src/components/Select/Select.tsx +0 -429
  124. package/src/components/Select/Select.types.ts +0 -15
  125. package/src/components/Select/_parts/SelectMenuItem.tsx +0 -40
  126. package/src/components/Select/_parts/SelectSearchHeader.tsx +0 -51
  127. package/src/components/Select/_parts/SelectValue.tsx +0 -96
  128. package/src/components/Select/index.ts +0 -14
  129. package/src/components/Stat/Stat.stories.tsx +0 -85
  130. package/src/components/Stat/Stat.tsx +0 -117
  131. package/src/components/Stat/index.ts +0 -2
  132. package/src/components/StatusMessage/StatusMessage.stories.tsx +0 -130
  133. package/src/components/StatusMessage/StatusMessage.tsx +0 -162
  134. package/src/components/StatusMessage/index.ts +0 -2
  135. package/src/components/Stepper/Step.tsx +0 -21
  136. package/src/components/Stepper/Stepper.definition.ts +0 -75
  137. package/src/components/Stepper/Stepper.stories.tsx +0 -122
  138. package/src/components/Stepper/Stepper.tsx +0 -75
  139. package/src/components/Stepper/index.ts +0 -2
  140. package/src/components/Table/EmptyTable.png +0 -0
  141. package/src/components/Table/Table.definition.ts +0 -580
  142. package/src/components/Table/Table.stories.tsx +0 -853
  143. package/src/components/Table/Table.tsx +0 -495
  144. package/src/components/Table/data.ts +0 -134
  145. package/src/components/Table/exportsUtils.ts +0 -195
  146. package/src/components/Table/index.ts +0 -3
  147. package/src/components/Table/types.ts +0 -34
  148. package/src/components/Tabs/Tab.definition.ts +0 -53
  149. package/src/components/Tabs/Tab.tsx +0 -19
  150. package/src/components/Tabs/Tabs.stories.tsx +0 -118
  151. package/src/components/Tabs/Tabs.tsx +0 -99
  152. package/src/components/Tabs/_tabUtils.tsx +0 -4
  153. package/src/components/Tabs/index.ts +0 -2
  154. package/src/components/Timeline/Timeline.definition.ts +0 -43
  155. package/src/components/Timeline/Timeline.stories.tsx +0 -108
  156. package/src/components/Timeline/Timeline.tsx +0 -49
  157. package/src/components/Timeline/TimelineItem.tsx +0 -31
  158. package/src/components/Timeline/index.ts +0 -2
  159. package/src/components/Tooltip/Tooltip.stories.tsx +0 -129
  160. package/src/components/Tooltip/Tooltip.tsx +0 -58
  161. package/src/components/Tooltip/index.ts +0 -1
  162. package/src/components/_shared/formField.sx.ts +0 -118
  163. package/src/components/_shared/resolvePreset.ts +0 -35
  164. package/src/hooks/ClipBoard/ClipBoard.stories.tsx +0 -168
  165. package/src/hooks/ClipBoard/ClipBoard.tsx +0 -131
  166. package/src/hooks/ClipBoard/ClipboardUnifiedDemo.tsx +0 -111
  167. package/src/hooks/ClipBoard/index.ts +0 -1
  168. package/src/hooks/Wizard/Wizard.stories.tsx +0 -301
  169. package/src/hooks/Wizard/WizardContext.tsx +0 -166
  170. package/src/hooks/Wizard/index.ts +0 -6
  171. package/src/hooks/Wizard/useWizard.ts +0 -13
  172. package/src/index.ts +0 -17
  173. package/src/mui.ts +0 -44
  174. package/src/styles.css +0 -3
  175. package/src/theme/componentStyles.ts +0 -47
  176. package/src/theme/tokens.ts +0 -43
  177. package/tailwind.config.js +0 -10
  178. package/tsconfig.json +0 -48
  179. package/tsup.config.js +0 -41
  180. package/vite.config.js +0 -132
  181. package/vitest.config.ts +0 -35
@@ -1,14 +0,0 @@
1
- import type { SelectProps as MuiSelectProps } from '@mui/material';
2
-
3
- import { buildFormFieldSx } from '../_shared/formField.sx';
4
- import type { LabelPosition } from './Select';
5
-
6
- /**
7
- * Builder de sx para el Select. Usa el builder compartido
8
- * `buildFormFieldSx`. El Select no necesita overrides específicos.
9
- */
10
- export const buildSelectSx = (
11
- borderRadius: number | string,
12
- labelPosition: LabelPosition,
13
- ): MuiSelectProps['sx'] =>
14
- buildFormFieldSx({ borderRadius, labelPosition }) as MuiSelectProps['sx'];
@@ -1,429 +0,0 @@
1
- import React, {
2
- isValidElement,
3
- useEffect,
4
- useMemo,
5
- useRef,
6
- useState,
7
- type ReactElement,
8
- type ReactNode,
9
- } from 'react';
10
- import {
11
- Box,
12
- CircularProgress,
13
- FormControl,
14
- FormHelperText,
15
- InputLabel,
16
- ListSubheader,
17
- MenuItem,
18
- OutlinedInput,
19
- Select as MuiSelect,
20
- SelectChangeEvent,
21
- Typography,
22
- type SelectProps as MuiSelectProps,
23
- } from '@mui/material';
24
- import { useTheme } from '@mui/material/styles';
25
- import { Controller, type Control, type RegisterOptions } from 'react-hook-form';
26
-
27
- import { buildSelectSx } from './Select.sx';
28
- import { resolvePreset } from '../_shared/resolvePreset';
29
- import {
30
- filterOptionsByLabel,
31
- groupOptions,
32
- isGroupedOptionsEmpty,
33
- isSelectValueEmpty,
34
- normalizeSelectValue,
35
- } from './Select.helpers';
36
- import { SelectSearchHeader } from './_parts/SelectSearchHeader';
37
- import { SelectValue } from './_parts/SelectValue';
38
- import { renderSelectMenuItem } from './_parts/SelectMenuItem';
39
-
40
- // ── Tipos de dominio ─────────────────────────────────────────────────────
41
- export interface SelectOption {
42
- value: string | number;
43
- label: string;
44
- img?: string;
45
- disabled?: boolean;
46
- group?: string;
47
- [key: string]: any;
48
- }
49
-
50
- export type LabelPosition = 'outside' | 'floating';
51
- export type ChipVariant = 'outlined' | 'filled';
52
- export type SelectSize = 'small' | 'medium';
53
-
54
- // ── Render slots ─────────────────────────────────────────────────────────
55
- export type RenderOptionItem = (item: SelectOption) => ReactNode;
56
- export type RenderChipLabel = (item: SelectOption) => ReactNode;
57
-
58
- // ── Sub-componente declarativo <Option> ──────────────────────────────────
59
- export interface OptionProps {
60
- children: RenderOptionItem;
61
- }
62
-
63
- // ── Props base ───────────────────────────────────────────────────────────
64
- export interface BaseSelectProps<
65
- TValue extends SelectOption['value'] = SelectOption['value'],
66
- > {
67
- label?: string;
68
- options?: SelectOption[];
69
- defaultValue?: TValue | TValue[];
70
- size?: SelectSize;
71
- multiple?: boolean;
72
- filterable?: boolean;
73
- placeholder?: string;
74
- children?: ReactElement<{ children: RenderOptionItem }>;
75
- maxHeight?: number | string;
76
- maxWidth?: number | string;
77
- maxChipsToShow?: number;
78
- renderChipLabel?: RenderChipLabel;
79
- chipVariant?: ChipVariant;
80
- loadOptions?: (inputValue: string) => Promise<SelectOption[]>;
81
- loadingMessage?: ReactNode;
82
- noOptionsMessage?: ReactNode;
83
- debounceTimeout?: number;
84
- disabled?: boolean;
85
- error?: boolean;
86
- helperText?: string;
87
- /** Border radius del input (px o string CSS). Default: 10 */
88
- borderRadius?: number | string;
89
- /**
90
- * Posición del label.
91
- * - "outside" (default): label arriba del input, sin animación.
92
- * - "floating": label clásico de MUI (flota dentro del notched outline).
93
- */
94
- labelPosition?: LabelPosition;
95
- /**
96
- * Nombre del preset de estilo registrado en `theme.styles.Select`.
97
- * - `"default"` (o ausente) = estilo built-in del paquete.
98
- * - Cualquier otro string = mergea el preset encima del estilo built-in.
99
- */
100
- preset?: string;
101
- sx?: MuiSelectProps['sx'];
102
- className?: string;
103
- }
104
-
105
- // ── Variantes discriminadas (RHF vs controlado) ──────────────────────────
106
- export interface RHFSelectProps<
107
- TValue extends SelectOption['value'] = SelectOption['value'],
108
- > extends BaseSelectProps<TValue> {
109
- name: string;
110
- control: Control<any>;
111
- validation?: RegisterOptions;
112
- value?: TValue | TValue[];
113
- onChange?: (val: TValue | TValue[]) => void;
114
- }
115
-
116
- export interface StandardSelectProps<
117
- TValue extends SelectOption['value'] = SelectOption['value'],
118
- > extends BaseSelectProps<TValue> {
119
- name?: string;
120
- control?: never;
121
- validation?: never;
122
- value: TValue | TValue[];
123
- onChange: (val: TValue | TValue[]) => void;
124
- }
125
-
126
- // ── API pública final ────────────────────────────────────────────────────
127
- export type SelectProps<
128
- TValue extends SelectOption['value'] = SelectOption['value'],
129
- > = RHFSelectProps<TValue> | StandardSelectProps<TValue>;
130
-
131
- // ── Sub-componente declarativo <Option> ──────────────────────────────────
132
- export function Option(_props: OptionProps) {
133
- return null;
134
- }
135
- Option.displayName = 'Option';
136
-
137
- // ── Componente ───────────────────────────────────────────────────────────
138
- export function Select<TValue extends SelectOption['value'] = SelectOption['value']>(
139
- props: SelectProps<TValue>,
140
- ) {
141
- const {
142
- label,
143
- options = [],
144
- value,
145
- onChange,
146
- defaultValue,
147
- size = 'small',
148
- multiple = false,
149
- filterable = false,
150
- placeholder,
151
- children,
152
- maxHeight = 300,
153
- maxWidth,
154
- maxChipsToShow = 3,
155
- renderChipLabel,
156
- chipVariant = 'outlined',
157
- loadOptions,
158
- loadingMessage = 'Cargando opciones...',
159
- noOptionsMessage = 'No hay opciones',
160
- debounceTimeout = 300,
161
- disabled = false,
162
- error = false,
163
- helperText,
164
- borderRadius = 10,
165
- labelPosition = 'outside',
166
- preset,
167
- sx,
168
- className,
169
- ...rest
170
- } = props;
171
-
172
- const theme = useTheme();
173
- const presetSx = resolvePreset('Select', preset, theme);
174
-
175
- const isRHFMode = 'control' in rest && (rest as any).control !== undefined;
176
-
177
- // Custom render opcional vía <Option>{item => ...}</Option>
178
- const customRender: RenderOptionItem | null = useMemo(() => {
179
- if (React.Children.count(children) === 1) {
180
- const child = React.Children.only(children);
181
- if (isValidElement(child) && (child.type as any)?.displayName === 'Option') {
182
- return (child.props as OptionProps).children;
183
- }
184
- }
185
- return null;
186
- }, [children]);
187
-
188
- // Búsqueda / async
189
- const [search, setSearch] = useState('');
190
- const [asyncOptions, setAsyncOptions] = useState<SelectOption[]>([]);
191
- const [loading, setLoading] = useState(false);
192
- const debounceTimerRef = useRef<number | null>(null);
193
- const searchInputRef = useRef<HTMLInputElement>(null);
194
-
195
- // Focus/open tracking para el comportamiento del label "outside".
196
- const [isOpen, setIsOpen] = useState(false);
197
- const [isFocused, setIsFocused] = useState(false);
198
-
199
- const isAsync = typeof loadOptions === 'function';
200
- const currentOptions = isAsync ? asyncOptions : options;
201
-
202
- useEffect(() => {
203
- if (!isAsync) return;
204
-
205
- if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
206
-
207
- if (search) {
208
- setLoading(true);
209
- debounceTimerRef.current = window.setTimeout(() => {
210
- loadOptions!(search)
211
- .then(setAsyncOptions)
212
- .finally(() => {
213
- setLoading(false);
214
- searchInputRef.current?.focus();
215
- });
216
- }, debounceTimeout);
217
- } else {
218
- setAsyncOptions([]);
219
- setLoading(false);
220
- searchInputRef.current?.focus();
221
- }
222
- }, [search, isAsync, loadOptions, debounceTimeout]);
223
-
224
- const filteredOptions = useMemo(() => {
225
- if (isAsync) return currentOptions;
226
- if (!filterable) return currentOptions;
227
- return filterOptionsByLabel(currentOptions, search);
228
- }, [search, filterable, currentOptions, isAsync]);
229
-
230
- const groupedOptions = useMemo(() => groupOptions(filteredOptions), [filteredOptions]);
231
-
232
- // Items del menu (search header + loading/empty + grupos + opciones)
233
- const menuItems = useMemo(() => {
234
- const items: React.ReactNode[] = [];
235
-
236
- if (filterable || isAsync) {
237
- items.push(
238
- <SelectSearchHeader
239
- key="search-header"
240
- value={search}
241
- onChange={setSearch}
242
- inputRef={searchInputRef}
243
- />,
244
- );
245
- }
246
-
247
- const noOptions = isGroupedOptionsEmpty(groupedOptions);
248
-
249
- if (loading) {
250
- items.push(
251
- <MenuItem key="loading-message" disabled>
252
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
253
- <CircularProgress size={20} />
254
- <Typography>{loadingMessage}</Typography>
255
- </Box>
256
- </MenuItem>,
257
- );
258
- } else if (noOptions) {
259
- items.push(
260
- <MenuItem key="no-options-message" disabled>
261
- <Typography>{noOptionsMessage}</Typography>
262
- </MenuItem>,
263
- );
264
- } else {
265
- Object.entries(groupedOptions).forEach(([group, opts]) => {
266
- if (group !== '__default') {
267
- items.push(
268
- <ListSubheader key={group} disableSticky>
269
- {group}
270
- </ListSubheader>,
271
- );
272
- }
273
- opts.forEach((opt) => {
274
- const isSelected = multiple
275
- ? (value as TValue[] | undefined)?.includes(opt.value as TValue) ?? false
276
- : value === opt.value;
277
-
278
- items.push(
279
- renderSelectMenuItem({
280
- option: opt,
281
- selected: isSelected,
282
- customRender,
283
- }),
284
- );
285
- });
286
- });
287
- }
288
- return items;
289
- }, [
290
- groupedOptions,
291
- customRender,
292
- filterable,
293
- isAsync,
294
- search,
295
- loading,
296
- loadingMessage,
297
- noOptionsMessage,
298
- multiple,
299
- value,
300
- ]);
301
-
302
- // Handler de cambio
303
- const handleChangeInternal = (
304
- event: SelectChangeEvent<any>,
305
- rhfOnChange?: (...event: any[]) => void,
306
- ) => {
307
- const newValue = event.target.value;
308
- if (isRHFMode) {
309
- rhfOnChange?.(newValue);
310
- } else {
311
- (onChange as (val: TValue | TValue[]) => void)?.(newValue);
312
- }
313
- };
314
-
315
- // Sx mergeado (defaults + consumer)
316
- const mergedSx = [
317
- buildSelectSx(borderRadius, labelPosition),
318
- ...(presetSx ? [presetSx] : []),
319
- ...(Array.isArray(sx) ? sx : [sx]),
320
- ];
321
-
322
- // Render base
323
- const renderSelect = (
324
- selectValue: any,
325
- selectOnChange: any,
326
- onBlur?: any,
327
- inputRef?: any,
328
- rhfError?: boolean,
329
- rhfHelperText?: string,
330
- ) => {
331
- const finalError = rhfError ?? error;
332
- const finalHelperText = rhfHelperText ?? helperText;
333
-
334
- const inputElement =
335
- labelPosition === 'floating' ? (
336
- <OutlinedInput label={label} />
337
- ) : (
338
- <OutlinedInput />
339
- );
340
-
341
- const normalizedValue = normalizeSelectValue(selectValue, multiple);
342
- const isEmpty = isSelectValueEmpty(normalizedValue, multiple);
343
-
344
- // Shrink cuando hay valor, foco, o dropdown abierto.
345
- const shouldShrink = !isEmpty || isFocused || isOpen;
346
- // Placeholder solo cuando el label ya subió.
347
- const shouldDisplayPlaceholder = isEmpty && (isFocused || isOpen) && !!placeholder;
348
-
349
- return (
350
- <FormControl
351
- fullWidth
352
- size={size}
353
- error={finalError}
354
- disabled={disabled}
355
- className={className}
356
- sx={mergedSx}
357
- >
358
- {label && <InputLabel shrink={shouldShrink}>{label}</InputLabel>}
359
- <MuiSelect
360
- value={normalizedValue}
361
- defaultValue={defaultValue}
362
- multiple={multiple}
363
- onChange={selectOnChange}
364
- onBlur={(e) => {
365
- setIsFocused(false);
366
- onBlur?.(e);
367
- }}
368
- onFocus={() => setIsFocused(true)}
369
- onOpen={() => setIsOpen(true)}
370
- onClose={() => setIsOpen(false)}
371
- renderValue={(selected) => (
372
- <SelectValue<TValue>
373
- selected={selected as TValue | TValue[]}
374
- options={currentOptions}
375
- multiple={multiple}
376
- placeholder={placeholder}
377
- maxChipsToShow={maxChipsToShow}
378
- chipVariant={chipVariant}
379
- renderChipLabel={renderChipLabel}
380
- currentValues={(value as TValue[]) ?? []}
381
- onDeleteChip={(next) =>
382
- (onChange as (v: TValue | TValue[]) => void)?.(next)
383
- }
384
- />
385
- )}
386
- displayEmpty={shouldDisplayPlaceholder}
387
- input={inputElement}
388
- disabled={disabled}
389
- MenuProps={{
390
- PaperProps: {
391
- style: { maxHeight, maxWidth },
392
- },
393
- }}
394
- inputRef={inputRef}
395
- {...(rest as any)}
396
- >
397
- {menuItems}
398
- </MuiSelect>
399
- {finalHelperText && <FormHelperText>{finalHelperText}</FormHelperText>}
400
- </FormControl>
401
- );
402
- };
403
-
404
- // Branch: RHF vs controlado
405
- if (isRHFMode) {
406
- const rhfProps = rest as RHFSelectProps<TValue>;
407
- return (
408
- <Controller
409
- name={rhfProps.name}
410
- control={rhfProps.control}
411
- rules={rhfProps.validation}
412
- render={({ field, fieldState: { error: fieldError } }) =>
413
- renderSelect(
414
- field.value,
415
- (e: SelectChangeEvent<any>) => handleChangeInternal(e, field.onChange),
416
- field.onBlur,
417
- field.ref,
418
- !!fieldError,
419
- fieldError?.message,
420
- )
421
- }
422
- />
423
- );
424
- }
425
-
426
- return renderSelect(value, (e: SelectChangeEvent<any>) => handleChangeInternal(e));
427
- }
428
-
429
- export default Select;
@@ -1,15 +0,0 @@
1
- // Re-export barrel para compatibilidad con imports antiguos.
2
- // Los tipos ahora viven dentro de Select.tsx.
3
- export type {
4
- SelectOption,
5
- LabelPosition,
6
- ChipVariant,
7
- SelectSize,
8
- RenderOptionItem,
9
- RenderChipLabel,
10
- OptionProps,
11
- BaseSelectProps,
12
- RHFSelectProps,
13
- StandardSelectProps,
14
- SelectProps,
15
- } from './Select';
@@ -1,40 +0,0 @@
1
- import React from 'react';
2
- import { ListItemIcon, MenuItem } from '@mui/material';
3
- import CheckIcon from '@mui/icons-material/Check';
4
-
5
- import type { SelectOption, RenderOptionItem } from '../Select';
6
-
7
- interface RenderSelectMenuItemArgs {
8
- option: SelectOption;
9
- selected: boolean;
10
- customRender?: RenderOptionItem | null;
11
- }
12
-
13
- /**
14
- * Render helper (no componente) que devuelve un <MenuItem> listo para
15
- * colocarse como hijo directo del <MuiSelect>. Se implementa como función
16
- * y no como componente porque MUI Select inspecciona `props.value` de sus
17
- * children para resolver la selección, y envolverlo en un componente extra
18
- * rompe esa detección.
19
- *
20
- * Incluye el icono de check a la izquierda cuando `selected` es true. Si el
21
- * consumer pasó un `<Option>{(opt) => ...}</Option>`, usa ese render para el
22
- * contenido principal; de lo contrario muestra `option.label`.
23
- */
24
- export const renderSelectMenuItem = ({
25
- option,
26
- selected,
27
- customRender,
28
- }: RenderSelectMenuItemArgs): React.ReactElement => (
29
- <MenuItem
30
- key={String(option.value)}
31
- value={option.value}
32
- disabled={option.disabled}
33
- selected={selected}
34
- >
35
- <ListItemIcon sx={{ minWidth: 32 }}>
36
- {selected && <CheckIcon color="success" fontSize="small" />}
37
- </ListItemIcon>
38
- {customRender ? customRender(option) : option.label}
39
- </MenuItem>
40
- );
@@ -1,51 +0,0 @@
1
- import React from 'react';
2
- import { ListSubheader, TextField } from '@mui/material';
3
-
4
- interface SelectSearchHeaderProps {
5
- value: string;
6
- onChange: (next: string) => void;
7
- placeholder?: string;
8
- inputRef?: React.Ref<HTMLInputElement>;
9
- }
10
-
11
- /**
12
- * Header sticky del menu del Select que contiene el input de búsqueda.
13
- * Se usa cuando `filterable` es true o cuando el Select opera en modo async.
14
- * Detiene la propagación de click/keydown para que el menu no se cierre ni
15
- * intercepte las teclas al tipear.
16
- */
17
- export const SelectSearchHeader: React.FC<SelectSearchHeaderProps> = ({
18
- value,
19
- onChange,
20
- placeholder = 'Buscar...',
21
- inputRef,
22
- }) => {
23
- return (
24
- <ListSubheader
25
- sx={{
26
- px: 2,
27
- pt: 1,
28
- pb: 1,
29
- backgroundColor: 'background.paper',
30
- zIndex: 1,
31
- position: 'sticky',
32
- top: 0,
33
- }}
34
- >
35
- <TextField
36
- inputRef={inputRef}
37
- placeholder={placeholder}
38
- variant="standard"
39
- size="small"
40
- fullWidth
41
- value={value}
42
- onChange={(e) => onChange(e.target.value)}
43
- slotProps={{ htmlInput: { autoFocus: true } }}
44
- onClick={(e) => e.stopPropagation()}
45
- onKeyDown={(e) => e.stopPropagation()}
46
- />
47
- </ListSubheader>
48
- );
49
- };
50
-
51
- export default SelectSearchHeader;
@@ -1,96 +0,0 @@
1
- import React from 'react';
2
- import { Avatar, Box, Chip, Typography } from '@mui/material';
3
- import ClearIcon from '@mui/icons-material/Clear';
4
-
5
- import type {
6
- SelectOption,
7
- RenderChipLabel,
8
- ChipVariant,
9
- } from '../Select';
10
-
11
- interface SelectValueProps<TValue extends SelectOption['value']> {
12
- selected: TValue | TValue[] | null | undefined;
13
- options: SelectOption[];
14
- multiple: boolean;
15
- placeholder?: string;
16
- maxChipsToShow: number;
17
- chipVariant: ChipVariant;
18
- renderChipLabel?: RenderChipLabel;
19
- /** Solo relevante en modo multiple. Recibe el nuevo array de valores. */
20
- onDeleteChip?: (nextValues: TValue[]) => void;
21
- /** Array de valores actuales (para componer el nuevo array al borrar chips). */
22
- currentValues?: TValue[];
23
- }
24
-
25
- /**
26
- * Render del valor seleccionado del Select.
27
- * - single: texto (label) o lo que devuelva `renderChipLabel`.
28
- * - multiple: chips con delete. Trunca después de `maxChipsToShow` mostrando "+N más".
29
- * - empty: placeholder en color disabled.
30
- */
31
- export function SelectValue<TValue extends SelectOption['value']>(
32
- props: SelectValueProps<TValue>,
33
- ) {
34
- const {
35
- selected,
36
- options,
37
- multiple,
38
- placeholder,
39
- maxChipsToShow,
40
- chipVariant,
41
- renderChipLabel,
42
- onDeleteChip,
43
- currentValues,
44
- } = props;
45
-
46
- const isEmpty =
47
- !selected || (Array.isArray(selected) && selected.length === 0);
48
-
49
- if (isEmpty) {
50
- return (
51
- <Typography sx={{ color: 'text.disabled' }}>{placeholder || ''}</Typography>
52
- );
53
- }
54
-
55
- if (!multiple) {
56
- const item = options.find((opt) => opt.value === selected);
57
- if (renderChipLabel && item) return <>{renderChipLabel(item)}</>;
58
- return <>{item?.label ?? String(selected)}</>;
59
- }
60
-
61
- const selectedValuesArray = selected as TValue[];
62
- const displayedChips = selectedValuesArray.slice(0, maxChipsToShow);
63
- const hiddenChipsCount = selectedValuesArray.length - maxChipsToShow;
64
-
65
- const handleDelete = (chipValue: TValue) => {
66
- if (!onDeleteChip || !currentValues) return;
67
- onDeleteChip(currentValues.filter((v) => v !== chipValue));
68
- };
69
-
70
- return (
71
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
72
- {displayedChips.map((val) => {
73
- const item = options.find((o) => o.value === val);
74
- if (!item) return null;
75
-
76
- return (
77
- <Chip
78
- variant={chipVariant}
79
- color="primary"
80
- size="small"
81
- key={String(val)}
82
- label={renderChipLabel ? renderChipLabel(item) : item.label}
83
- avatar={item.img ? <Avatar src={item.img} /> : undefined}
84
- onDelete={() => handleDelete(val)}
85
- deleteIcon={<ClearIcon fontSize="small" />}
86
- />
87
- );
88
- })}
89
- {hiddenChipsCount > 0 && (
90
- <Chip size="small" variant={chipVariant} label={`+${hiddenChipsCount} más`} />
91
- )}
92
- </Box>
93
- );
94
- }
95
-
96
- export default SelectValue;
@@ -1,14 +0,0 @@
1
- export { Select, Option, default } from './Select';
2
- export type {
3
- SelectProps,
4
- SelectOption,
5
- OptionProps,
6
- BaseSelectProps,
7
- RHFSelectProps,
8
- StandardSelectProps,
9
- RenderOptionItem,
10
- RenderChipLabel,
11
- LabelPosition,
12
- ChipVariant,
13
- SelectSize,
14
- } from './Select.types';