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

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 (211) hide show
  1. package/dist/README.md +243 -0
  2. package/dist/components/Drawer/Drawer.cjs +14 -17
  3. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  4. package/dist/components/Drawer/Drawer.d.ts +8 -1
  5. package/dist/components/Drawer/Drawer.js +14 -17
  6. package/dist/components/Drawer/Drawer.js.map +1 -1
  7. package/dist/components/Input/Input.definitions.d.ts +1 -0
  8. package/dist/components/RadioGroup/RadioGroup.cjs +202 -0
  9. package/dist/components/RadioGroup/RadioGroup.cjs.map +1 -0
  10. package/dist/components/RadioGroup/RadioGroup.d.ts +53 -0
  11. package/dist/components/RadioGroup/RadioGroup.definitions.d.ts +6 -0
  12. package/dist/components/RadioGroup/RadioGroup.js +202 -0
  13. package/dist/components/RadioGroup/RadioGroup.js.map +1 -0
  14. package/dist/components/RadioGroup/RadioGroup.sx.d.ts +20 -0
  15. package/dist/components/RadioGroup/RadioGroup.types.d.ts +1 -0
  16. package/dist/components/RadioGroup/index.d.ts +2 -0
  17. package/dist/components/RadioGroup.d.ts +6 -0
  18. package/dist/components/Stepper/Stepper.cjs +136 -23
  19. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  20. package/dist/components/Stepper/Stepper.js +137 -24
  21. package/dist/components/Stepper/Stepper.js.map +1 -1
  22. package/dist/components/Switch/Switch.cjs +181 -0
  23. package/dist/components/Switch/Switch.cjs.map +1 -0
  24. package/dist/components/Switch/Switch.d.ts +43 -0
  25. package/dist/components/Switch/Switch.definitions.d.ts +7 -0
  26. package/dist/components/Switch/Switch.js +181 -0
  27. package/dist/components/Switch/Switch.js.map +1 -0
  28. package/dist/components/Switch/Switch.sx.d.ts +22 -0
  29. package/dist/components/Switch/Switch.types.d.ts +1 -0
  30. package/dist/components/Switch/index.d.ts +2 -0
  31. package/dist/components/Switch.d.ts +6 -0
  32. package/dist/index.cjs +24 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js +7 -1
  35. package/dist/mui.d.ts +1 -0
  36. package/dist/package.json +207 -0
  37. package/dist/theme/componentStyles.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/src/components/Drawer/Drawer.stories.tsx +168 -0
  40. package/src/components/Drawer/Drawer.tsx +26 -18
  41. package/src/components/Input/Input.definitions.ts +24 -0
  42. package/src/components/Input/Input.stories.tsx +29 -0
  43. package/src/components/RadioGroup/RadioGroup.definitions.ts +177 -0
  44. package/src/components/RadioGroup/RadioGroup.stories.tsx +231 -0
  45. package/src/components/RadioGroup/RadioGroup.sx.ts +75 -0
  46. package/src/components/RadioGroup/RadioGroup.tsx +196 -0
  47. package/src/components/RadioGroup/RadioGroup.types.ts +10 -0
  48. package/src/components/RadioGroup/index.ts +9 -0
  49. package/src/components/Stepper/Stepper.stories.tsx +72 -0
  50. package/src/components/Stepper/Stepper.tsx +139 -4
  51. package/src/components/Switch/Switch.definitions.ts +134 -0
  52. package/src/components/Switch/Switch.stories.tsx +213 -0
  53. package/src/components/Switch/Switch.sx.ts +81 -0
  54. package/src/components/Switch/Switch.tsx +172 -0
  55. package/src/components/Switch/Switch.types.ts +10 -0
  56. package/src/components/Switch/index.ts +9 -0
  57. package/src/mui.ts +10 -0
  58. package/src/theme/componentStyles.ts +3 -1
  59. package/storybook-static/addon-visual-tests-assets/visual-test-illustration.mp4 +0 -0
  60. package/storybook-static/assets/AccountCircle-BDZFsbTw.js +1 -0
  61. package/storybook-static/assets/ActionMenu-EynP8yU1.js +19 -0
  62. package/storybook-static/assets/ActionMenu.stories-DqSqRGix.js +185 -0
  63. package/storybook-static/assets/Alert-3zvTPc0p.js +1 -0
  64. package/storybook-static/assets/AppBar.stories-DcX3M5th.js +172 -0
  65. package/storybook-static/assets/Autocomplete.stories-CXJm8FOT.js +788 -0
  66. package/storybook-static/assets/Avatar-NbFfkZws.js +1 -0
  67. package/storybook-static/assets/Avatar.stories-CwOYCzqU.js +390 -0
  68. package/storybook-static/assets/Box-BnhEcfFm.js +1 -0
  69. package/storybook-static/assets/Button-D9h7OggD.js +1 -0
  70. package/storybook-static/assets/Button-DBpqmVB_.js +1 -0
  71. package/storybook-static/assets/Button.stories-F20dmnjq.js +320 -0
  72. package/storybook-static/assets/ButtonBase-qyaMEhe4.js +74 -0
  73. package/storybook-static/assets/Card.stories-B3NpAhO0.js +154 -0
  74. package/storybook-static/assets/CheckCircleOutline-CEj5mDsl.js +1 -0
  75. package/storybook-static/assets/Chip-C3vKPpzR.js +1 -0
  76. package/storybook-static/assets/Chip.stories-sxcfHVo9.js +333 -0
  77. package/storybook-static/assets/CircularProgress-DC7ZNWwl.js +28 -0
  78. package/storybook-static/assets/Clear-4kYcKvT3.js +1 -0
  79. package/storybook-static/assets/ClipBoard-DvLBdNHe.js +1 -0
  80. package/storybook-static/assets/ClipBoard.stories-BGUo47r6.js +108 -0
  81. package/storybook-static/assets/Close-CgHeRgmh.js +1 -0
  82. package/storybook-static/assets/Close-Cy8nELYU.js +1 -0
  83. package/storybook-static/assets/Color-AVL7NMMY-BJKvwERm.js +1 -0
  84. package/storybook-static/assets/ContentCopy-BfLTDb10.js +1 -0
  85. package/storybook-static/assets/DatePicker-Clkpr-Ku.js +1 -0
  86. package/storybook-static/assets/DatePicker.stories-EaUCMkh3.js +444 -0
  87. package/storybook-static/assets/DateRangePicker.stories-BMlkj-8K.js +390 -0
  88. package/storybook-static/assets/DateTimePicker.stories-B6gdzKq5.js +555 -0
  89. package/storybook-static/assets/DefaultPropsProvider-BGoQxtDa.js +16 -0
  90. package/storybook-static/assets/Delete-D2SMMmIA.js +1 -0
  91. package/storybook-static/assets/DialogContent-BeCDKgax.js +1 -0
  92. package/storybook-static/assets/Divider-BbCj9wT4.js +1 -0
  93. package/storybook-static/assets/DocsRenderer-PQXLIZUC-BebLK5Y_.js +1243 -0
  94. package/storybook-static/assets/Drawer-DcFwy73r.js +1 -0
  95. package/storybook-static/assets/Drawer.stories-C5AZkJBk.js +173 -0
  96. package/storybook-static/assets/EmptyTable-B-RKtgVs.png +0 -0
  97. package/storybook-static/assets/ErrorOutline-D9gM7ART.js +1 -0
  98. package/storybook-static/assets/Fade-Ll96CvH8.js +1 -0
  99. package/storybook-static/assets/Flyout.stories-Cf7z6MNw.js +163 -0
  100. package/storybook-static/assets/Gallery.stories-DdpWVTF6.js +127 -0
  101. package/storybook-static/assets/Grow-8y4FglGK.js +1 -0
  102. package/storybook-static/assets/Home-BRvJEp2L.js +1 -0
  103. package/storybook-static/assets/Icon.stories-D0mUiW_t.js +78 -0
  104. package/storybook-static/assets/IconButton-9OYSTH58.js +1 -0
  105. package/storybook-static/assets/Input-CjX0t4h-.js +1 -0
  106. package/storybook-static/assets/Input.stories-BRxekliy.js +650 -0
  107. package/storybook-static/assets/InputGroup.stories-DH6gUfmn.js +306 -0
  108. package/storybook-static/assets/KeyboardArrowRight-WO_attK2.js +1 -0
  109. package/storybook-static/assets/KeyboardArrowUp-DsyVef-i.js +1 -0
  110. package/storybook-static/assets/ListItem-D3O0103N.js +1 -0
  111. package/storybook-static/assets/ListItemIcon-hca6xN79.js +1 -0
  112. package/storybook-static/assets/ListItemText-BFLAwLdl.js +1 -0
  113. package/storybook-static/assets/Logout-gj-P3AfU.js +1 -0
  114. package/storybook-static/assets/Menu-ClzfjLc3.js +1 -0
  115. package/storybook-static/assets/MenuButton.stories-B-W_QVDt.js +162 -0
  116. package/storybook-static/assets/MenuItem-iU6tAqJI.js +1 -0
  117. package/storybook-static/assets/Modal-3okp9H2i.js +1 -0
  118. package/storybook-static/assets/Modal.stories-DIWzm4qR.js +468 -0
  119. package/storybook-static/assets/MoreVert-BoIVG4gh.js +1 -0
  120. package/storybook-static/assets/Notifications-DY_A-Sho.js +1 -0
  121. package/storybook-static/assets/PageLoader.stories-DmtO1mlm.js +158 -0
  122. package/storybook-static/assets/Paper-SwQBhqI7.js +1 -0
  123. package/storybook-static/assets/Person-CkQl-mpq.js +1 -0
  124. package/storybook-static/assets/PickersModalDialog-Tjnr_cu5.js +10 -0
  125. package/storybook-static/assets/PickersToolbarButton-Tt185-si.js +1 -0
  126. package/storybook-static/assets/Popper-CnCTYXxy.js +1 -0
  127. package/storybook-static/assets/Portal-Cj8XF9Lf.js +1 -0
  128. package/storybook-static/assets/ScrollTopButton.stories-BflQCwNP.js +90 -0
  129. package/storybook-static/assets/Select-CjcuMAY0.js +4 -0
  130. package/storybook-static/assets/Select-DJh2biEb.js +3 -0
  131. package/storybook-static/assets/Select.stories-DU1Gb3I2.js +1103 -0
  132. package/storybook-static/assets/Settings-BLKc1CnO.js +1 -0
  133. package/storybook-static/assets/Snackbar-BtVeKTw6.js +1 -0
  134. package/storybook-static/assets/Stack-D01OUIXi.js +1 -0
  135. package/storybook-static/assets/Stat.stories-Bn9-iuPT.js +60 -0
  136. package/storybook-static/assets/StatusMessage.stories-hnfX8FeU.js +73 -0
  137. package/storybook-static/assets/Stepper-BtKB5ykn.js +2 -0
  138. package/storybook-static/assets/Stepper.stories-CTEZbgPc.js +165 -0
  139. package/storybook-static/assets/Table.stories-CTn2Ktmn.js +1260 -0
  140. package/storybook-static/assets/TableContainer-CzLNaEU-.js +1 -0
  141. package/storybook-static/assets/TableRow-CS88-1HF.js +2 -0
  142. package/storybook-static/assets/Tabs-DLpDOu_n.js +1 -0
  143. package/storybook-static/assets/Tabs.stories-BFVuFy_5.js +159 -0
  144. package/storybook-static/assets/TextField-22T-xHBm.js +1 -0
  145. package/storybook-static/assets/Timeline.stories-DJU_U2Hv.js +97 -0
  146. package/storybook-static/assets/Tooltip-DbnHUxNj.js +1 -0
  147. package/storybook-static/assets/Tooltip.stories-B7tA3dnV.js +66 -0
  148. package/storybook-static/assets/Typography-BgntX2Ep.js +1 -0
  149. package/storybook-static/assets/Wizard.stories-CVrJLK_D.js +23 -0
  150. package/storybook-static/assets/createSimplePaletteValueFilter-bm0fmN_7.js +1 -0
  151. package/storybook-static/assets/createSvgIcon-D_Gca4vA.js +1 -0
  152. package/storybook-static/assets/debounce-Be36O1Ab.js +1 -0
  153. package/storybook-static/assets/emotion-react.browser.esm--g-C9cX9.js +8 -0
  154. package/storybook-static/assets/extendSxProp-CEpa30hT.js +1 -0
  155. package/storybook-static/assets/formField.sx-DMCmZIAa.js +1 -0
  156. package/storybook-static/assets/getReactElementRef-BQ3ANZdy.js +1 -0
  157. package/storybook-static/assets/iframe-BAJnc_4n.js +1079 -0
  158. package/storybook-static/assets/index-B1tlhOpe.js +240 -0
  159. package/storybook-static/assets/index-BF3FAXTk.js +9 -0
  160. package/storybook-static/assets/index-CIeucmOB.js +2 -0
  161. package/storybook-static/assets/index-CY7j4a7o.js +1 -0
  162. package/storybook-static/assets/index-CxkKctw5.js +1 -0
  163. package/storybook-static/assets/isFocusVisible-B8k4qzLc.js +1 -0
  164. package/storybook-static/assets/isMuiElement-CTZSFcY5.js +1 -0
  165. package/storybook-static/assets/jsx-runtime-D_zvdyIk.js +9 -0
  166. package/storybook-static/assets/listItemTextClasses-CC_rwJam.js +1 -0
  167. package/storybook-static/assets/mergeSlotProps-B0UBKBMe.js +1 -0
  168. package/storybook-static/assets/ownerDocument-DW-IO8s5.js +1 -0
  169. package/storybook-static/assets/ownerWindow-HkKU3E4x.js +1 -0
  170. package/storybook-static/assets/preload-helper-PPVm8Dsz.js +1 -0
  171. package/storybook-static/assets/react-18-BUJ64QCV.js +25 -0
  172. package/storybook-static/assets/resolvePreset-CN2aOJJr.js +1 -0
  173. package/storybook-static/assets/useControlled-DsVh1a5j.js +1 -0
  174. package/storybook-static/assets/useForkRef-0ANIrxcF.js +1 -0
  175. package/storybook-static/assets/useId-b4fZxjOL.js +1 -0
  176. package/storybook-static/assets/useMobilePicker-DK-c8xbD.js +1 -0
  177. package/storybook-static/assets/usePreviousProps-WR0rG4aR.js +1 -0
  178. package/storybook-static/assets/useSlot-b6pXgp5_.js +1 -0
  179. package/storybook-static/assets/useSlotProps-C0uMfuBt.js +1 -0
  180. package/storybook-static/assets/useTheme-BmOJK7ra.js +1 -0
  181. package/storybook-static/assets/useThemeProps-DYtxXiUU.js +1 -0
  182. package/storybook-static/assets/useThemeProps-U4yXiZ_5.js +1 -0
  183. package/storybook-static/assets/useTimeout-DNjRaOWc.js +1 -0
  184. package/storybook-static/assets/visuallyHidden-Dan1xhjv.js +1 -0
  185. package/storybook-static/favicon-wrapper.svg +46 -0
  186. package/storybook-static/favicon.svg +1 -0
  187. package/storybook-static/iframe.html +686 -0
  188. package/storybook-static/index.html +160 -0
  189. package/storybook-static/index.json +1 -0
  190. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  191. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  192. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  193. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  194. package/storybook-static/project.json +1 -0
  195. package/storybook-static/sb-addons/chromatic-com-storybook-2/manager-bundle.js +356 -0
  196. package/storybook-static/sb-addons/chromatic-com-storybook-2/manager-bundle.js.LEGAL.txt +40 -0
  197. package/storybook-static/sb-addons/docs-4/manager-bundle.js +151 -0
  198. package/storybook-static/sb-addons/onboarding-1/manager-bundle.js +127 -0
  199. package/storybook-static/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js +971 -0
  200. package/storybook-static/sb-addons/vitest-3/manager-bundle.js +3 -0
  201. package/storybook-static/sb-common-assets/favicon-wrapper.svg +46 -0
  202. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  203. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  204. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  205. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  206. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  207. package/storybook-static/sb-manager/globals-module-info.js +797 -0
  208. package/storybook-static/sb-manager/globals-runtime.js +69679 -0
  209. package/storybook-static/sb-manager/globals.js +34 -0
  210. package/storybook-static/sb-manager/runtime.js +13195 -0
  211. package/storybook-static/vite-inject-mocker-entry.js +18 -0
@@ -1,5 +1,13 @@
1
- import React, { ReactElement } from "react";
2
- import { Stepper as MuiStepper, Step as MuiStep, StepLabel as MuiStepLabel, Box } from "@mui/material";
1
+ import React, { ReactElement, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import {
3
+ Stepper as MuiStepper,
4
+ Step as MuiStep,
5
+ StepLabel as MuiStepLabel,
6
+ Box,
7
+ IconButton,
8
+ } from "@mui/material";
9
+ import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
10
+ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
3
11
  import { StepProps } from "./Step";
4
12
  import { useWizard } from "../../hooks/Wizard";
5
13
 
@@ -35,9 +43,120 @@ export const Stepper: React.FC<MyStepperProps> = ({
35
43
  ? wizard.currentStep
36
44
  : 0;
37
45
 
46
+ const isHorizontal = orientation === "horizontal";
47
+
48
+ // Scroll horizontal con flechitas cuando los steps no caben.
49
+ const scrollRef = useRef<HTMLDivElement | null>(null);
50
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
51
+ const [canScrollRight, setCanScrollRight] = useState(false);
52
+
53
+ const updateScrollState = () => {
54
+ const el = scrollRef.current;
55
+ if (!el) return;
56
+ const { scrollLeft, scrollWidth, clientWidth } = el;
57
+ // Tolerancia de 1px por redondeos de subpixel.
58
+ setCanScrollLeft(scrollLeft > 1);
59
+ setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 1);
60
+ };
61
+
62
+ useLayoutEffect(() => {
63
+ if (!isHorizontal) return;
64
+ updateScrollState();
65
+ }, [isHorizontal, children.length, currentStep]);
66
+
67
+ useEffect(() => {
68
+ if (!isHorizontal) return;
69
+ const el = scrollRef.current;
70
+ if (!el) return;
71
+ const onScroll = () => updateScrollState();
72
+ el.addEventListener("scroll", onScroll, { passive: true });
73
+ const ro = new ResizeObserver(() => updateScrollState());
74
+ ro.observe(el);
75
+ return () => {
76
+ el.removeEventListener("scroll", onScroll);
77
+ ro.disconnect();
78
+ };
79
+ }, [isHorizontal]);
80
+
81
+ const scrollBy = (delta: number) => {
82
+ const el = scrollRef.current;
83
+ if (!el) return;
84
+ el.scrollBy({ left: delta, behavior: "smooth" });
85
+ };
86
+
87
+ const showArrows = isHorizontal && (canScrollLeft || canScrollRight);
88
+
38
89
  return (
39
90
  <Box sx={sx}>
40
- <MuiStepper activeStep={currentStep} orientation={orientation} alternativeLabel={alternateLabel} >
91
+ {/*
92
+ En horizontal envolvemos el MuiStepper en un Box con `overflowX: auto`
93
+ y flechitas `<` `>` a los costados para navegar cuando los steps no
94
+ caben en el contenedor. Mantenemos el scroll nativo (rueda / swipe)
95
+ y ocultamos la scrollbar visual.
96
+ */}
97
+ <Box
98
+ sx={
99
+ isHorizontal
100
+ ? {
101
+ display: "flex",
102
+ alignItems: "center",
103
+ width: "100%",
104
+ gap: 0.5,
105
+ }
106
+ : undefined
107
+ }
108
+ >
109
+ {isHorizontal && (
110
+ <IconButton
111
+ aria-label="Anterior"
112
+ size="small"
113
+ onClick={() => scrollBy(-160)}
114
+ disabled={!canScrollLeft}
115
+ sx={{
116
+ flex: "0 0 auto",
117
+ // Reservamos el slot siempre que haya overflow en algún lado,
118
+ // así el área scroll tiene un ancho estable (no saltan los
119
+ // steps cuando aparece/desaparece la flecha).
120
+ visibility: showArrows ? "visible" : "hidden",
121
+ }}
122
+ >
123
+ <ChevronLeftIcon fontSize="small" />
124
+ </IconButton>
125
+ )}
126
+ <Box
127
+ ref={scrollRef}
128
+ sx={
129
+ isHorizontal
130
+ ? {
131
+ flex: "1 1 auto",
132
+ minWidth: 0,
133
+ overflowX: "auto",
134
+ scrollBehavior: "smooth",
135
+ WebkitOverflowScrolling: "touch",
136
+ // Ocultamos la scrollbar (la navegación es con flechas)
137
+ scrollbarWidth: "none",
138
+ "&::-webkit-scrollbar": { display: "none" },
139
+ }
140
+ : undefined
141
+ }
142
+ >
143
+ <MuiStepper
144
+ activeStep={currentStep}
145
+ orientation={orientation}
146
+ alternativeLabel={alternateLabel}
147
+ sx={
148
+ isHorizontal
149
+ ? {
150
+ display: "inline-flex",
151
+ flexWrap: "nowrap",
152
+ minWidth: "100%",
153
+ width: "max-content",
154
+ // Evita que los steps se compriman al punto de solaparse.
155
+ "& .MuiStep-root": { flex: "0 0 auto", minWidth: 120 },
156
+ }
157
+ : undefined
158
+ }
159
+ >
41
160
  {children.map((child, idx) => {
42
161
  const { label, completed, disabled, className, sx: stepSx, dotColor, activeDotColor, completedDotColor, iconTextColor } = child.props;
43
162
  return (
@@ -62,7 +181,23 @@ export const Stepper: React.FC<MyStepperProps> = ({
62
181
  </MuiStep>
63
182
  );
64
183
  })}
65
- </MuiStepper>
184
+ </MuiStepper>
185
+ </Box>
186
+ {isHorizontal && (
187
+ <IconButton
188
+ aria-label="Siguiente"
189
+ size="small"
190
+ onClick={() => scrollBy(160)}
191
+ disabled={!canScrollRight}
192
+ sx={{
193
+ flex: "0 0 auto",
194
+ visibility: showArrows ? "visible" : "hidden",
195
+ }}
196
+ >
197
+ <ChevronRightIcon fontSize="small" />
198
+ </IconButton>
199
+ )}
200
+ </Box>
66
201
  <Box mt={2}>
67
202
  {children.map((child, idx) =>
68
203
  idx === currentStep ? <Box key={idx}>{child.props.children}</Box> : null
@@ -0,0 +1,134 @@
1
+ // Switch.definitions.ts — código fuente para la pestaña Docs de Storybook.
2
+
3
+ export const BasicSwitchDefinition = `
4
+ import React, { useState } from 'react';
5
+ import { Switch } from './Switch';
6
+ import { Box, Typography } from '@mui/material';
7
+
8
+ export const BasicSwitchExample = () => {
9
+ const [checked, setChecked] = useState(false);
10
+ return (
11
+ <Box sx={{ width: 280 }}>
12
+ <Switch checked={checked} onChange={setChecked} />
13
+ <Typography sx={{ mt: 2 }}>Estado: {checked ? 'ON' : 'OFF'}</Typography>
14
+ </Box>
15
+ );
16
+ };
17
+ `;
18
+
19
+ export const SwitchWithLabelDefinition = `
20
+ import React, { useState } from 'react';
21
+ import { Switch } from './Switch';
22
+ import { Box } from '@mui/material';
23
+
24
+ export const SwitchWithLabelExample = () => {
25
+ const [checked, setChecked] = useState(true);
26
+ return (
27
+ <Box sx={{ width: 280 }}>
28
+ <Switch
29
+ label="Notificaciones por email"
30
+ checked={checked}
31
+ onChange={setChecked}
32
+ />
33
+ </Box>
34
+ );
35
+ };
36
+ `;
37
+
38
+ export const BorderedSwitchDefinition = `
39
+ import React, { useState } from 'react';
40
+ import { Switch } from './Switch';
41
+ import { Box } from '@mui/material';
42
+
43
+ export const BorderedSwitchExample = () => {
44
+ const [checked, setChecked] = useState(true);
45
+ return (
46
+ <Box sx={{ width: 360 }}>
47
+ <Switch
48
+ label="Recibir reportes diarios"
49
+ checked={checked}
50
+ onChange={setChecked}
51
+ bordered
52
+ />
53
+ </Box>
54
+ );
55
+ };
56
+ `;
57
+
58
+ export const SwitchSizesDefinition = `
59
+ import React, { useState } from 'react';
60
+ import { Switch } from './Switch';
61
+ import { Box } from '@mui/material';
62
+
63
+ export const SwitchSizesExample = () => {
64
+ const [a, setA] = useState(false);
65
+ const [b, setB] = useState(true);
66
+ return (
67
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: 280 }}>
68
+ <Switch label="Pequeño" size="small" checked={a} onChange={setA} />
69
+ <Switch label="Mediano (default)" size="medium" checked={b} onChange={setB} />
70
+ </Box>
71
+ );
72
+ };
73
+ `;
74
+
75
+ export const SwitchWithErrorDefinition = `
76
+ import React, { useState } from 'react';
77
+ import { Switch } from './Switch';
78
+ import { Box } from '@mui/material';
79
+
80
+ export const SwitchWithErrorExample = () => {
81
+ const [checked, setChecked] = useState(false);
82
+ const hasError = !checked;
83
+ return (
84
+ <Box sx={{ width: 320 }}>
85
+ <Switch
86
+ label="Aceptar términos y condiciones"
87
+ checked={checked}
88
+ onChange={setChecked}
89
+ error={hasError}
90
+ helperText={hasError ? 'Debes aceptar los términos para continuar' : ''}
91
+ />
92
+ </Box>
93
+ );
94
+ };
95
+ `;
96
+
97
+ export const DisabledSwitchDefinition = `
98
+ import React from 'react';
99
+ import { Switch } from './Switch';
100
+ import { Box } from '@mui/material';
101
+
102
+ export const DisabledSwitchExample = () => (
103
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: 280 }}>
104
+ <Switch label="Off bloqueado" checked={false} onChange={() => {}} disabled />
105
+ <Switch label="On bloqueado" checked={true} onChange={() => {}} disabled />
106
+ </Box>
107
+ );
108
+ `;
109
+
110
+ export const RHFSwitchDefinition = `
111
+ import React from 'react';
112
+ import { useForm } from 'react-hook-form';
113
+ import { Switch } from './Switch';
114
+ import { Box, Button, Typography } from '@mui/material';
115
+
116
+ export const RHFSwitchExample = () => {
117
+ const { control, handleSubmit, watch } = useForm({
118
+ defaultValues: { darkMode: false },
119
+ });
120
+ const value = watch('darkMode');
121
+ return (
122
+ <Box sx={{ width: 320 }} component="form" onSubmit={handleSubmit(console.log)}>
123
+ <Switch
124
+ label="Modo oscuro"
125
+ name="darkMode"
126
+ control={control}
127
+ bordered
128
+ />
129
+ <Typography sx={{ mt: 2 }}>Valor en el form: {String(value)}</Typography>
130
+ <Button type="submit" sx={{ mt: 1 }}>Enviar</Button>
131
+ </Box>
132
+ );
133
+ };
134
+ `;
@@ -0,0 +1,213 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React, { useState } from 'react';
3
+ import { Box, Button, Typography } from '@mui/material';
4
+ import { useForm } from 'react-hook-form';
5
+
6
+ import { Switch } from './Switch';
7
+ import {
8
+ BasicSwitchDefinition,
9
+ SwitchWithLabelDefinition,
10
+ BorderedSwitchDefinition,
11
+ SwitchSizesDefinition,
12
+ SwitchWithErrorDefinition,
13
+ DisabledSwitchDefinition,
14
+ RHFSwitchDefinition,
15
+ } from './Switch.definitions';
16
+
17
+ // =============================================================================
18
+ // Meta
19
+ // =============================================================================
20
+ const meta: Meta<typeof Switch> = {
21
+ title: 'Components/Switch',
22
+ component: Switch,
23
+ tags: ['autodocs'],
24
+ parameters: {
25
+ layout: 'centered',
26
+ docs: {
27
+ description: {
28
+ component:
29
+ 'Un toggle/switch basado en MUI `Switch`. Soporta modo controlado (`checked` + `onChange`) ' +
30
+ 'y modo react-hook-form (`name` + `control`). Mantiene compatibilidad visual con el legacy ' +
31
+ '`FormToggleInput` mediante la prop `bordered` (label a la izquierda, switch a la derecha).',
32
+ },
33
+ },
34
+ },
35
+ argTypes: {
36
+ label: { control: 'text', description: 'Texto al lado del switch. Si está ausente, se renderiza solo el switch.' },
37
+ labelPlacement: {
38
+ control: 'radio',
39
+ options: ['start', 'end', 'top', 'bottom'],
40
+ description: 'Posición del label respecto al switch. Default: `end`.',
41
+ },
42
+ size: { control: 'radio', options: ['small', 'medium'], description: 'Tamaño del switch. Default: `medium`.' },
43
+ bordered: { control: 'boolean', description: 'Renderizar contenedor con borde estilo "form field". Default: false.' },
44
+ borderRadius: { control: { type: 'number' }, description: 'Border radius del contenedor cuando `bordered`. Default: 10.' },
45
+ disabled: { control: 'boolean', description: 'Deshabilitar el switch.' },
46
+ error: { control: 'boolean', description: 'Estado de error visual.' },
47
+ helperText: { control: 'text', description: 'Texto auxiliar debajo del switch.' },
48
+ },
49
+ };
50
+
51
+ export default meta;
52
+ type Story = StoryObj<typeof Switch>;
53
+
54
+ // =============================================================================
55
+ // Stories
56
+ // =============================================================================
57
+
58
+ export const BasicSwitch: Story = {
59
+ render: () => {
60
+ const [checked, setChecked] = useState(false);
61
+ return (
62
+ <Box sx={{ width: 280 }}>
63
+ <Switch checked={checked} onChange={setChecked} />
64
+ <Typography sx={{ mt: 2 }}>Estado: {checked ? 'ON' : 'OFF'}</Typography>
65
+ </Box>
66
+ );
67
+ },
68
+ parameters: {
69
+ docs: {
70
+ description: { story: 'Uso mínimo, sin label. Equivalente al legacy `ToggleInputReport`.' },
71
+ source: { code: BasicSwitchDefinition.trim() },
72
+ },
73
+ },
74
+ };
75
+
76
+ export const SwitchWithLabel: Story = {
77
+ render: () => {
78
+ const [checked, setChecked] = useState(true);
79
+ return (
80
+ <Box sx={{ width: 280 }}>
81
+ <Switch
82
+ label="Notificaciones por email"
83
+ checked={checked}
84
+ onChange={setChecked}
85
+ />
86
+ </Box>
87
+ );
88
+ },
89
+ parameters: {
90
+ docs: {
91
+ description: { story: 'Switch con label a la derecha (default `labelPlacement="end"`).' },
92
+ source: { code: SwitchWithLabelDefinition.trim() },
93
+ },
94
+ },
95
+ };
96
+
97
+ export const BorderedSwitch: Story = {
98
+ render: () => {
99
+ const [checked, setChecked] = useState(true);
100
+ return (
101
+ <Box sx={{ width: 360 }}>
102
+ <Switch
103
+ label="Recibir reportes diarios"
104
+ checked={checked}
105
+ onChange={setChecked}
106
+ bordered
107
+ />
108
+ </Box>
109
+ );
110
+ },
111
+ parameters: {
112
+ docs: {
113
+ description: {
114
+ story:
115
+ 'Variante con `bordered`: contenedor con borde, label a la izquierda y switch al borde opuesto. ' +
116
+ 'Replica el estilo del legacy `FormToggleInput`.',
117
+ },
118
+ source: { code: BorderedSwitchDefinition.trim() },
119
+ },
120
+ },
121
+ };
122
+
123
+ export const SwitchSizes: Story = {
124
+ render: () => {
125
+ const [a, setA] = useState(false);
126
+ const [b, setB] = useState(true);
127
+ return (
128
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: 280 }}>
129
+ <Switch label="Pequeño" size="small" checked={a} onChange={setA} />
130
+ <Switch label="Mediano (default)" size="medium" checked={b} onChange={setB} />
131
+ </Box>
132
+ );
133
+ },
134
+ parameters: {
135
+ docs: {
136
+ description: { story: 'Tamaños disponibles: `small` y `medium` (default).' },
137
+ source: { code: SwitchSizesDefinition.trim() },
138
+ },
139
+ },
140
+ };
141
+
142
+ export const SwitchWithError: Story = {
143
+ render: () => {
144
+ const [checked, setChecked] = useState(false);
145
+ const hasError = !checked;
146
+ return (
147
+ <Box sx={{ width: 320 }}>
148
+ <Switch
149
+ label="Aceptar términos y condiciones"
150
+ checked={checked}
151
+ onChange={setChecked}
152
+ error={hasError}
153
+ helperText={hasError ? 'Debes aceptar los términos para continuar' : ''}
154
+ />
155
+ </Box>
156
+ );
157
+ },
158
+ parameters: {
159
+ docs: {
160
+ description: { story: 'Estado de error con `error` + `helperText`.' },
161
+ source: { code: SwitchWithErrorDefinition.trim() },
162
+ },
163
+ },
164
+ };
165
+
166
+ export const DisabledSwitch: Story = {
167
+ render: () => (
168
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: 280 }}>
169
+ <Switch label="Off bloqueado" checked={false} onChange={() => {}} disabled />
170
+ <Switch label="On bloqueado" checked={true} onChange={() => {}} disabled />
171
+ </Box>
172
+ ),
173
+ parameters: {
174
+ docs: {
175
+ description: { story: 'Switch deshabilitado en ambos estados.' },
176
+ source: { code: DisabledSwitchDefinition.trim() },
177
+ },
178
+ },
179
+ };
180
+
181
+ export const RHFSwitch: Story = {
182
+ render: () => {
183
+ const { control, handleSubmit, watch } = useForm({
184
+ defaultValues: { darkMode: false },
185
+ });
186
+ const value = watch('darkMode');
187
+ return (
188
+ <Box
189
+ sx={{ width: 320 }}
190
+ component="form"
191
+ onSubmit={handleSubmit((data) => console.log(data))}
192
+ >
193
+ <Switch
194
+ label="Modo oscuro"
195
+ name="darkMode"
196
+ control={control}
197
+ bordered
198
+ />
199
+ <Typography sx={{ mt: 2 }}>Valor en el form: {String(value)}</Typography>
200
+ <Button type="submit" sx={{ mt: 1 }}>Enviar</Button>
201
+ </Box>
202
+ );
203
+ },
204
+ parameters: {
205
+ docs: {
206
+ description: {
207
+ story:
208
+ 'Modo react-hook-form con `name` + `control`. El valor `true`/`false` viaja directo al form state.',
209
+ },
210
+ source: { code: RHFSwitchDefinition.trim() },
211
+ },
212
+ },
213
+ };
@@ -0,0 +1,81 @@
1
+ import type { SxProps, Theme } from '@mui/material/styles';
2
+
3
+ export interface BuildSwitchSxOptions {
4
+ bordered: boolean;
5
+ borderRadius: number | string;
6
+ hasLabel: boolean;
7
+ error: boolean;
8
+ }
9
+
10
+ const toRadius = (borderRadius: number | string) =>
11
+ typeof borderRadius === 'number' ? `${borderRadius}px` : borderRadius;
12
+
13
+ /**
14
+ * Transición compartida (matchea el InputGroup y el RadioGroup).
15
+ */
16
+ const FOCUS_TRANSITION =
17
+ 'border-color 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms, ' +
18
+ 'box-shadow 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms';
19
+
20
+ /**
21
+ * Builder de sx para Switch. Reproduce el estilo del antiguo
22
+ * `FormToggleInput` legacy cuando `bordered=true`: contenedor con borde,
23
+ * label a la izquierda y switch a la derecha (vía justify-content: space-between).
24
+ *
25
+ * Cuando `bordered=true`, el contenedor gana:
26
+ * - `:focus-within` → borde + shadow primario (mismo patrón que `InputGroup`).
27
+ * - `error=true` → borde + shadow de error (palette.error.main).
28
+ *
29
+ * El shadow es de 1px, así que no hay shift de layout — sólo se intensifica
30
+ * visualmente el borde sin empujar nada alrededor.
31
+ *
32
+ * Si `bordered=false`, sólo aplica un reset de margin del helper text.
33
+ */
34
+ export const buildSwitchSx = ({
35
+ bordered,
36
+ borderRadius,
37
+ hasLabel,
38
+ error,
39
+ }: BuildSwitchSxOptions): SxProps<Theme> => {
40
+ const radius = toRadius(borderRadius);
41
+
42
+ if (!bordered) {
43
+ return {
44
+ '& .MuiFormHelperText-root': { marginLeft: 0 },
45
+ };
46
+ }
47
+
48
+ return (theme) => ({
49
+ '& .MuiFormControlLabel-root': {
50
+ marginLeft: 0,
51
+ marginRight: 0,
52
+ paddingX: 1.5,
53
+ paddingY: 1.25,
54
+ border: `1px solid ${
55
+ error ? theme.palette.error.main : theme.palette.divider
56
+ }`,
57
+ borderRadius: radius,
58
+ transition: FOCUS_TRANSITION,
59
+ ...(error && {
60
+ boxShadow: `0 0 0 1px ${theme.palette.error.main}`,
61
+ }),
62
+ // Focus del usuario sobre el switch interno.
63
+ '&:focus-within': {
64
+ borderColor: error
65
+ ? theme.palette.error.main
66
+ : theme.palette.primary.main,
67
+ boxShadow: `0 0 0 1px ${
68
+ error ? theme.palette.error.main : theme.palette.primary.main
69
+ }`,
70
+ },
71
+ // Si hay label, separar el switch al borde opuesto.
72
+ ...(hasLabel && {
73
+ width: '100%',
74
+ justifyContent: 'space-between',
75
+ }),
76
+ },
77
+ '& .MuiFormHelperText-root': {
78
+ marginLeft: 0,
79
+ },
80
+ });
81
+ };