@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,6 +1,13 @@
1
- import { Avatar as MuiAvatar, Tooltip } from '@mui/material';
2
- import AccountCircleIcon from '@mui/icons-material/AccountCircle';
3
1
  import React, { useState } from 'react';
2
+ import {
3
+ Avatar as MuiAvatar,
4
+ Box,
5
+ Tooltip,
6
+ Typography,
7
+ type SxProps,
8
+ type Theme,
9
+ } from '@mui/material';
10
+ import AccountCircleIcon from '@mui/icons-material/AccountCircle';
4
11
 
5
12
  export interface AvatarItem {
6
13
  text?: string;
@@ -10,27 +17,59 @@ export interface AvatarItem {
10
17
  backgroundColor?: string;
11
18
  }
12
19
 
20
+ export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl' | number;
21
+
13
22
  export interface AvatarProps {
14
23
  type?: string;
15
24
  items: AvatarItem[];
16
25
  displayedAvatars?: number;
17
- size?: 'sm' | 'xl';
18
- trustUrls?: boolean;
26
+ size?: AvatarSize;
19
27
  showText?: boolean;
20
28
  showTooltip?: boolean;
29
+ /**
30
+ * sx aplicado al contenedor raíz.
31
+ */
32
+ sx?: SxProps<Theme>;
33
+ /**
34
+ * sx aplicado a cada MuiAvatar individual (se mergea sobre los defaults).
35
+ */
36
+ avatarSx?: SxProps<Theme>;
37
+ className?: string;
38
+ /**
39
+ * Overlap (px) entre avatares cuando hay varios. Default depende del tamaño.
40
+ */
41
+ overlap?: number;
21
42
  }
22
43
 
23
- const sizes = {
24
- sm: {
25
- avatar: { width: 35, height: 35, borderWidth: 3, marginTop: '.5rem' },
26
- icon: { width: 42, height: 42, marginTop: '.5rem', fontSize: 42 },
27
- badge: { width: 35, height: 35, borderWidth: 3, marginTop: '.5rem', fontSize: 16 },
28
- },
29
- xl: {
30
- avatar: { width: 105, height: 105, borderWidth: 10, marginTop: '0' },
31
- icon: { width: 126, height: 126, marginTop: '0', fontSize: 126 },
32
- badge: { width: 105, height: 105, borderWidth: 10, marginTop: '0', fontSize: 64 },
33
- },
44
+ // Escala alineada con la escala de MUI (sm=32, md=40, lg=56, xl=96) con borde
45
+ // proporcional para el efecto stacked.
46
+ const sizeMap: Record<
47
+ Exclude<AvatarSize, number>,
48
+ { px: number; border: number; font: number; overlap: number }
49
+ > = {
50
+ sm: { px: 32, border: 2, font: 14, overlap: 8 },
51
+ md: { px: 40, border: 2, font: 16, overlap: 10 },
52
+ lg: { px: 56, border: 3, font: 22, overlap: 14 },
53
+ xl: { px: 96, border: 4, font: 36, overlap: 20 },
54
+ };
55
+
56
+ const resolveSize = (size: AvatarSize) => {
57
+ if (typeof size === 'number') {
58
+ return {
59
+ px: size,
60
+ border: Math.max(2, Math.round(size * 0.05)),
61
+ font: Math.round(size * 0.4),
62
+ overlap: Math.round(size * 0.25),
63
+ };
64
+ }
65
+ return sizeMap[size];
66
+ };
67
+
68
+ const mergeSx = (base: SxProps<Theme>, extra?: SxProps<Theme>): SxProps<Theme> => {
69
+ if (!extra) return base;
70
+ const baseArr = Array.isArray(base) ? base : [base];
71
+ const extraArr = Array.isArray(extra) ? extra : [extra];
72
+ return [...baseArr, ...extraArr] as SxProps<Theme>;
34
73
  };
35
74
 
36
75
  export const Avatar: React.FC<AvatarProps> = ({
@@ -38,106 +77,130 @@ export const Avatar: React.FC<AvatarProps> = ({
38
77
  type,
39
78
  displayedAvatars = 4,
40
79
  size = 'sm',
41
- trustUrls = false,
42
80
  showText = true,
43
81
  showTooltip = false,
82
+ sx,
83
+ avatarSx,
84
+ className,
85
+ overlap,
44
86
  }) => {
45
87
  const [errorIndex, setErrorIndex] = useState<Set<number>>(new Set());
46
88
 
47
89
  const handleImageError = (index: number) => {
48
- setErrorIndex((prev) => new Set(prev).add(index));
90
+ setErrorIndex((prev) => {
91
+ const next = new Set(prev);
92
+ next.add(index);
93
+ return next;
94
+ });
49
95
  };
50
96
 
51
- return (
52
- <div data-testid="avatar-container" className="flex items-center leading-2 h-full w-full">
53
- {items.slice(0, displayedAvatars).map((item, i) => {
54
- const commonStyle = {
55
- zIndex: displayedAvatars - i,
56
- marginLeft: i > 0 ? -22 : 0,
57
- };
58
-
59
- const style = sizes[size];
60
-
61
- const imgSrc = trustUrls ? item.imageUrl : item.imageUrl;
62
-
63
- const showFallbackIcon = (!item.imageUrl && !item.badge) || (errorIndex.has(i) && !item.badge);
64
- const showBadge = (item.badge && !item.imageUrl) || (item.badge && errorIndex.has(i));
65
-
66
- const content = (
67
- <span key={i} data-testid="avatar" style={commonStyle}>
68
- {item.imageUrl && !errorIndex.has(i) ? (
69
- <img
70
- data-testid="image"
71
- alt={item.text || 'User avatar'}
72
- src={imgSrc}
73
- onError={() => handleImageError(i)}
74
- style={{
75
- ...style.avatar,
76
- color: item.color || '#667688',
77
- border: `${style.avatar.borderWidth}px solid white`,
78
- borderRadius: '9999px',
79
- marginTop: style.avatar.marginTop,
80
- }}
81
- />
82
- ) : showFallbackIcon ? (
83
- <AccountCircleIcon
84
- data-testid="icon"
85
- aria-label={item.text}
86
- sx={{
87
- ...style.icon,
88
- fontSize: style.icon.fontSize,
89
- marginTop: style.icon.marginTop,
90
- borderRadius: '9999px',
91
- backgroundColor: '#fff',
92
- color: item.color || '#667688',
93
- }}
94
- />
95
- ) : null}
96
-
97
- {showBadge && (
98
- <span
99
- data-testid="badge"
100
- aria-label={item.text}
101
- style={{
102
- ...style.badge,
103
- display: 'inline-block',
104
- backgroundColor: item.backgroundColor || '#c8d5e9',
105
- color: item.color || '#667688',
106
- fontWeight: 700,
107
- textAlign: 'center',
108
- borderRadius: '9999px',
109
- border: `${style.badge.borderWidth}px solid white`,
110
- lineHeight: style.badge.height,
111
- fontSize: style.badge.fontSize,
112
- marginTop: style.badge.marginTop,
113
- marginBottom: style.badge.marginTop,
114
- }}
115
- >
116
- {item.badge}
117
- </span>
118
- )}
97
+ if (!items || items.length === 0) {
98
+ return null;
99
+ }
100
+
101
+ const s = resolveSize(size);
102
+ const effectiveOverlap = overlap ?? s.overlap;
103
+ const visibleItems = items.slice(0, displayedAvatars);
104
+
105
+ const baseAvatarSx: SxProps<Theme> = {
106
+ width: s.px,
107
+ height: s.px,
108
+ fontSize: s.font,
109
+ fontWeight: 700,
110
+ border: (theme) => `${s.border}px solid ${theme.palette.background.paper}`,
111
+ boxSizing: 'content-box',
112
+ };
113
+
114
+ const renderSingleAvatar = (item: AvatarItem, i: number) => {
115
+ const hasImage = !!item.imageUrl && !errorIndex.has(i);
116
+ const showBadgeFallback = !!item.badge;
117
+
118
+ // Defaults tirando al theme; item.color / item.backgroundColor tienen prioridad.
119
+ const itemSx: SxProps<Theme> = {
120
+ bgcolor: item.backgroundColor ?? 'action.selected',
121
+ color: item.color ?? 'text.secondary',
122
+ // Stacking manual: margen negativo al segundo avatar en adelante.
123
+ ...(i > 0 && { marginLeft: `-${effectiveOverlap}px` }),
124
+ zIndex: visibleItems.length - i,
125
+ };
126
+
127
+ const finalSx = mergeSx(mergeSx(baseAvatarSx, itemSx), avatarSx);
128
+
129
+ const avatarEl = (
130
+ <MuiAvatar
131
+ data-testid="avatar"
132
+ alt={item.text || 'User avatar'}
133
+ src={hasImage ? item.imageUrl : undefined}
134
+ imgProps={{
135
+ 'data-testid': 'image',
136
+ onError: () => handleImageError(i),
137
+ } as React.ImgHTMLAttributes<HTMLImageElement>}
138
+ sx={finalSx}
139
+ >
140
+ {!hasImage && showBadgeFallback ? (
141
+ <span data-testid="badge" aria-label={item.text}>
142
+ {item.badge}
119
143
  </span>
120
- );
121
-
122
- return showTooltip && item.text ? (
123
- <Tooltip key={i} title={item.text}>
124
- {content}
125
- </Tooltip>
126
- ) : (
127
- content
128
- );
129
- })}
130
-
131
- {showText && (
132
- <span data-testid="text" className="ml-2 text-xs font-normal">
144
+ ) : !hasImage ? (
145
+ <AccountCircleIcon
146
+ data-testid="icon"
147
+ aria-label={item.text}
148
+ sx={{ width: '100%', height: '100%' }}
149
+ />
150
+ ) : null}
151
+ </MuiAvatar>
152
+ );
153
+
154
+ if (showTooltip && item.text) {
155
+ return (
156
+ <Tooltip key={i} title={item.text}>
157
+ {avatarEl}
158
+ </Tooltip>
159
+ );
160
+ }
161
+ return <React.Fragment key={i}>{avatarEl}</React.Fragment>;
162
+ };
163
+
164
+ return (
165
+ <Box
166
+ data-testid="avatar-container"
167
+ className={className}
168
+ sx={mergeSx(
169
+ {
170
+ display: 'flex',
171
+ alignItems: 'center',
172
+ lineHeight: 1,
173
+ width: 'fit-content',
174
+ },
175
+ sx,
176
+ )}
177
+ >
178
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
179
+ {visibleItems.map((item, i) => renderSingleAvatar(item, i))}
180
+ </Box>
181
+
182
+ {showText && items[0]?.text && (
183
+ <Typography
184
+ data-testid="text"
185
+ variant="caption"
186
+ sx={{
187
+ ml: 1,
188
+ fontSize: '0.75rem',
189
+ fontWeight: 400,
190
+ color: 'text.primary',
191
+ }}
192
+ >
133
193
  {type && items.length === 1 && (
134
- <span data-testid="type">{type}: </span>
194
+ <Box component="span" data-testid="type" sx={{ mr: 0.5 }}>
195
+ {type}:
196
+ </Box>
135
197
  )}
136
- {items[0].text} {items.length > 2 && `+${items.length - 1}`}
137
- </span>
198
+ {items[0].text}
199
+ {items.length > 1 && ` +${items.length - 1}`}
200
+ </Typography>
138
201
  )}
139
- </div>
202
+ </Box>
140
203
  );
141
204
  };
142
205
 
143
- export default Avatar;
206
+ export default Avatar;
@@ -1,32 +1,221 @@
1
- import { DefaultCardDefinition } from "./Card.definition";
2
- import Card from "./Card";
3
- import type { Meta, StoryObj } from "@storybook/react";
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Box, Button, Chip, Stack, Typography } from '@mui/material';
4
+ import EditIcon from '@mui/icons-material/Edit';
5
+ import DeleteIcon from '@mui/icons-material/Delete';
6
+ import PersonIcon from '@mui/icons-material/Person';
7
+
8
+ import { Card } from './Card';
9
+ import { ActionMenu } from '../ActionMenu';
4
10
 
5
11
  const meta: Meta<typeof Card> = {
6
- title: "Components/Card",
12
+ title: 'Components/Card',
7
13
  component: Card,
14
+ tags: ['autodocs'],
8
15
  parameters: {
9
- layout: "centered"
16
+ layout: 'padded',
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'Contenedor semántico con slots `title` / `subtitle` / `actions` / `footer`. Reemplaza el patrón `.card` + `.card-header` + `.card-body` de Metronic con composición declarativa. Backward-compat: si solo se pasa `children`, se comporta como el Card original.',
21
+ },
22
+ },
23
+ },
24
+ argTypes: {
25
+ variant: {
26
+ control: 'select',
27
+ options: ['elevated', 'outlined', 'plain'],
28
+ },
29
+ padding: {
30
+ control: 'select',
31
+ options: ['none', 'dense', 'normal', 'loose'],
32
+ },
33
+ clickable: { control: 'boolean' },
10
34
  },
11
- tags: ["autodocs"]
12
35
  };
13
36
 
14
37
  export default meta;
15
-
16
38
  type Story = StoryObj<typeof Card>;
17
39
 
18
- export const Default: Story = {
40
+ // ── Stories ──────────────────────────────────────────────────────────────
41
+
42
+ export const LegacyBodyOnly: Story = {
19
43
  args: {
20
- children: "Card",
44
+ children: 'Contenido simple del card — sin header ni footer.',
21
45
  },
22
46
  parameters: {
23
47
  docs: {
24
48
  description: {
25
- story: "Tarjeta con el texto \"Card\" y el estilo \"default\"."
49
+ story:
50
+ 'Modo backward-compat: pasando solo `children` el Card se comporta como el original (body con padding, sombra sutil).',
26
51
  },
27
- source: {
28
- code: DefaultCardDefinition.trim()
29
- }
30
- }
31
- }
32
- };
52
+ },
53
+ },
54
+ };
55
+
56
+ export const WithTitleAndSubtitle: Story = {
57
+ args: {
58
+ title: 'Datos del cliente',
59
+ subtitle: 'Última actualización: hace 2 horas',
60
+ children: (
61
+ <Typography variant="body2" color="text.secondary">
62
+ Aquí va el contenido del card, por ejemplo un formulario o una tabla.
63
+ </Typography>
64
+ ),
65
+ },
66
+ };
67
+
68
+ export const WithActions: Story = {
69
+ args: {
70
+ title: 'Afiliación #12458',
71
+ subtitle: 'Aprobada el 10 abril 2026',
72
+ actions: (
73
+ <ActionMenu
74
+ items={[
75
+ { label: 'Editar', icon: <EditIcon fontSize="small" />, onClick: () => {} },
76
+ {
77
+ label: 'Eliminar',
78
+ icon: <DeleteIcon fontSize="small" />,
79
+ onClick: () => {},
80
+ danger: true,
81
+ dividerBefore: true,
82
+ },
83
+ ]}
84
+ />
85
+ ),
86
+ children: (
87
+ <Stack direction="row" spacing={2} alignItems="center">
88
+ <PersonIcon color="action" />
89
+ <Box>
90
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
91
+ Andrea Pineda
92
+ </Typography>
93
+ <Typography variant="caption" color="text.secondary">
94
+ andrea@soyfri.com
95
+ </Typography>
96
+ </Box>
97
+ <Chip label="Activo" color="success" size="small" sx={{ ml: 'auto' }} />
98
+ </Stack>
99
+ ),
100
+ },
101
+ };
102
+
103
+ export const WithFooter: Story = {
104
+ args: {
105
+ title: 'Nueva afiliación',
106
+ children: (
107
+ <Stack spacing={1}>
108
+ <Typography variant="body2">
109
+ Confirma los datos antes de enviar la solicitud.
110
+ </Typography>
111
+ <Typography variant="caption" color="text.secondary">
112
+ Campos marcados con * son obligatorios.
113
+ </Typography>
114
+ </Stack>
115
+ ),
116
+ footer: (
117
+ <>
118
+ <Button variant="outlined" color="secondary">
119
+ Cancelar
120
+ </Button>
121
+ <Button variant="contained">Enviar</Button>
122
+ </>
123
+ ),
124
+ },
125
+ };
126
+
127
+ export const OutlinedVariant: Story = {
128
+ args: {
129
+ variant: 'outlined',
130
+ title: 'Card outlined',
131
+ subtitle: 'Sin sombra, solo borde',
132
+ children: (
133
+ <Typography variant="body2">
134
+ Útil cuando el card vive sobre un fondo con elevation mayor, o para
135
+ evitar stacked shadows en listas densas.
136
+ </Typography>
137
+ ),
138
+ },
139
+ };
140
+
141
+ export const PlainVariant: Story = {
142
+ args: {
143
+ variant: 'plain',
144
+ title: 'Card plain',
145
+ subtitle: 'Sin borde ni sombra',
146
+ children: (
147
+ <Typography variant="body2">
148
+ Perfecto para composición interna — por ejemplo, cuando un card va
149
+ dentro de otro y quieres evitar doble sombra.
150
+ </Typography>
151
+ ),
152
+ },
153
+ };
154
+
155
+ export const Clickable: Story = {
156
+ args: {
157
+ clickable: true,
158
+ title: 'Haz click en este card',
159
+ subtitle: 'Fíjate en el hover',
160
+ children: (
161
+ <Typography variant="body2" color="text.secondary">
162
+ El cursor cambia y aparece un lift sutil en hover. Se dispara `onClick`.
163
+ </Typography>
164
+ ),
165
+ onClick: () => console.log('card clicked'),
166
+ },
167
+ };
168
+
169
+ export const DensePadding: Story = {
170
+ args: {
171
+ padding: 'dense',
172
+ title: 'Card denso',
173
+ subtitle: 'Padding reducido',
174
+ children: (
175
+ <Stack spacing={0.5}>
176
+ <Typography variant="body2">Línea 1</Typography>
177
+ <Typography variant="body2">Línea 2</Typography>
178
+ <Typography variant="body2">Línea 3</Typography>
179
+ </Stack>
180
+ ),
181
+ },
182
+ };
183
+
184
+ export const CustomHeader: Story = {
185
+ args: {
186
+ header: (
187
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, width: '100%' }}>
188
+ <Box
189
+ sx={{
190
+ width: 40,
191
+ height: 40,
192
+ borderRadius: 2,
193
+ bgcolor: 'primary.main',
194
+ color: 'primary.contrastText',
195
+ display: 'flex',
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ fontWeight: 700,
199
+ }}
200
+ >
201
+ 42
202
+ </Box>
203
+ <Box sx={{ flex: 1 }}>
204
+ <Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
205
+ Header totalmente custom
206
+ </Typography>
207
+ <Typography variant="caption" color="text.secondary">
208
+ Tomas control del slot entero
209
+ </Typography>
210
+ </Box>
211
+ <Chip label="v2" size="small" variant="outlined" />
212
+ </Box>
213
+ ),
214
+ children: (
215
+ <Typography variant="body2">
216
+ Cuando `header` se usa, los props `title`/`subtitle`/`actions` son
217
+ ignorados.
218
+ </Typography>
219
+ ),
220
+ },
221
+ };
@@ -0,0 +1,104 @@
1
+ import type { SxProps, Theme } from '@mui/material/styles';
2
+
3
+ export type CardVariant = 'elevated' | 'outlined' | 'plain';
4
+ export type CardPadding = 'none' | 'dense' | 'normal' | 'loose';
5
+
6
+ export interface BuildCardSxArgs {
7
+ variant: CardVariant;
8
+ padding: CardPadding;
9
+ clickable: boolean;
10
+ }
11
+
12
+ const paddingMap: Record<CardPadding, number> = {
13
+ none: 0,
14
+ dense: 1.5,
15
+ normal: 2.5,
16
+ loose: 4,
17
+ };
18
+
19
+ /**
20
+ * Estilo base del Card (root). La props `sx` del consumer se compone encima
21
+ * junto al preset resuelto (en Card.tsx). Este builder solo se ocupa del
22
+ * variant + padding, para que siga siendo predecible.
23
+ */
24
+ export function buildCardSx({
25
+ variant,
26
+ padding,
27
+ clickable,
28
+ }: BuildCardSxArgs): SxProps<Theme> {
29
+ return (theme) => ({
30
+ borderRadius: 2,
31
+ backgroundColor: 'background.paper',
32
+ boxShadow:
33
+ variant === 'elevated'
34
+ ? theme.shadows[1]
35
+ : 'none',
36
+ border:
37
+ variant === 'outlined'
38
+ ? `1px solid ${theme.palette.divider}`
39
+ : 'none',
40
+ transition: theme.transitions.create(
41
+ ['box-shadow', 'transform', 'border-color'],
42
+ { duration: theme.transitions.duration.shorter },
43
+ ),
44
+ cursor: clickable ? 'pointer' : 'default',
45
+ ...(clickable && {
46
+ '&:hover': {
47
+ boxShadow:
48
+ variant === 'elevated'
49
+ ? theme.shadows[3]
50
+ : variant === 'outlined'
51
+ ? theme.shadows[1]
52
+ : 'none',
53
+ transform: variant !== 'plain' ? 'translateY(-1px)' : 'none',
54
+ },
55
+ '&:active': {
56
+ transform: 'translateY(0)',
57
+ },
58
+ }),
59
+ // Body padding (cuando no hay header/footer envolventes). Para el caso
60
+ // compuesto los slots manejan su propio padding, dejamos el root en 0.
61
+ p: paddingMap[padding],
62
+ });
63
+ }
64
+
65
+ export function buildCardHeaderSx(): SxProps<Theme> {
66
+ return (theme) => ({
67
+ display: 'flex',
68
+ alignItems: 'center',
69
+ gap: 1.5,
70
+ px: 2.5,
71
+ py: 1.75,
72
+ borderBottom: `1px solid ${theme.palette.divider}`,
73
+ '& .card-header-text': {
74
+ flex: 1,
75
+ minWidth: 0,
76
+ },
77
+ '& .card-header-actions': {
78
+ display: 'flex',
79
+ alignItems: 'center',
80
+ gap: 0.5,
81
+ flexShrink: 0,
82
+ },
83
+ });
84
+ }
85
+
86
+ export function buildCardBodySx(padding: CardPadding): SxProps<Theme> {
87
+ return {
88
+ px: padding === 'none' ? 0 : padding === 'dense' ? 1.5 : padding === 'loose' ? 4 : 2.5,
89
+ py: padding === 'none' ? 0 : padding === 'dense' ? 1.5 : padding === 'loose' ? 4 : 2.5,
90
+ };
91
+ }
92
+
93
+ export function buildCardFooterSx(): SxProps<Theme> {
94
+ return (theme) => ({
95
+ display: 'flex',
96
+ alignItems: 'center',
97
+ justifyContent: 'flex-end',
98
+ gap: 1,
99
+ px: 2.5,
100
+ py: 1.5,
101
+ borderTop: `1px solid ${theme.palette.divider}`,
102
+ backgroundColor: theme.palette.action.hover,
103
+ });
104
+ }