@soyfri/shared-library 1.5.0 → 2.0.0-beta.1

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 (284) hide show
  1. package/build.js +75 -38
  2. package/dist/components/ActionMenu/ActionMenu.cjs +107 -0
  3. package/dist/components/ActionMenu/ActionMenu.cjs.map +1 -0
  4. package/dist/components/ActionMenu/ActionMenu.d.ts +60 -0
  5. package/dist/components/ActionMenu/ActionMenu.js +107 -0
  6. package/dist/components/ActionMenu/ActionMenu.js.map +1 -0
  7. package/dist/components/ActionMenu/index.d.ts +2 -0
  8. package/dist/components/ActionMenu.d.ts +6 -0
  9. package/dist/components/AppBar/AppBar.cjs +346 -0
  10. package/dist/components/AppBar/AppBar.cjs.map +1 -0
  11. package/dist/components/AppBar/AppBar.d.ts +55 -0
  12. package/dist/components/AppBar/AppBar.js +346 -0
  13. package/dist/components/AppBar/AppBar.js.map +1 -0
  14. package/dist/components/AppBar/AppBar.sx.d.ts +12 -0
  15. package/dist/components/AppBar/AppBarBrand.d.ts +31 -0
  16. package/dist/components/AppBar/AppBarContext.d.ts +18 -0
  17. package/dist/components/AppBar/AppBarMenuToggle.d.ts +39 -0
  18. package/dist/components/AppBar/AppBarUserMenu.d.ts +65 -0
  19. package/dist/components/AppBar/index.d.ts +12 -0
  20. package/dist/components/AppBar.d.ts +6 -0
  21. package/dist/components/Autocomplete/Autocomplete.cjs +259 -54
  22. package/dist/components/Autocomplete/Autocomplete.cjs.map +1 -1
  23. package/dist/components/Autocomplete/Autocomplete.d.ts +64 -9
  24. package/dist/components/Autocomplete/Autocomplete.definitions.d.ts +6 -0
  25. package/dist/components/Autocomplete/Autocomplete.helpers.d.ts +18 -0
  26. package/dist/components/Autocomplete/Autocomplete.js +261 -56
  27. package/dist/components/Autocomplete/Autocomplete.js.map +1 -1
  28. package/dist/components/Autocomplete/Autocomplete.sx.d.ts +7 -0
  29. package/dist/components/Autocomplete/Autocomplete.types.d.ts +1 -0
  30. package/dist/components/Autocomplete/_parts/AutocompleteChips.d.ts +19 -0
  31. package/dist/components/Autocomplete/_parts/AutocompleteLoader.d.ts +9 -0
  32. package/dist/components/Autocomplete/_parts/AutocompleteOption.d.ts +19 -0
  33. package/dist/components/Autocomplete/index.d.ts +2 -1
  34. package/dist/components/Autocomplete.d.ts +4 -0
  35. package/dist/components/Avatar/Avatar.cjs +116 -79
  36. package/dist/components/Avatar/Avatar.cjs.map +1 -1
  37. package/dist/components/Avatar/Avatar.d.ts +16 -2
  38. package/dist/components/Avatar/Avatar.definitions.d.ts +11 -0
  39. package/dist/components/Avatar/Avatar.js +117 -80
  40. package/dist/components/Avatar/Avatar.js.map +1 -1
  41. package/dist/components/Card/Card.cjs +168 -9
  42. package/dist/components/Card/Card.cjs.map +1 -1
  43. package/dist/components/Card/Card.d.ts +78 -8
  44. package/dist/components/Card/Card.js +170 -11
  45. package/dist/components/Card/Card.js.map +1 -1
  46. package/dist/components/Card/Card.sx.d.ts +17 -0
  47. package/dist/components/Card/index.d.ts +4 -1
  48. package/dist/components/Card.d.ts +4 -0
  49. package/dist/components/DatePicker/DatePicker.cjs +201 -3
  50. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  51. package/dist/components/DatePicker/DatePicker.d.ts +47 -9
  52. package/dist/components/DatePicker/DatePicker.definitions.d.ts +1 -0
  53. package/dist/components/DatePicker/DatePicker.helpers.d.ts +7 -0
  54. package/dist/components/DatePicker/DatePicker.js +200 -2
  55. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  56. package/dist/components/DatePicker/DatePicker.sx.d.ts +9 -0
  57. package/dist/components/DatePicker/DatePicker.types.d.ts +1 -0
  58. package/dist/components/DatePicker/index.d.ts +2 -1
  59. package/dist/components/DatePicker.d.ts +4 -0
  60. package/dist/components/DateTimePicker/DateTimePicker.cjs +152 -138
  61. package/dist/components/DateTimePicker/DateTimePicker.cjs.map +1 -1
  62. package/dist/components/DateTimePicker/DateTimePicker.d.ts +46 -9
  63. package/dist/components/DateTimePicker/DateTimePicker.definitions.d.ts +1 -0
  64. package/dist/components/DateTimePicker/DateTimePicker.helpers.d.ts +11 -0
  65. package/dist/components/DateTimePicker/DateTimePicker.js +152 -138
  66. package/dist/components/DateTimePicker/DateTimePicker.js.map +1 -1
  67. package/dist/components/DateTimePicker/DateTimePicker.sx.d.ts +7 -0
  68. package/dist/components/DateTimePicker/DateTimePicker.types.d.ts +1 -0
  69. package/dist/components/DateTimePicker/index.d.ts +2 -1
  70. package/dist/components/DateTimePicker.d.ts +4 -0
  71. package/dist/components/Drawer/Drawer.cjs +271 -0
  72. package/dist/components/Drawer/Drawer.cjs.map +1 -0
  73. package/dist/components/Drawer/Drawer.d.ts +51 -0
  74. package/dist/components/Drawer/Drawer.js +271 -0
  75. package/dist/components/Drawer/Drawer.js.map +1 -0
  76. package/dist/components/Drawer/Drawer.sx.d.ts +23 -0
  77. package/dist/components/Drawer/DrawerContext.d.ts +18 -0
  78. package/dist/components/Drawer/DrawerItem.d.ts +35 -0
  79. package/dist/components/Drawer/index.d.ts +6 -0
  80. package/dist/components/Drawer.d.ts +6 -0
  81. package/dist/components/Icon/Icon.cjs +44 -3
  82. package/dist/components/Icon/Icon.cjs.map +1 -1
  83. package/dist/components/Icon/Icon.d.ts +34 -1
  84. package/dist/components/Icon/Icon.js +44 -3
  85. package/dist/components/Icon/Icon.js.map +1 -1
  86. package/dist/components/Input/Input.cjs +173 -3
  87. package/dist/components/Input/Input.cjs.map +1 -1
  88. package/dist/components/Input/Input.d.ts +20 -15
  89. package/dist/components/Input/Input.definitions.d.ts +5 -2
  90. package/dist/components/Input/Input.helpers.d.ts +14 -0
  91. package/dist/components/Input/Input.js +172 -2
  92. package/dist/components/Input/Input.js.map +1 -1
  93. package/dist/components/Input/Input.sx.d.ts +8 -0
  94. package/dist/components/Input/Input.types.d.ts +1 -0
  95. package/dist/components/Input/index.d.ts +2 -1
  96. package/dist/components/Input.d.ts +4 -0
  97. package/dist/components/InputGroup/InputGroup.cjs +104 -91
  98. package/dist/components/InputGroup/InputGroup.cjs.map +1 -1
  99. package/dist/components/InputGroup/InputGroup.d.ts +37 -1
  100. package/dist/components/InputGroup/InputGroup.definitions.d.ts +6 -0
  101. package/dist/components/InputGroup/InputGroup.js +106 -93
  102. package/dist/components/InputGroup/InputGroup.js.map +1 -1
  103. package/dist/components/Modal/Modal.cjs +226 -116
  104. package/dist/components/Modal/Modal.cjs.map +1 -1
  105. package/dist/components/Modal/Modal.d.ts +38 -2
  106. package/dist/components/Modal/Modal.js +227 -117
  107. package/dist/components/Modal/Modal.js.map +1 -1
  108. package/dist/components/Modal/ModalFooter.d.ts +9 -1
  109. package/dist/components/Modal/index.d.ts +5 -0
  110. package/dist/components/PageLoader/PageLoader.cjs +61 -0
  111. package/dist/components/PageLoader/PageLoader.cjs.map +1 -0
  112. package/dist/components/PageLoader/PageLoader.d.ts +38 -0
  113. package/dist/components/PageLoader/PageLoader.js +61 -0
  114. package/dist/components/PageLoader/PageLoader.js.map +1 -0
  115. package/dist/components/PageLoader/index.d.ts +2 -0
  116. package/dist/components/PageLoader.d.ts +6 -0
  117. package/dist/components/ScrollTopButton/ScrollTopButton.cjs +79 -0
  118. package/dist/components/ScrollTopButton/ScrollTopButton.cjs.map +1 -0
  119. package/dist/components/ScrollTopButton/ScrollTopButton.d.ts +48 -0
  120. package/dist/components/ScrollTopButton/ScrollTopButton.js +79 -0
  121. package/dist/components/ScrollTopButton/ScrollTopButton.js.map +1 -0
  122. package/dist/components/ScrollTopButton/index.d.ts +4 -0
  123. package/dist/components/ScrollTopButton/scrollToTop.d.ts +29 -0
  124. package/dist/components/ScrollTopButton.d.ts +6 -0
  125. package/dist/components/Select/Select.cjs +446 -4
  126. package/dist/components/Select/Select.cjs.map +1 -1
  127. package/dist/components/Select/Select.d.ts +33 -13
  128. package/dist/components/Select/Select.definitions.d.ts +3 -0
  129. package/dist/components/Select/Select.helpers.d.ts +28 -0
  130. package/dist/components/Select/Select.js +445 -3
  131. package/dist/components/Select/Select.js.map +1 -1
  132. package/dist/components/Select/Select.sx.d.ts +7 -0
  133. package/dist/components/Select/Select.types.d.ts +1 -0
  134. package/dist/components/Select/_parts/SelectMenuItem.d.ts +20 -0
  135. package/dist/components/Select/_parts/SelectSearchHeader.d.ts +15 -0
  136. package/dist/components/Select/_parts/SelectValue.d.ts +22 -0
  137. package/dist/components/Select/index.d.ts +2 -1
  138. package/dist/components/Select.d.ts +4 -0
  139. package/dist/components/Stat/Stat.cjs +1 -1
  140. package/dist/components/Stat/Stat.js +1 -1
  141. package/dist/components/Stepper/Stepper.cjs +4 -1
  142. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  143. package/dist/components/Stepper/Stepper.d.ts +5 -0
  144. package/dist/components/Stepper/Stepper.js +4 -1
  145. package/dist/components/Stepper/Stepper.js.map +1 -1
  146. package/dist/components/_shared/formField.sx.d.ts +33 -0
  147. package/dist/components/_shared/resolvePreset.d.ts +18 -0
  148. package/dist/formField.sx-CQ1mbk9M.cjs +76 -0
  149. package/dist/formField.sx-CQ1mbk9M.cjs.map +1 -0
  150. package/dist/formField.sx-DfVbMe0V.js +77 -0
  151. package/dist/formField.sx-DfVbMe0V.js.map +1 -0
  152. package/dist/hooks/Wizard/Wizard.cjs +7 -0
  153. package/dist/hooks/Wizard/Wizard.cjs.map +1 -0
  154. package/dist/hooks/Wizard/Wizard.js +7 -0
  155. package/dist/hooks/Wizard/Wizard.js.map +1 -0
  156. package/dist/hooks/Wizard/WizardContext.d.ts +67 -0
  157. package/dist/hooks/Wizard/index.d.ts +3 -0
  158. package/dist/hooks/Wizard/useWizard.d.ts +9 -0
  159. package/dist/hooks/Wizard.d.ts +2 -0
  160. package/dist/index.cjs +99 -1
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.ts +3 -0
  163. package/dist/index.js +31 -2
  164. package/dist/index.js.map +1 -1
  165. package/dist/mui.d.ts +5 -0
  166. package/dist/resolvePreset-B-IB0ehH.js +15 -0
  167. package/dist/resolvePreset-B-IB0ehH.js.map +1 -0
  168. package/dist/resolvePreset-CT3kU-K2.cjs +14 -0
  169. package/dist/resolvePreset-CT3kU-K2.cjs.map +1 -0
  170. package/dist/styles.css +3 -112
  171. package/dist/theme/componentStyles.d.ts +32 -0
  172. package/dist/theme/tokens.d.ts +28 -0
  173. package/dist/useWizard-CWdIxZzX.cjs +94 -0
  174. package/dist/useWizard-CWdIxZzX.cjs.map +1 -0
  175. package/dist/useWizard-CWq--C3o.js +95 -0
  176. package/dist/useWizard-CWq--C3o.js.map +1 -0
  177. package/package.json +1 -1
  178. package/src/components/ActionMenu/ActionMenu.stories.tsx +230 -0
  179. package/src/components/ActionMenu/ActionMenu.tsx +174 -0
  180. package/src/components/ActionMenu/index.ts +2 -0
  181. package/src/components/AppBar/AppBar.stories.tsx +272 -0
  182. package/src/components/AppBar/AppBar.sx.ts +32 -0
  183. package/src/components/AppBar/AppBar.tsx +123 -0
  184. package/src/components/AppBar/AppBarBrand.tsx +120 -0
  185. package/src/components/AppBar/AppBarContext.ts +25 -0
  186. package/src/components/AppBar/AppBarMenuToggle.tsx +90 -0
  187. package/src/components/AppBar/AppBarUserMenu.tsx +217 -0
  188. package/src/components/AppBar/index.ts +25 -0
  189. package/src/components/Autocomplete/Autocomplete.definitions.ts +223 -0
  190. package/src/components/Autocomplete/Autocomplete.helpers.ts +60 -0
  191. package/src/components/Autocomplete/Autocomplete.stories.tsx +363 -2
  192. package/src/components/Autocomplete/Autocomplete.sx.ts +30 -0
  193. package/src/components/Autocomplete/Autocomplete.tsx +312 -90
  194. package/src/components/Autocomplete/Autocomplete.types.ts +13 -0
  195. package/src/components/Autocomplete/_parts/AutocompleteChips.tsx +55 -0
  196. package/src/components/Autocomplete/_parts/AutocompleteLoader.tsx +17 -0
  197. package/src/components/Autocomplete/_parts/AutocompleteOption.tsx +31 -0
  198. package/src/components/Autocomplete/index.ts +12 -1
  199. package/src/components/Avatar/Avatar.definitions.ts +162 -0
  200. package/src/components/Avatar/Avatar.stories.tsx +205 -1
  201. package/src/components/Avatar/Avatar.tsx +166 -103
  202. package/src/components/Card/Card.stories.tsx +205 -16
  203. package/src/components/Card/Card.sx.ts +104 -0
  204. package/src/components/Card/Card.tsx +191 -35
  205. package/src/components/Card/index.ts +9 -1
  206. package/src/components/DatePicker/DatePicker.definitions.ts +24 -1
  207. package/src/components/DatePicker/DatePicker.helpers.ts +24 -0
  208. package/src/components/DatePicker/DatePicker.stories.tsx +29 -2
  209. package/src/components/DatePicker/DatePicker.sx.ts +33 -0
  210. package/src/components/DatePicker/DatePicker.tsx +163 -139
  211. package/src/components/DatePicker/DatePicker.types.ts +10 -0
  212. package/src/components/DatePicker/index.ts +9 -1
  213. package/src/components/DateTimePicker/DateTimePicker.definitions.ts +24 -0
  214. package/src/components/DateTimePicker/DateTimePicker.helpers.ts +38 -0
  215. package/src/components/DateTimePicker/DateTimePicker.stories.tsx +29 -1
  216. package/src/components/DateTimePicker/DateTimePicker.sx.ts +30 -0
  217. package/src/components/DateTimePicker/DateTimePicker.tsx +200 -166
  218. package/src/components/DateTimePicker/DateTimePicker.types.ts +10 -0
  219. package/src/components/DateTimePicker/index.ts +9 -1
  220. package/src/components/Drawer/Drawer.stories.tsx +270 -0
  221. package/src/components/Drawer/Drawer.sx.ts +106 -0
  222. package/src/components/Drawer/Drawer.tsx +214 -0
  223. package/src/components/Drawer/DrawerContext.ts +26 -0
  224. package/src/components/Drawer/DrawerItem.tsx +110 -0
  225. package/src/components/Drawer/index.ts +10 -0
  226. package/src/components/Flyout/Flyout.stories.tsx +26 -18
  227. package/src/components/Icon/Icon.stories.tsx +68 -1
  228. package/src/components/Icon/Icon.tsx +87 -6
  229. package/src/components/Input/Input.definitions.ts +74 -2
  230. package/src/components/Input/Input.helpers.ts +49 -0
  231. package/src/components/Input/Input.stories.tsx +116 -4
  232. package/src/components/Input/Input.sx.ts +42 -0
  233. package/src/components/Input/Input.tsx +117 -162
  234. package/src/components/Input/Input.types.ts +10 -0
  235. package/src/components/Input/index.ts +9 -1
  236. package/src/components/InputGroup/InputGroup.definitions.ts +158 -0
  237. package/src/components/InputGroup/InputGroup.stories.tsx +159 -28
  238. package/src/components/InputGroup/InputGroup.tsx +159 -116
  239. package/src/components/Modal/Modal.stories.tsx +434 -6
  240. package/src/components/Modal/Modal.tsx +303 -121
  241. package/src/components/Modal/ModalFooter.tsx +22 -12
  242. package/src/components/Modal/index.ts +6 -1
  243. package/src/components/PageLoader/PageLoader.stories.tsx +217 -0
  244. package/src/components/PageLoader/PageLoader.tsx +96 -0
  245. package/src/components/PageLoader/index.ts +2 -0
  246. package/src/components/ScrollTopButton/ScrollTopButton.stories.tsx +158 -0
  247. package/src/components/ScrollTopButton/ScrollTopButton.tsx +135 -0
  248. package/src/components/ScrollTopButton/index.ts +8 -0
  249. package/src/components/ScrollTopButton/scrollToTop.ts +37 -0
  250. package/src/components/Select/Select.definitions.ts +114 -0
  251. package/src/components/Select/Select.helpers.ts +71 -0
  252. package/src/components/Select/Select.stories.tsx +126 -8
  253. package/src/components/Select/Select.sx.ts +14 -0
  254. package/src/components/Select/Select.tsx +246 -285
  255. package/src/components/Select/Select.types.ts +15 -0
  256. package/src/components/Select/_parts/SelectMenuItem.tsx +40 -0
  257. package/src/components/Select/_parts/SelectSearchHeader.tsx +51 -0
  258. package/src/components/Select/_parts/SelectValue.tsx +96 -0
  259. package/src/components/Select/index.ts +14 -1
  260. package/src/components/Stepper/Stepper.tsx +17 -1
  261. package/src/components/Tooltip/Tooltip.stories.tsx +15 -3
  262. package/src/components/_shared/formField.sx.ts +118 -0
  263. package/src/components/_shared/resolvePreset.ts +35 -0
  264. package/src/hooks/Wizard/Wizard.stories.tsx +301 -0
  265. package/src/hooks/Wizard/WizardContext.tsx +166 -0
  266. package/src/hooks/Wizard/index.ts +6 -0
  267. package/src/hooks/Wizard/useWizard.ts +13 -0
  268. package/src/index.ts +17 -1
  269. package/src/mui.ts +44 -0
  270. package/src/theme/componentStyles.ts +47 -0
  271. package/src/theme/tokens.ts +43 -0
  272. package/dist/DatePicker-BSNboVhN.js +0 -201
  273. package/dist/DatePicker-BSNboVhN.js.map +0 -1
  274. package/dist/DatePicker-BoqxWAhj.cjs +0 -200
  275. package/dist/DatePicker-BoqxWAhj.cjs.map +0 -1
  276. package/dist/Input-DFHs7cJ_.js +0 -171
  277. package/dist/Input-DFHs7cJ_.js.map +0 -1
  278. package/dist/Input-c8MwNNPg.cjs +0 -170
  279. package/dist/Input-c8MwNNPg.cjs.map +0 -1
  280. package/dist/Select-BO2N56sm.cjs +0 -411
  281. package/dist/Select-BO2N56sm.cjs.map +0 -1
  282. package/dist/Select-BcLkyHSE.js +0 -412
  283. package/dist/Select-BcLkyHSE.js.map +0 -1
  284. package/dist/index.css +0 -3
@@ -1,25 +1,43 @@
1
- import React, { ReactElement, useMemo, useState, Children, isValidElement, useEffect, useRef } from 'react';
1
+ import React, {
2
+ isValidElement,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type ReactElement,
8
+ type ReactNode,
9
+ } from 'react';
2
10
  import {
11
+ Box,
12
+ CircularProgress,
3
13
  FormControl,
14
+ FormHelperText,
4
15
  InputLabel,
16
+ ListSubheader,
5
17
  MenuItem,
6
- Select as MuiSelect,
7
- Chip,
8
- Avatar,
9
- Box,
10
- Typography,
11
18
  OutlinedInput,
12
- TextField,
13
- ListSubheader,
14
- CircularProgress,
15
- ListItemIcon,
19
+ Select as MuiSelect,
16
20
  SelectChangeEvent,
21
+ Typography,
22
+ type SelectProps as MuiSelectProps,
17
23
  } from '@mui/material';
18
- import { styled } from '@mui/system';
19
- import ClearIcon from '@mui/icons-material/Clear';
20
- import CheckIcon from '@mui/icons-material/Check';
21
- import { Controller, Control, RegisterOptions } from 'react-hook-form';
24
+ import { useTheme } from '@mui/material/styles';
25
+ import { Controller, type Control, type RegisterOptions } from 'react-hook-form';
22
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 ─────────────────────────────────────────────────────
23
41
  export interface SelectOption {
24
42
  value: string | number;
25
43
  label: string;
@@ -29,17 +47,27 @@ export interface SelectOption {
29
47
  [key: string]: any;
30
48
  }
31
49
 
32
- type RenderOptionItem = (item: SelectOption) => React.ReactNode;
33
- type RenderChipLabel = (item: SelectOption) => React.ReactNode;
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;
34
57
 
35
- // --- Definición de Props con Union Type ---
58
+ // ── Sub-componente declarativo <Option> ──────────────────────────────────
59
+ export interface OptionProps {
60
+ children: RenderOptionItem;
61
+ }
36
62
 
37
- // Props comunes para ambos modos
38
- interface BaseSelectProps<TValue extends SelectOption['value'] = SelectOption['value']> {
63
+ // ── Props base ───────────────────────────────────────────────────────────
64
+ export interface BaseSelectProps<
65
+ TValue extends SelectOption['value'] = SelectOption['value'],
66
+ > {
39
67
  label?: string;
40
68
  options?: SelectOption[];
41
69
  defaultValue?: TValue | TValue[];
42
- size?: 'small' | 'medium';
70
+ size?: SelectSize;
43
71
  multiple?: boolean;
44
72
  filterable?: boolean;
45
73
  placeholder?: string;
@@ -48,18 +76,36 @@ interface BaseSelectProps<TValue extends SelectOption['value'] = SelectOption['v
48
76
  maxWidth?: number | string;
49
77
  maxChipsToShow?: number;
50
78
  renderChipLabel?: RenderChipLabel;
51
- chipVariant?: 'outlined' | 'filled';
79
+ chipVariant?: ChipVariant;
52
80
  loadOptions?: (inputValue: string) => Promise<SelectOption[]>;
53
- loadingMessage?: React.ReactNode;
54
- noOptionsMessage?: React.ReactNode;
81
+ loadingMessage?: ReactNode;
82
+ noOptionsMessage?: ReactNode;
55
83
  debounceTimeout?: number;
56
84
  disabled?: boolean;
57
85
  error?: boolean;
58
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;
59
103
  }
60
104
 
61
- // Props para el modo React Hook Form
62
- interface RHFSelectProps<TValue extends SelectOption['value'] = SelectOption['value']> extends BaseSelectProps<TValue> {
105
+ // ── Variantes discriminadas (RHF vs controlado) ──────────────────────────
106
+ export interface RHFSelectProps<
107
+ TValue extends SelectOption['value'] = SelectOption['value'],
108
+ > extends BaseSelectProps<TValue> {
63
109
  name: string;
64
110
  control: Control<any>;
65
111
  validation?: RegisterOptions;
@@ -67,8 +113,9 @@ interface RHFSelectProps<TValue extends SelectOption['value'] = SelectOption['va
67
113
  onChange?: (val: TValue | TValue[]) => void;
68
114
  }
69
115
 
70
- // Props para el modo controlado estándar
71
- interface StandardSelectProps<TValue extends SelectOption['value'] = SelectOption['value']> extends BaseSelectProps<TValue> {
116
+ export interface StandardSelectProps<
117
+ TValue extends SelectOption['value'] = SelectOption['value'],
118
+ > extends BaseSelectProps<TValue> {
72
119
  name?: string;
73
120
  control?: never;
74
121
  validation?: never;
@@ -76,98 +123,28 @@ interface StandardSelectProps<TValue extends SelectOption['value'] = SelectOptio
76
123
  onChange: (val: TValue | TValue[]) => void;
77
124
  }
78
125
 
79
- export type SelectProps<TValue extends SelectOption['value'] = SelectOption['value']> = RHFSelectProps<TValue> | StandardSelectProps<TValue>;
80
-
81
- export interface OptionProps {
82
- children: RenderOptionItem;
83
- }
126
+ // ── API pública final ────────────────────────────────────────────────────
127
+ export type SelectProps<
128
+ TValue extends SelectOption['value'] = SelectOption['value'],
129
+ > = RHFSelectProps<TValue> | StandardSelectProps<TValue>;
84
130
 
131
+ // ── Sub-componente declarativo <Option> ──────────────────────────────────
85
132
  export function Option(_props: OptionProps) {
86
133
  return null;
87
134
  }
88
135
  Option.displayName = 'Option';
89
136
 
90
- const StyledSelect = styled(MuiSelect)(({ theme }) => ({
91
- '& .MuiInputBase-root': {
92
- maxHeight: '4px',
93
- padding: '8px 10px',
94
- borderRadius: '10px',
95
- display: 'flex',
96
- alignItems: 'center',
97
- fontSize: '14px',
98
- },
99
- '& .MuiSelect-select': {
100
- padding: '0 !important',
101
- height: '28px !important',
102
- display: 'flex',
103
- alignItems: 'center',
104
- marginLeft: '8px'
105
- },
106
- '& .MuiOutlinedInput-notchedOutline': {
107
- marginBottom: '-4px !important',
108
- borderRadius: '10px',
109
- },
110
- '& .MuiSelect-icon': {
111
- top: '50%',
112
- transform: 'translateY(-50%)',
113
- },
114
- '& .MuiOutlinedInput-notchedOutline > legend':{
115
- display: 'none',
116
- },
117
- }));
118
-
119
- const StyledLabel = styled(InputLabel)(({theme}) =>({
120
- '&.MuiInputLabel-root': {
121
- top: '50%',
122
- transform: 'translate(14px, -50%)',
123
- },
124
- '&.MuiInputLabel-shrink': {
125
- transform: 'translate(1px, -200%) scale(0.75)',
126
- fontSize: '16px',
127
- '> legend': {
128
- display: 'none',
129
- }
130
- },
131
- '&.MuiInputLabel-animated': {
132
- '&.MuiInputLabel-shrink': {
133
- display: 'block'
134
- }
135
- }
136
- }));
137
-
138
- const StyledOutlinedInput = styled(OutlinedInput)(({ theme }) => ({
139
- '& .MuiInputBase-root': {
140
- maxHeight: '34px',
141
- padding: '8px 10px',
142
- borderRadius: '10px',
143
- display: 'flex',
144
- alignItems: 'center',
145
- fontSize: '14px',
146
- ' & > .MuiAvatar-img': {
147
- width: '14px',
148
- height: '14px',
149
- }
150
- },
151
- '& .MuiInputBase-input': {
152
- padding: '0 !important',
153
- height: '18px',
154
- display: 'flex',
155
- alignItems: 'center',
156
- },
157
- '& .MuiOutlinedInput-notchedOutline': {
158
- border: '0.7px solid',
159
- borderColor: '#e0e0e0',
160
- },
161
- }));
162
-
163
- export function Select<TValue extends SelectOption['value'] = SelectOption['value']>(props: SelectProps<TValue>) {
137
+ // ── Componente ───────────────────────────────────────────────────────────
138
+ export function Select<TValue extends SelectOption['value'] = SelectOption['value']>(
139
+ props: SelectProps<TValue>,
140
+ ) {
164
141
  const {
165
142
  label,
166
143
  options = [],
167
144
  value,
168
145
  onChange,
169
146
  defaultValue,
170
- size = 'medium',
147
+ size = 'small',
171
148
  multiple = false,
172
149
  filterable = false,
173
150
  placeholder,
@@ -184,11 +161,20 @@ export function Select<TValue extends SelectOption['value'] = SelectOption['valu
184
161
  disabled = false,
185
162
  error = false,
186
163
  helperText,
164
+ borderRadius = 10,
165
+ labelPosition = 'outside',
166
+ preset,
167
+ sx,
168
+ className,
187
169
  ...rest
188
170
  } = props;
189
171
 
190
- const isRHFMode = 'control' in rest && rest.control !== undefined;
172
+ const theme = useTheme();
173
+ const presetSx = resolvePreset('Select', preset, theme);
174
+
175
+ const isRHFMode = 'control' in rest && (rest as any).control !== undefined;
191
176
 
177
+ // Custom render opcional vía <Option>{item => ...}</Option>
192
178
  const customRender: RenderOptionItem | null = useMemo(() => {
193
179
  if (React.Children.count(children) === 1) {
194
180
  const child = React.Children.only(children);
@@ -199,153 +185,67 @@ export function Select<TValue extends SelectOption['value'] = SelectOption['valu
199
185
  return null;
200
186
  }, [children]);
201
187
 
188
+ // Búsqueda / async
202
189
  const [search, setSearch] = useState('');
203
190
  const [asyncOptions, setAsyncOptions] = useState<SelectOption[]>([]);
204
191
  const [loading, setLoading] = useState(false);
205
192
  const debounceTimerRef = useRef<number | null>(null);
206
-
207
193
  const searchInputRef = useRef<HTMLInputElement>(null);
208
194
 
195
+ // Focus/open tracking para el comportamiento del label "outside".
196
+ const [isOpen, setIsOpen] = useState(false);
197
+ const [isFocused, setIsFocused] = useState(false);
198
+
209
199
  const isAsync = typeof loadOptions === 'function';
210
200
  const currentOptions = isAsync ? asyncOptions : options;
211
201
 
212
202
  useEffect(() => {
213
- if (isAsync) {
214
- if (debounceTimerRef.current) {
215
- clearTimeout(debounceTimerRef.current);
216
- }
217
-
218
- if (search) {
219
- setLoading(true);
220
- debounceTimerRef.current = window.setTimeout(() => {
221
- loadOptions(search)
222
- .then(loadedOpts => {
223
- setAsyncOptions(loadedOpts);
224
- })
225
- .finally(() => {
226
- setLoading(false);
227
- if (searchInputRef.current) {
228
- searchInputRef.current.focus();
229
- }
230
- });
231
- }, debounceTimeout);
232
- } else {
233
- setAsyncOptions([]);
234
- setLoading(false);
235
- if (searchInputRef.current) {
236
- searchInputRef.current.focus();
237
- }
238
- }
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();
239
221
  }
240
222
  }, [search, isAsync, loadOptions, debounceTimeout]);
241
223
 
242
224
  const filteredOptions = useMemo(() => {
243
- if (isAsync) {
244
- return currentOptions;
245
- }
246
- if (!filterable || !search) return currentOptions;
247
- return currentOptions.filter(opt =>
248
- opt.label.toLowerCase().includes(search.toLowerCase())
249
- );
225
+ if (isAsync) return currentOptions;
226
+ if (!filterable) return currentOptions;
227
+ return filterOptionsByLabel(currentOptions, search);
250
228
  }, [search, filterable, currentOptions, isAsync]);
251
229
 
252
- const groupedOptions = useMemo(() => {
253
- const groups: Record<string, SelectOption[]> = {};
254
- filteredOptions.forEach(opt => {
255
- const group = opt.group || '__default';
256
- if (!groups[group]) groups[group] = [];
257
- groups[group].push(opt);
258
- });
259
- return groups;
260
- }, [filteredOptions]);
261
-
262
- const renderValue = (selected: any) => {
263
- if (!selected || (Array.isArray(selected) && selected.length === 0)) {
264
- return <Typography sx={{ color: 'text.disabled' }}>{placeholder || ''}</Typography>;
265
- }
266
-
267
- if (!multiple) {
268
- const item = currentOptions.find((opt) => opt.value === selected);
269
- return renderChipLabel && item ? renderChipLabel(item) : (item?.label ?? String(selected));
270
- }
271
-
272
- const selectedValuesArray = selected as TValue[];
273
- const displayedChips = selectedValuesArray.slice(0, maxChipsToShow);
274
- const hiddenChipsCount = selectedValuesArray.length - maxChipsToShow;
275
-
276
- return (
277
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
278
- {displayedChips.map((val) => {
279
- const item = currentOptions.find((o) => o.value === val);
280
- if (!item) return null;
281
-
282
- const handleDelete = (chipValue: TValue) => {
283
- const newSelected = (value as TValue[]).filter(v => v !== chipValue);
284
- (onChange as (val: TValue | TValue[]) => void)?.(newSelected);
285
- };
286
-
287
- return (
288
- <Chip
289
- variant={chipVariant}
290
- color="primary"
291
- key={val}
292
- label={renderChipLabel ? renderChipLabel(item) : item.label}
293
- avatar={item.img ?
294
- <Avatar
295
- sx={
296
- {
297
- '& > .MuiAvatar-root, .MuiChip-avatar': {
298
- backgroundColor: 'red',
299
- }
300
- }
301
- }
302
- src={item.img}
303
- /> : undefined}
304
- onDelete={() => handleDelete(val)}
305
- deleteIcon={<ClearIcon sx={{ fontSize: '12px !important' }} />}
306
- sx={
307
- {
308
- height: '27px',
309
- marginTop: '1px',
310
- }
311
- }
312
- />
313
- );
314
- })}
315
- {hiddenChipsCount > 0 && (
316
- <Chip
317
- sx={{
318
- height: '27px',
319
- marginTop: '0px',
320
- }}
321
- label={`+${hiddenChipsCount} más`} variant={chipVariant} />
322
- )}
323
- </Box>
324
- );
325
- };
230
+ const groupedOptions = useMemo(() => groupOptions(filteredOptions), [filteredOptions]);
326
231
 
232
+ // Items del menu (search header + loading/empty + grupos + opciones)
327
233
  const menuItems = useMemo(() => {
328
234
  const items: React.ReactNode[] = [];
329
235
 
330
236
  if (filterable || isAsync) {
331
237
  items.push(
332
- <ListSubheader key="search-header" sx={{ px: 2, pt: 1, pb: 1, backgroundColor: 'background.paper', zIndex: 1, position: 'sticky', top: 0 }}>
333
- <TextField
334
- inputRef={searchInputRef}
335
- placeholder="Buscar..."
336
- variant="standard"
337
- size="small"
338
- fullWidth
339
- value={search}
340
- onChange={(e) => setSearch(e.target.value)}
341
- inputProps={{ autoFocus: true }}
342
- onClick={(e) => e.stopPropagation()}
343
- onKeyDown={(e) => e.stopPropagation()}
344
- />
345
- </ListSubheader>
238
+ <SelectSearchHeader
239
+ key="search-header"
240
+ value={search}
241
+ onChange={setSearch}
242
+ inputRef={searchInputRef}
243
+ />,
346
244
  );
347
245
  }
348
246
 
247
+ const noOptions = isGroupedOptionsEmpty(groupedOptions);
248
+
349
249
  if (loading) {
350
250
  items.push(
351
251
  <MenuItem key="loading-message" disabled>
@@ -353,46 +253,57 @@ export function Select<TValue extends SelectOption['value'] = SelectOption['valu
353
253
  <CircularProgress size={20} />
354
254
  <Typography>{loadingMessage}</Typography>
355
255
  </Box>
356
- </MenuItem>
256
+ </MenuItem>,
357
257
  );
358
- } else if (Object.keys(groupedOptions).length === 0 || (Object.keys(groupedOptions).length === 1 && groupedOptions['__default'] && groupedOptions['__default'].length === 0)) {
258
+ } else if (noOptions) {
359
259
  items.push(
360
260
  <MenuItem key="no-options-message" disabled>
361
261
  <Typography>{noOptionsMessage}</Typography>
362
- </MenuItem>
262
+ </MenuItem>,
363
263
  );
364
264
  } else {
365
265
  Object.entries(groupedOptions).forEach(([group, opts]) => {
366
266
  if (group !== '__default') {
367
267
  items.push(
368
- <ListSubheader key={group} disableSticky>{group}</ListSubheader>
268
+ <ListSubheader key={group} disableSticky>
269
+ {group}
270
+ </ListSubheader>,
369
271
  );
370
272
  }
371
273
  opts.forEach((opt) => {
372
274
  const isSelected = multiple
373
- ? (value as TValue[] | undefined)?.includes(opt.value as TValue)
275
+ ? (value as TValue[] | undefined)?.includes(opt.value as TValue) ?? false
374
276
  : value === opt.value;
375
277
 
376
278
  items.push(
377
- <MenuItem key={opt.value} value={opt.value} disabled={opt.disabled} selected={isSelected}>
378
- <ListItemIcon sx={{ minWidth: 32 }}>
379
- {isSelected && (
380
- <CheckIcon sx={{ color: 'green' }} />
381
- )}
382
- </ListItemIcon>
383
- {customRender ? customRender(opt) : opt.label}
384
- </MenuItem>
279
+ renderSelectMenuItem({
280
+ option: opt,
281
+ selected: isSelected,
282
+ customRender,
283
+ }),
385
284
  );
386
285
  });
387
286
  });
388
287
  }
389
288
  return items;
390
- }, [groupedOptions, customRender, filterable, isAsync, search, loading, loadingMessage, noOptionsMessage, multiple, value]);
391
-
392
- const showLabel = label && !placeholder;
289
+ }, [
290
+ groupedOptions,
291
+ customRender,
292
+ filterable,
293
+ isAsync,
294
+ search,
295
+ loading,
296
+ loadingMessage,
297
+ noOptionsMessage,
298
+ multiple,
299
+ value,
300
+ ]);
393
301
 
394
- // Lógica para manejar el cambio de valor
395
- const handleChangeInternal = (event: SelectChangeEvent<any>, rhfOnChange?: (...event: any[]) => void) => {
302
+ // Handler de cambio
303
+ const handleChangeInternal = (
304
+ event: SelectChangeEvent<any>,
305
+ rhfOnChange?: (...event: any[]) => void,
306
+ ) => {
396
307
  const newValue = event.target.value;
397
308
  if (isRHFMode) {
398
309
  rhfOnChange?.(newValue);
@@ -401,68 +312,118 @@ export function Select<TValue extends SelectOption['value'] = SelectOption['valu
401
312
  }
402
313
  };
403
314
 
404
- // Renderizado condicional basado en el modo
405
- const isRHFModeRender = 'control' in rest && rest.control !== undefined;
406
-
407
- const renderSelect = (selectValue: any, selectOnChange: any, onBlur?: any, inputRef?: any) => (
408
- <FormControl fullWidth size={size} error={error} disabled={disabled}>
409
- {showLabel && <StyledLabel>{label}</StyledLabel>}
410
- <Box
411
- sx={{
412
- width: '100%',
413
- display: 'grid',
414
- marginBottom: '10px',
415
- marginTop: '10px'
416
- }}>
417
- <StyledSelect
418
- label={label}
419
- value={selectValue ?? (multiple ? [] : '')}
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}
420
361
  defaultValue={defaultValue}
421
362
  multiple={multiple}
422
363
  onChange={selectOnChange}
423
- onBlur={onBlur}
424
- renderValue={renderValue}
425
- displayEmpty
426
- input={<StyledOutlinedInput label={label} />}
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}
427
388
  disabled={disabled}
428
389
  MenuProps={{
429
390
  PaperProps: {
430
- style: {
431
- maxHeight: maxHeight,
432
- maxWidth: maxWidth,
433
- },
391
+ style: { maxHeight, maxWidth },
434
392
  },
435
393
  }}
436
394
  inputRef={inputRef}
437
- {...rest}
395
+ {...(rest as any)}
438
396
  >
439
397
  {menuItems}
440
- </StyledSelect>
441
- {helperText && <Typography variant="caption" color="error" sx={{ mt: 1 }}>{helperText}</Typography>}
442
- </Box>
443
- </FormControl>
444
- );
398
+ </MuiSelect>
399
+ {finalHelperText && <FormHelperText>{finalHelperText}</FormHelperText>}
400
+ </FormControl>
401
+ );
402
+ };
445
403
 
446
- if (isRHFModeRender) {
404
+ // Branch: RHF vs controlado
405
+ if (isRHFMode) {
447
406
  const rhfProps = rest as RHFSelectProps<TValue>;
448
407
  return (
449
408
  <Controller
450
409
  name={rhfProps.name}
451
410
  control={rhfProps.control}
452
411
  rules={rhfProps.validation}
453
- render={({ field, fieldState: { error } }) => (
412
+ render={({ field, fieldState: { error: fieldError } }) =>
454
413
  renderSelect(
455
414
  field.value,
456
415
  (e: SelectChangeEvent<any>) => handleChangeInternal(e, field.onChange),
457
416
  field.onBlur,
458
- field.ref
417
+ field.ref,
418
+ !!fieldError,
419
+ fieldError?.message,
459
420
  )
460
- )}
421
+ }
461
422
  />
462
423
  );
463
- } else {
464
- return renderSelect(value, (e: SelectChangeEvent<any>) => handleChangeInternal(e));
465
424
  }
425
+
426
+ return renderSelect(value, (e: SelectChangeEvent<any>) => handleChangeInternal(e));
466
427
  }
467
428
 
468
- export default Select;
429
+ export default Select;