flysoft-react-ui 1.2.3 → 1.2.5

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 (270) hide show
  1. package/AI_CONTEXT.md +1400 -217
  2. package/AI_INTEGRATION_GUIDE.md +343 -0
  3. package/INTEGRATION_GUIDE.md +60 -0
  4. package/README.md +5 -3
  5. package/dist/components/form-controls/Input.d.ts.map +1 -1
  6. package/dist/components/form-controls/index.d.ts +2 -2
  7. package/dist/components/form-controls/index.d.ts.map +1 -1
  8. package/dist/components/layout/Accordion.d.ts +1 -0
  9. package/dist/components/layout/Accordion.d.ts.map +1 -1
  10. package/dist/components/layout/DataTable.d.ts.map +1 -1
  11. package/dist/components/layout/DropdownMenu.d.ts +2 -1
  12. package/dist/components/layout/DropdownMenu.d.ts.map +1 -1
  13. package/dist/components/layout/DropdownPanel.d.ts +2 -1
  14. package/dist/components/layout/DropdownPanel.d.ts.map +1 -1
  15. package/dist/components/layout/Filter.d.ts +1 -0
  16. package/dist/components/layout/Filter.d.ts.map +1 -1
  17. package/dist/components/layout/Menu.d.ts +2 -1
  18. package/dist/components/layout/Menu.d.ts.map +1 -1
  19. package/dist/components/layout/TabsGroup.d.ts +1 -0
  20. package/dist/components/layout/TabsGroup.d.ts.map +1 -1
  21. package/dist/index.css +1 -1
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +11889 -24
  25. package/dist/index.js.map +1 -1
  26. package/dist/templates/forms/ContactForm.d.ts +1 -0
  27. package/dist/templates/forms/ContactForm.d.ts.map +1 -1
  28. package/dist/templates/forms/LoginForm.d.ts +1 -0
  29. package/dist/templates/forms/LoginForm.d.ts.map +1 -1
  30. package/dist/templates/forms/RegistrationForm.d.ts +1 -0
  31. package/dist/templates/forms/RegistrationForm.d.ts.map +1 -1
  32. package/dist/templates/layouts/DashboardLayout.d.ts +1 -0
  33. package/dist/templates/layouts/DashboardLayout.d.ts.map +1 -1
  34. package/dist/templates/layouts/SidebarLayout.d.ts +1 -0
  35. package/dist/templates/layouts/SidebarLayout.d.ts.map +1 -1
  36. package/dist/templates/patterns/FormPattern.d.ts +1 -0
  37. package/dist/templates/patterns/FormPattern.d.ts.map +1 -1
  38. package/dist/templates/patterns/ListPattern.d.ts +77 -0
  39. package/dist/templates/patterns/ListPattern.d.ts.map +1 -0
  40. package/package.json +6 -3
  41. package/dist/App.d.ts +0 -4
  42. package/dist/App.d.ts.map +0 -1
  43. package/dist/App.js +0 -30
  44. package/dist/components/ThemeSwitcher.js +0 -12
  45. package/dist/components/form-controls/AutocompleteInput.js +0 -680
  46. package/dist/components/form-controls/Button.js +0 -211
  47. package/dist/components/form-controls/Checkbox.js +0 -79
  48. package/dist/components/form-controls/CurrencyInput.js +0 -106
  49. package/dist/components/form-controls/DateInput.js +0 -578
  50. package/dist/components/form-controls/DatePicker.js +0 -144
  51. package/dist/components/form-controls/Input.js +0 -35
  52. package/dist/components/form-controls/LinkButton.js +0 -248
  53. package/dist/components/form-controls/Pagination.js +0 -23
  54. package/dist/components/form-controls/RadioButtonGroup.js +0 -220
  55. package/dist/components/form-controls/SearchSelectInput-OLD.d.ts +0 -68
  56. package/dist/components/form-controls/SearchSelectInput-OLD.d.ts.map +0 -1
  57. package/dist/components/form-controls/SearchSelectInput-OLD.js +0 -962
  58. package/dist/components/form-controls/SearchSelectInput.js +0 -336
  59. package/dist/components/form-controls/index.js +0 -11
  60. package/dist/components/index.js +0 -7
  61. package/dist/components/layout/Accordion.js +0 -67
  62. package/dist/components/layout/AppLayout.js +0 -230
  63. package/dist/components/layout/Card.js +0 -54
  64. package/dist/components/layout/Collection.js +0 -18
  65. package/dist/components/layout/DataField.js +0 -38
  66. package/dist/components/layout/DataTable.js +0 -164
  67. package/dist/components/layout/DropdownMenu.js +0 -176
  68. package/dist/components/layout/DropdownPanel.js +0 -162
  69. package/dist/components/layout/Filter.js +0 -629
  70. package/dist/components/layout/Menu.js +0 -21
  71. package/dist/components/layout/TabPanel.js +0 -11
  72. package/dist/components/layout/TabsGroup.js +0 -52
  73. package/dist/components/layout/index.js +0 -12
  74. package/dist/components/utils/Avatar.js +0 -77
  75. package/dist/components/utils/Badge.js +0 -151
  76. package/dist/components/utils/Dialog.js +0 -44
  77. package/dist/components/utils/FiltersDialog.js +0 -104
  78. package/dist/components/utils/Loader.js +0 -44
  79. package/dist/components/utils/RoadMap.js +0 -139
  80. package/dist/components/utils/Skeleton.js +0 -10
  81. package/dist/components/utils/Snackbar.js +0 -136
  82. package/dist/components/utils/SnackbarContainer.js +0 -26
  83. package/dist/components/utils/iconUtils.js +0 -40
  84. package/dist/components/utils/index.js +0 -9
  85. package/dist/contexts/AppLayoutContext.js +0 -104
  86. package/dist/contexts/AuthContext.js +0 -224
  87. package/dist/contexts/CrudContext.js +0 -333
  88. package/dist/contexts/SnackbarContext.js +0 -41
  89. package/dist/contexts/ThemeContext.js +0 -197
  90. package/dist/contexts/index.js +0 -13
  91. package/dist/contexts/presets.js +0 -311
  92. package/dist/contexts/types.js +0 -1
  93. package/dist/docs/AccordionDocs.d.ts +0 -4
  94. package/dist/docs/AccordionDocs.d.ts.map +0 -1
  95. package/dist/docs/AccordionDocs.js +0 -21
  96. package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts +0 -13
  97. package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts.map +0 -1
  98. package/dist/docs/AuthDocs.tsx/AuthDocs.js +0 -18
  99. package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts +0 -2
  100. package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts.map +0 -1
  101. package/dist/docs/AuthDocs.tsx/AuthDocsContent.js +0 -22
  102. package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts +0 -24
  103. package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts.map +0 -1
  104. package/dist/docs/AuthDocs.tsx/mockAuthService.js +0 -78
  105. package/dist/docs/AutocompleteInputDocs.d.ts +0 -4
  106. package/dist/docs/AutocompleteInputDocs.d.ts.map +0 -1
  107. package/dist/docs/AutocompleteInputDocs.js +0 -84
  108. package/dist/docs/AvatarDocs.d.ts +0 -4
  109. package/dist/docs/AvatarDocs.d.ts.map +0 -1
  110. package/dist/docs/AvatarDocs.js +0 -7
  111. package/dist/docs/BadgeDocs.d.ts +0 -4
  112. package/dist/docs/BadgeDocs.d.ts.map +0 -1
  113. package/dist/docs/BadgeDocs.js +0 -9
  114. package/dist/docs/ButtonDocs.d.ts +0 -4
  115. package/dist/docs/ButtonDocs.d.ts.map +0 -1
  116. package/dist/docs/ButtonDocs.js +0 -7
  117. package/dist/docs/CardDocs.d.ts +0 -4
  118. package/dist/docs/CardDocs.d.ts.map +0 -1
  119. package/dist/docs/CardDocs.js +0 -22
  120. package/dist/docs/CheckboxDocs.d.ts +0 -4
  121. package/dist/docs/CheckboxDocs.d.ts.map +0 -1
  122. package/dist/docs/CheckboxDocs.js +0 -7
  123. package/dist/docs/CurrencyInputDocs.d.ts +0 -4
  124. package/dist/docs/CurrencyInputDocs.d.ts.map +0 -1
  125. package/dist/docs/CurrencyInputDocs.js +0 -22
  126. package/dist/docs/DataFieldDocs.d.ts +0 -4
  127. package/dist/docs/DataFieldDocs.d.ts.map +0 -1
  128. package/dist/docs/DataFieldDocs.js +0 -7
  129. package/dist/docs/DataTableDocs.d.ts +0 -4
  130. package/dist/docs/DataTableDocs.d.ts.map +0 -1
  131. package/dist/docs/DataTableDocs.js +0 -244
  132. package/dist/docs/DateInputDocs.d.ts +0 -5
  133. package/dist/docs/DateInputDocs.d.ts.map +0 -1
  134. package/dist/docs/DateInputDocs.js +0 -19
  135. package/dist/docs/DatePickerDocs.d.ts +0 -5
  136. package/dist/docs/DatePickerDocs.d.ts.map +0 -1
  137. package/dist/docs/DatePickerDocs.js +0 -16
  138. package/dist/docs/DialogDocs.d.ts +0 -4
  139. package/dist/docs/DialogDocs.d.ts.map +0 -1
  140. package/dist/docs/DialogDocs.js +0 -13
  141. package/dist/docs/DocAdmin.d.ts +0 -4
  142. package/dist/docs/DocAdmin.d.ts.map +0 -1
  143. package/dist/docs/DocAdmin.js +0 -68
  144. package/dist/docs/DocsMenu.d.ts +0 -2
  145. package/dist/docs/DocsMenu.d.ts.map +0 -1
  146. package/dist/docs/DocsMenu.js +0 -5
  147. package/dist/docs/DocsRouter.d.ts +0 -4
  148. package/dist/docs/DocsRouter.d.ts.map +0 -1
  149. package/dist/docs/DocsRouter.js +0 -39
  150. package/dist/docs/DropdownMenuDocs.d.ts +0 -4
  151. package/dist/docs/DropdownMenuDocs.d.ts.map +0 -1
  152. package/dist/docs/DropdownMenuDocs.js +0 -66
  153. package/dist/docs/DropdownPanelDocs.d.ts +0 -4
  154. package/dist/docs/DropdownPanelDocs.d.ts.map +0 -1
  155. package/dist/docs/DropdownPanelDocs.js +0 -7
  156. package/dist/docs/ExampleFormDocs.d.ts +0 -4
  157. package/dist/docs/ExampleFormDocs.d.ts.map +0 -1
  158. package/dist/docs/ExampleFormDocs.js +0 -153
  159. package/dist/docs/FilterDocs.d.ts +0 -4
  160. package/dist/docs/FilterDocs.d.ts.map +0 -1
  161. package/dist/docs/FilterDocs.js +0 -130
  162. package/dist/docs/InputDocs.d.ts +0 -4
  163. package/dist/docs/InputDocs.d.ts.map +0 -1
  164. package/dist/docs/InputDocs.js +0 -17
  165. package/dist/docs/LinkButtonDocs.d.ts +0 -4
  166. package/dist/docs/LinkButtonDocs.d.ts.map +0 -1
  167. package/dist/docs/LinkButtonDocs.js +0 -7
  168. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts +0 -2
  169. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts.map +0 -1
  170. package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.js +0 -47
  171. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts +0 -2
  172. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts.map +0 -1
  173. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.js +0 -34
  174. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts +0 -2
  175. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts.map +0 -1
  176. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.js +0 -66
  177. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts +0 -2
  178. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts.map +0 -1
  179. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.js +0 -7
  180. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts +0 -10
  181. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts.map +0 -1
  182. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.js +0 -39
  183. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts +0 -2
  184. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts.map +0 -1
  185. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.js +0 -57
  186. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts +0 -9
  187. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts.map +0 -1
  188. package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.js +0 -30
  189. package/dist/docs/LoaderDocs.d.ts +0 -4
  190. package/dist/docs/LoaderDocs.d.ts.map +0 -1
  191. package/dist/docs/LoaderDocs.js +0 -33
  192. package/dist/docs/MenuDocs.d.ts +0 -4
  193. package/dist/docs/MenuDocs.d.ts.map +0 -1
  194. package/dist/docs/MenuDocs.js +0 -26
  195. package/dist/docs/PaginationDocs.d.ts +0 -4
  196. package/dist/docs/PaginationDocs.d.ts.map +0 -1
  197. package/dist/docs/PaginationDocs.js +0 -38
  198. package/dist/docs/RadioButtonGroupDocs.d.ts +0 -4
  199. package/dist/docs/RadioButtonGroupDocs.d.ts.map +0 -1
  200. package/dist/docs/RadioButtonGroupDocs.js +0 -46
  201. package/dist/docs/RoadMapDocs.d.ts +0 -4
  202. package/dist/docs/RoadMapDocs.d.ts.map +0 -1
  203. package/dist/docs/RoadMapDocs.js +0 -171
  204. package/dist/docs/SearchSelectInputDocs.d.ts +0 -4
  205. package/dist/docs/SearchSelectInputDocs.d.ts.map +0 -1
  206. package/dist/docs/SearchSelectInputDocs.js +0 -168
  207. package/dist/docs/SkeletonDocs.d.ts +0 -4
  208. package/dist/docs/SkeletonDocs.d.ts.map +0 -1
  209. package/dist/docs/SkeletonDocs.js +0 -7
  210. package/dist/docs/SnackbarDocs.d.ts +0 -4
  211. package/dist/docs/SnackbarDocs.d.ts.map +0 -1
  212. package/dist/docs/SnackbarDocs.js +0 -69
  213. package/dist/docs/TabsGroupDocs.d.ts +0 -4
  214. package/dist/docs/TabsGroupDocs.d.ts.map +0 -1
  215. package/dist/docs/TabsGroupDocs.js +0 -38
  216. package/dist/docs/ThemeSwitcherDocs.d.ts +0 -4
  217. package/dist/docs/ThemeSwitcherDocs.d.ts.map +0 -1
  218. package/dist/docs/ThemeSwitcherDocs.js +0 -11
  219. package/dist/docs/docMockServices/empresaService.d.ts +0 -38
  220. package/dist/docs/docMockServices/empresaService.d.ts.map +0 -1
  221. package/dist/docs/docMockServices/empresaService.js +0 -125
  222. package/dist/docs/docMockServices/index.d.ts +0 -9
  223. package/dist/docs/docMockServices/index.d.ts.map +0 -1
  224. package/dist/docs/docMockServices/index.js +0 -8
  225. package/dist/docs/docMockServices/initialData.d.ts +0 -6
  226. package/dist/docs/docMockServices/initialData.d.ts.map +0 -1
  227. package/dist/docs/docMockServices/initialData.js +0 -132
  228. package/dist/docs/docMockServices/interfaces.d.ts +0 -38
  229. package/dist/docs/docMockServices/interfaces.d.ts.map +0 -1
  230. package/dist/docs/docMockServices/interfaces.js +0 -1
  231. package/dist/docs/docMockServices/personaEmpresaService.d.ts +0 -43
  232. package/dist/docs/docMockServices/personaEmpresaService.d.ts.map +0 -1
  233. package/dist/docs/docMockServices/personaEmpresaService.js +0 -151
  234. package/dist/docs/docMockServices/personaService.d.ts +0 -39
  235. package/dist/docs/docMockServices/personaService.d.ts.map +0 -1
  236. package/dist/docs/docMockServices/personaService.js +0 -190
  237. package/dist/helpers/currencyFormat.js +0 -3
  238. package/dist/helpers/getErrorMessage.js +0 -13
  239. package/dist/helpers/getInitialLetters.js +0 -5
  240. package/dist/helpers/getQueryString.js +0 -13
  241. package/dist/helpers/index.js +0 -9
  242. package/dist/helpers/mappers.js +0 -27
  243. package/dist/helpers/nameValueArrayToObject.js +0 -3
  244. package/dist/helpers/objectToQueryString.js +0 -3
  245. package/dist/helpers/queryStringToObject.js +0 -13
  246. package/dist/helpers/regularExpressions.js +0 -5
  247. package/dist/hooks/index.js +0 -6
  248. package/dist/hooks/useAsyncRequest.js +0 -53
  249. package/dist/hooks/useBreakpoint.js +0 -59
  250. package/dist/hooks/useElementScroll.js +0 -58
  251. package/dist/hooks/useEnum.js +0 -21
  252. package/dist/hooks/useGlobalThemeStyles.js +0 -40
  253. package/dist/hooks/useThemeOverride.js +0 -99
  254. package/dist/interfaces/index.js +0 -1
  255. package/dist/interfaces/name-value.interface.js +0 -1
  256. package/dist/interfaces/pagination.interface.js +0 -1
  257. package/dist/main.d.ts +0 -2
  258. package/dist/main.d.ts.map +0 -1
  259. package/dist/main.js +0 -6
  260. package/dist/services/apiClient.js +0 -216
  261. package/dist/services/index.js +0 -1
  262. package/dist/styles.d.ts +0 -2
  263. package/dist/styles.d.ts.map +0 -1
  264. package/dist/styles.js +0 -3
  265. package/dist/templates/forms/ContactForm.js +0 -58
  266. package/dist/templates/forms/LoginForm.js +0 -36
  267. package/dist/templates/forms/RegistrationForm.js +0 -54
  268. package/dist/templates/layouts/DashboardLayout.js +0 -26
  269. package/dist/templates/layouts/SidebarLayout.js +0 -28
  270. package/dist/templates/patterns/FormPattern.js +0 -68
@@ -1,54 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { twMerge } from "tailwind-merge";
4
- export const Card = ({ title, subtitle, children, className = "", headerActions, footer, variant = "default", alwaysDisplayHeaderActions = false, headerClassName = "", contentClassName = "", footerClassName = "", compact = false, }) => {
5
- const variantClasses = {
6
- default: "border-[var(--color-border-default)]",
7
- elevated: "border-[var(--color-border-default)] shadow-[var(--shadow-lg)]",
8
- outlined: "border-[var(--color-gray-300)]",
9
- };
10
- // Unimos las clases usando twMerge para consistencia
11
- const mergedClasses = twMerge("bg-[var(--color-bg-default)] rounded-lg border font-[var(--font-default)]", variantClasses[variant], className);
12
- // Verificamos si existe alguna clase de ancho (w-*) que no sea w-auto.
13
- // Es importante distinguir entre w-* (ancho) y max-w-*/min-w-* (límites),
14
- // ya que un max-w-* sin un w-full puede hacer que la card colapse a su contenido.
15
- const hasExplicitWidth = mergedClasses.split(/\s+/).some((cls) => {
16
- const mainClass = cls.split(":").pop() || "";
17
- return (mainClass.startsWith("w-") &&
18
- mainClass !== "w-auto" &&
19
- !mainClass.startsWith("max-w-") &&
20
- !mainClass.startsWith("min-w-"));
21
- });
22
- // Si no hay un ancho explícito, forzamos w-full para que ocupe todo el espacio disponible
23
- // (incluyendo el espacio limitado por un posible max-w- en la misma card o en su padre).
24
- const classes = hasExplicitWidth ? mergedClasses : `${mergedClasses} w-full`;
25
- const [isHovered, setIsHovered] = React.useState(false);
26
- const [isLargeScreen, setIsLargeScreen] = React.useState(false);
27
- React.useEffect(() => {
28
- const checkScreenSize = () => {
29
- // md breakpoint en Tailwind es 768px, así que lg es 1024px
30
- setIsLargeScreen(window.innerWidth >= 1024);
31
- };
32
- checkScreenSize();
33
- window.addEventListener("resize", checkScreenSize);
34
- return () => {
35
- window.removeEventListener("resize", checkScreenSize);
36
- };
37
- }, []);
38
- // Determinar la opacidad de las headerActions
39
- const getHeaderActionsOpacity = () => {
40
- if (!headerActions)
41
- return 0;
42
- // En pantallas pequeñas (md e inferiores) siempre se muestran
43
- if (!isLargeScreen)
44
- return 1;
45
- // Si alwaysDisplayHeaderActions es true, siempre se muestran
46
- if (alwaysDisplayHeaderActions)
47
- return 1;
48
- // Si es false y pantalla grande, solo al hacer hover
49
- return isHovered ? 1 : 0;
50
- };
51
- return (_jsxs("div", { className: `${classes} relative`, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [(title || subtitle || headerActions) && (_jsx("div", { className: twMerge(compact ? "px-4 pt-2" : "px-6 pt-4", headerClassName), children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [title && (_jsx("h3", { className: "text-lg font-semibold text-[var(--color-text-primary)]", children: title })), subtitle && (_jsx("div", { className: "text-sm text-[var(--color-text-secondary)] mt-1", children: subtitle }))] }), headerActions && (_jsx("div", { className: "flex items-center transition-opacity", style: {
52
- opacity: getHeaderActionsOpacity(),
53
- }, children: headerActions }))] }) })), children && (_jsx("div", { className: twMerge(compact ? "px-4 py-4" : "px-6 py-4", contentClassName), children: children })), footer && (_jsx("div", { className: twMerge(compact ? "px-4 pb-2" : "px-6 pb-4", "flex items-center justify-end", footerClassName), children: footer }))] }));
54
- };
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from "react";
3
- export const Collection = ({ children, gap = "1rem", direction = "column", wrap = false, className = "", }) => {
4
- const baseClasses = `
5
- flex
6
- font-[var(--font-default)]
7
- `;
8
- const directionClasses = {
9
- column: "flex-col",
10
- row: "flex-row",
11
- };
12
- const wrapClass = wrap ? "flex-wrap" : "flex-nowrap";
13
- const classes = `${baseClasses} ${directionClasses[direction]} ${wrapClass} ${className}`.trim();
14
- const style = {
15
- gap: gap,
16
- };
17
- return (_jsx("div", { className: classes, style: style, children: children }));
18
- };
@@ -1,38 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Button } from "../form-controls";
4
- export const DataField = ({ label, value, inline = false, align = "left", title, link, className = "", labelClassName = "", }) => {
5
- const handleLinkClick = () => {
6
- if (link) {
7
- window.open(link, "_blank", "noopener,noreferrer");
8
- }
9
- };
10
- const alignClasses = {
11
- left: "text-left",
12
- right: "text-right",
13
- center: "text-center",
14
- };
15
- const justifyClasses = {
16
- left: "justify-start",
17
- right: "justify-end",
18
- center: "justify-center",
19
- };
20
- const baseContainerClasses = `
21
- font-[var(--font-default)]
22
- ${alignClasses[align]}
23
- ${className}
24
- `.trim();
25
- const baseLabelClasses = `
26
- text-sm text-[var(--color-text-primary)]
27
- ${labelClassName}
28
- `.trim();
29
- const baseValueClasses = `
30
- text-base text-[var(--color-text-primary)]
31
- `;
32
- if (inline) {
33
- // Modo inline: label y value en la misma línea
34
- return (_jsx("div", { className: baseContainerClasses, title: title, children: _jsxs("div", { className: `flex items-center gap-2 ${justifyClasses[align]}`, children: [label && _jsxs("span", { className: baseLabelClasses, children: [label, ":"] }), _jsx("span", { className: baseValueClasses, children: value }), link && (_jsx(Button, { size: "sm", variant: "ghost", icon: "fa-arrow-right", onClick: handleLinkClick, "aria-label": "Abrir enlace" }))] }) }));
35
- }
36
- // Modo vertical: label arriba, value abajo
37
- return (_jsxs("div", { className: baseContainerClasses, title: title, children: [label && _jsx("div", { className: baseLabelClasses, children: label }), _jsxs("div", { className: `flex items-center gap-2 ${justifyClasses[align]}`, children: [_jsx("div", { className: baseValueClasses, children: value }), link && (_jsx(Button, { size: "sm", variant: "ghost", icon: "fa-arrow-right", onClick: handleLinkClick, "aria-label": "Abrir enlace" }))] })] }));
38
- };
@@ -1,164 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { twMerge } from "tailwind-merge";
4
- import { DropdownMenu } from "./DropdownMenu";
5
- export const DataTable = ({ columns, rows, className = "", maxRows, locale = "es-AR", isLoading = false, loadingRows = 5, rowClassName, headerClassName = "", footerClassName = "", headerCellClassName = "", footerCellClassName = "", cellClassName = "", compact = false, }) => {
6
- // Calcular si necesitamos scroll
7
- const displayRows = isLoading ? loadingRows : rows.length;
8
- const needsScroll = maxRows !== undefined && displayRows > maxRows;
9
- // Altura aproximada de una fila (px-4 py-3 = ~48px por fila, compact es menos)
10
- const rowHeight = compact ? 32 : 48;
11
- const maxHeight = maxRows ? `${maxRows * rowHeight}px` : undefined;
12
- const cellPadding = compact ? "px-2 py-1" : "px-4 py-3";
13
- // Verificar si alguna columna tiene footer
14
- const hasFooter = columns.some((column) => column.footer !== undefined);
15
- const getCellValue = (column, row) => {
16
- if (!column.value)
17
- return null;
18
- if (typeof column.value === "function") {
19
- return column.value(row);
20
- }
21
- // Si es string o number, puede ser un nombre de propiedad o un valor directo
22
- if (typeof column.value === "string" || typeof column.value === "number") {
23
- // Intentar obtener la propiedad del objeto si existe
24
- if (typeof column.value === "string" &&
25
- typeof row === "object" &&
26
- row !== null) {
27
- const value = row[column.value];
28
- if (value !== undefined) {
29
- return value;
30
- }
31
- }
32
- // Si no es una propiedad, retornar el valor directo
33
- return column.value;
34
- }
35
- return column.value;
36
- };
37
- const formatValue = (value, type) => {
38
- if (React.isValidElement(value)) {
39
- return value;
40
- }
41
- // Convertir string a número si es necesario para currency o numeric
42
- let numericValue = null;
43
- if (typeof value === "number") {
44
- numericValue = value;
45
- }
46
- else if (typeof value === "string" &&
47
- (type === "currency" || type === "numeric")) {
48
- const parsed = parseFloat(value);
49
- if (!isNaN(parsed)) {
50
- numericValue = parsed;
51
- }
52
- }
53
- if (numericValue !== null) {
54
- if (type === "currency") {
55
- // Formatear usando el locale proporcionado sin símbolo de moneda
56
- const parts = new Intl.NumberFormat(locale, {
57
- style: "currency",
58
- currency: "EUR",
59
- minimumFractionDigits: 2,
60
- maximumFractionDigits: 2,
61
- }).formatToParts(numericValue);
62
- // Construir el string sin el símbolo de moneda
63
- return parts
64
- .filter((part) => part.type !== "currency")
65
- .map((part) => part.value)
66
- .join("");
67
- }
68
- if (type === "numeric") {
69
- // Formatear usando el locale proporcionado
70
- const hasDecimals = numericValue % 1 !== 0;
71
- return new Intl.NumberFormat(locale, {
72
- minimumFractionDigits: hasDecimals ? 2 : 0,
73
- maximumFractionDigits: hasDecimals ? 2 : 0,
74
- }).format(numericValue);
75
- }
76
- }
77
- if (typeof value === "string" && type === "date") {
78
- try {
79
- const date = new Date(value);
80
- return date.toLocaleDateString(locale);
81
- }
82
- catch {
83
- return value;
84
- }
85
- }
86
- return value;
87
- };
88
- const getAlignmentClass = (align, type) => {
89
- // Las columnas de tipo 'date' siempre se alinean a la izquierda
90
- // Las columnas de tipo 'currency' y 'numeric' siempre se alinean a la derecha
91
- let effectiveAlign = align;
92
- if (type === "date") {
93
- effectiveAlign = "left";
94
- }
95
- else if (type === "currency" || type === "numeric") {
96
- effectiveAlign = "right";
97
- }
98
- switch (effectiveAlign) {
99
- case "right":
100
- return "text-right";
101
- case "center":
102
- return "text-center";
103
- case "left":
104
- default:
105
- return "text-left";
106
- }
107
- };
108
- // Convertir array de ReactNode a array de ActionItem para DropdownMenu
109
- const convertActionsToOptions = (actions) => {
110
- return actions.map((action, index) => ({
111
- id: index,
112
- content: (_jsx("div", { onClick: (e) => {
113
- // Detener la propagación para que el onClick del DropdownMenu no interfiera
114
- e.stopPropagation();
115
- }, children: action })),
116
- }));
117
- };
118
- // Componente Skeleton para celdas de carga
119
- const SkeletonCell = () => (_jsx("div", { className: "h-4 bg-[var(--color-border-default)]/40 rounded animate-pulse w-full" }));
120
- return (_jsx("div", { className: `overflow-x-auto ${className}`, children: _jsx("div", { className: needsScroll ? "relative overflow-y-auto" : "", style: needsScroll && maxHeight ? { maxHeight: maxHeight } : undefined, children: _jsxs("table", { className: "w-full border-collapse font-[var(--font-default)]", children: [_jsx("thead", { className: needsScroll ? "sticky top-0 z-10" : "", children: _jsx("tr", { className: twMerge("border-b border-[var(--color-border-default)] text-[var(--color-text-primary)]", headerClassName), children: columns.map((column, index) => {
121
- const headerActions = column.headerActions?.();
122
- const hasHeaderActions = headerActions && headerActions.length > 0;
123
- const headerBgClasses = headerClassName
124
- .split(/\s+/)
125
- .filter((cls) => cls.split(":").pop()?.startsWith("bg-"))
126
- .join(" ");
127
- return (_jsx("th", { className: twMerge(cellPadding, "text-sm font-semibold", headerBgClasses || "bg-[var(--color-bg-secondary)]", getAlignmentClass(column.align, column.type), hasHeaderActions ? "relative" : "", headerCellClassName), style: {
128
- ...(column.width ? { width: column.width } : {}),
129
- }, children: isLoading ? (_jsx(SkeletonCell, {})) : hasHeaderActions ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { children: column.header || "" }), _jsx(DropdownMenu, { options: convertActionsToOptions(headerActions), onOptionSelected: () => {
130
- // Las acciones ya manejan sus propios eventos
131
- }, renderOption: (item) => item.content, replaceOnSingleOption: true })] })) : (column.header || "") }, index));
132
- }) }) }), _jsx("tbody", { children: isLoading
133
- ? Array.from({ length: loadingRows }).map((_, rowIndex) => (_jsx("tr", { className: "border-b border-[var(--color-border-default)] text-[var(--color-text-primary)]", children: columns.map((column, colIndex) => (_jsx("td", { className: twMerge(cellPadding, "text-sm", getAlignmentClass(column.align, column.type)), style: {
134
- ...(column.width ? { width: column.width } : {}),
135
- }, children: _jsx(SkeletonCell, {}) }, colIndex))) }, `skeleton-${rowIndex}`)))
136
- : rows.map((row, rowIndex) => (_jsx("tr", { className: twMerge("group/row border-b border-[var(--color-border-default)] transition-colors hover:bg-[var(--color-bg-secondary)] text-[var(--color-text-primary)]", rowClassName?.(row)), children: columns.map((column, colIndex) => {
137
- const cellValue = getCellValue(column, row);
138
- const formattedValue = formatValue(cellValue, column.type);
139
- const tooltip = column.tooltip
140
- ? column.tooltip(row)
141
- : undefined;
142
- const rowActions = column.actions?.(row);
143
- const hasRowActions = rowActions && rowActions.length > 0;
144
- return (_jsx("td", { className: twMerge(cellPadding, "text-sm", getAlignmentClass(column.align, column.type), typeof cellClassName === "function"
145
- ? cellClassName(row, column)
146
- : cellClassName), style: {
147
- ...(column.width ? { width: column.width } : {}),
148
- }, title: tooltip
149
- ? typeof tooltip === "string"
150
- ? tooltip
151
- : undefined
152
- : undefined, children: hasRowActions ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { children: formattedValue }), _jsx("div", { className: "lg:opacity-0 lg:group-hover/row:opacity-100 transition-opacity", children: _jsx(DropdownMenu, { options: convertActionsToOptions(rowActions), onOptionSelected: () => {
153
- // Las acciones ya manejan sus propios eventos
154
- }, renderOption: (item) => item.content, replaceOnSingleOption: true }) })] })) : (formattedValue) }, colIndex));
155
- }) }, rowIndex))) }), hasFooter && (_jsx("tfoot", { className: needsScroll ? "sticky bottom-0 z-10" : "", children: _jsx("tr", { className: twMerge("border-t border-[var(--color-border-default)] text-[var(--color-text-primary)]", footerClassName), children: columns.map((column, index) => {
156
- const footerBgClasses = footerClassName
157
- .split(/\s+/)
158
- .filter((cls) => cls.split(":").pop()?.startsWith("bg-"))
159
- .join(" ");
160
- return (_jsx("td", { className: twMerge(cellPadding, "text-sm font-semibold", footerBgClasses || "bg-[var(--color-bg-secondary)]", getAlignmentClass(column.align, column.type), footerCellClassName), style: {
161
- ...(column.width ? { width: column.width } : {}),
162
- }, children: isLoading ? _jsx(SkeletonCell, {}) : column.footer || "" }, index));
163
- }) }) }))] }) }) }));
164
- };
@@ -1,176 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useState, useRef, useEffect, useCallback, useMemo, } from "react";
3
- import { createPortal } from "react-dom";
4
- import { Button } from "../form-controls/Button";
5
- export const DropdownMenu = ({ options, onOptionSelected, renderNode, getOptionLabel, renderOption, replaceOnSingleOption = false, openOnHover = false, }) => {
6
- const [isOpen, setIsOpen] = useState(false);
7
- const [menuPosition, setMenuPosition] = useState("bottom");
8
- const [scrollUpdate, setScrollUpdate] = useState(0);
9
- const triggerRef = useRef(null);
10
- const menuRef = useRef(null);
11
- const hoverTimeoutRef = useRef(null);
12
- // Calcular posición del menú
13
- const calculatePosition = useCallback(() => {
14
- if (isOpen && triggerRef.current) {
15
- const triggerRect = triggerRef.current.getBoundingClientRect();
16
- const viewportHeight = window.innerHeight;
17
- // Estimar altura del menú (aproximadamente 40px por opción + padding)
18
- const estimatedMenuHeight = options.length * 40 + 16;
19
- const menuMargin = 4;
20
- // Calcular espacio disponible arriba y abajo
21
- const spaceBelow = viewportHeight - triggerRect.bottom - menuMargin;
22
- const spaceAbove = triggerRect.top - menuMargin;
23
- // Si no hay suficiente espacio abajo pero sí arriba, mostrar arriba
24
- // Usamos un margen de seguridad para asegurar que el menú quepa
25
- if (spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow) {
26
- setMenuPosition("top");
27
- }
28
- else {
29
- setMenuPosition("bottom");
30
- }
31
- }
32
- }, [isOpen, options.length]);
33
- useEffect(() => {
34
- calculatePosition();
35
- }, [calculatePosition]);
36
- // Recalcular posición al hacer scroll o redimensionar
37
- useEffect(() => {
38
- if (isOpen) {
39
- const handleScroll = () => {
40
- calculatePosition();
41
- // Forzar actualización del estilo del menú
42
- setScrollUpdate((prev) => prev + 1);
43
- };
44
- window.addEventListener("scroll", handleScroll, true);
45
- window.addEventListener("resize", handleScroll);
46
- return () => {
47
- window.removeEventListener("scroll", handleScroll, true);
48
- window.removeEventListener("resize", handleScroll);
49
- };
50
- }
51
- }, [isOpen, calculatePosition]);
52
- // Cerrar menú al hacer click fuera
53
- useEffect(() => {
54
- const handleClickOutside = (event) => {
55
- if (isOpen &&
56
- triggerRef.current &&
57
- menuRef.current &&
58
- !triggerRef.current.contains(event.target) &&
59
- !menuRef.current.contains(event.target)) {
60
- setIsOpen(false);
61
- }
62
- };
63
- if (isOpen) {
64
- document.addEventListener("mousedown", handleClickOutside);
65
- }
66
- return () => {
67
- document.removeEventListener("mousedown", handleClickOutside);
68
- };
69
- }, [isOpen]);
70
- // Cerrar menú al presionar Escape
71
- useEffect(() => {
72
- const handleEscape = (event) => {
73
- if (event.key === "Escape" && isOpen) {
74
- setIsOpen(false);
75
- }
76
- };
77
- if (isOpen) {
78
- document.addEventListener("keydown", handleEscape);
79
- }
80
- return () => {
81
- document.removeEventListener("keydown", handleEscape);
82
- };
83
- }, [isOpen]);
84
- // Limpiar timeout al desmontar
85
- useEffect(() => {
86
- return () => {
87
- if (hoverTimeoutRef.current) {
88
- window.clearTimeout(hoverTimeoutRef.current);
89
- }
90
- };
91
- }, []);
92
- const labelGetter = useCallback((item) => {
93
- if (getOptionLabel)
94
- return getOptionLabel(item);
95
- const anyItem = item;
96
- return (anyItem.label ?? "").toString();
97
- }, [getOptionLabel]);
98
- const handleToggle = () => {
99
- setIsOpen(!isOpen);
100
- };
101
- const handleMouseEnter = () => {
102
- if (!openOnHover)
103
- return;
104
- if (hoverTimeoutRef.current) {
105
- window.clearTimeout(hoverTimeoutRef.current);
106
- hoverTimeoutRef.current = null;
107
- }
108
- setIsOpen(true);
109
- };
110
- const handleMouseLeave = () => {
111
- if (!openOnHover)
112
- return;
113
- hoverTimeoutRef.current = window.setTimeout(() => {
114
- setIsOpen(false);
115
- }, 150); // Pequeño delay para permitir mover el mouse al menú
116
- };
117
- const handleOptionClick = (item) => {
118
- setIsOpen(false);
119
- onOptionSelected(item);
120
- };
121
- // Si replaceOnSingleOption es true y hay una sola opción, mostrar directamente la opción
122
- const shouldReplace = replaceOnSingleOption && options.length === 1;
123
- const singleOption = shouldReplace ? options[0] : null;
124
- const menuStyles = useMemo(() => {
125
- if (!isOpen || !triggerRef.current) {
126
- return {};
127
- }
128
- const triggerRect = triggerRef.current.getBoundingClientRect();
129
- const menuMargin = 4;
130
- // Asegurar que el menú no se salga de la pantalla horizontalmente
131
- let leftPosition = triggerRect.left;
132
- const menuMinWidth = 160;
133
- const viewportWidth = window.innerWidth;
134
- // Si el menú se sale por la derecha, ajustar la posición
135
- if (leftPosition + menuMinWidth > viewportWidth) {
136
- leftPosition = viewportWidth - menuMinWidth - 8; // 8px de margen
137
- }
138
- // Asegurar que no se salga por la izquierda
139
- if (leftPosition < 8) {
140
- leftPosition = 8;
141
- }
142
- if (menuPosition === "top") {
143
- return {
144
- position: "fixed",
145
- bottom: window.innerHeight - triggerRect.top + menuMargin,
146
- left: leftPosition,
147
- minWidth: Math.max(triggerRect.width, menuMinWidth),
148
- };
149
- }
150
- else {
151
- return {
152
- position: "fixed",
153
- top: triggerRect.bottom + menuMargin,
154
- left: leftPosition,
155
- minWidth: Math.max(triggerRect.width, menuMinWidth),
156
- };
157
- }
158
- // scrollUpdate se usa intencionalmente para forzar el recálculo en scroll
159
- // eslint-disable-next-line react-hooks/exhaustive-deps
160
- }, [isOpen, menuPosition, scrollUpdate]);
161
- // Si debe reemplazar con la opción única, mostrar directamente la opción
162
- if (shouldReplace && singleOption) {
163
- return (_jsx("div", { onClick: () => handleOptionClick(singleOption), className: "cursor-pointer", children: renderOption ? renderOption(singleOption) : labelGetter(singleOption) }));
164
- }
165
- return (_jsxs("div", { className: "relative inline-block", ref: triggerRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [_jsx("div", { onClick: handleToggle, className: "cursor-pointer", children: renderNode ? (renderNode) : (_jsx(Button, { variant: "ghost", icon: "fa-ellipsis-h" })) }), isOpen &&
166
- (typeof document !== "undefined" && document.body
167
- ? createPortal(_jsx("div", { ref: menuRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: (e) => e.stopPropagation(), className: "fixed z-[2000] bg-[var(--color-bg-default)] border border-[var(--color-border-default)] rounded-md shadow-[var(--shadow-lg)] py-1 min-w-[160px] font-[var(--font-default)]", style: menuStyles, children: options.map((option, index) => {
168
- const key = String(option?.id ??
169
- labelGetter(option) ??
170
- index);
171
- return (_jsx("div", { onClick: () => handleOptionClick(option), className: "px-4 py-2 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] cursor-pointer transition-colors flex items-center", children: renderOption
172
- ? renderOption(option)
173
- : labelGetter(option) }, key));
174
- }) }), document.body)
175
- : null)] }));
176
- };
@@ -1,162 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useState, useRef, useEffect, useCallback, useMemo, } from "react";
3
- import { createPortal } from "react-dom";
4
- import { Button } from "../form-controls/Button";
5
- export const DropdownPanel = ({ renderNode, children, openOnHover = false, }) => {
6
- const [isOpen, setIsOpen] = useState(false);
7
- const [menuPosition, setMenuPosition] = useState("bottom");
8
- const [scrollUpdate, setScrollUpdate] = useState(0);
9
- const triggerRef = useRef(null);
10
- const menuRef = useRef(null);
11
- const hoverTimeoutRef = useRef(null);
12
- // Calcular posición del menú
13
- const calculatePosition = useCallback(() => {
14
- if (isOpen && triggerRef.current) {
15
- const triggerRect = triggerRef.current.getBoundingClientRect();
16
- const viewportHeight = window.innerHeight;
17
- // Intentar obtener la altura real del menú, o usar una estimación si no está montado aún
18
- let menuHeight = 200; // valor por defecto
19
- if (menuRef.current) {
20
- menuHeight = menuRef.current.getBoundingClientRect().height;
21
- }
22
- const menuMargin = 4;
23
- // Calcular espacio disponible arriba y abajo
24
- const spaceBelow = viewportHeight - triggerRect.bottom - menuMargin;
25
- const spaceAbove = triggerRect.top - menuMargin;
26
- // Si no hay suficiente espacio abajo pero sí arriba, mostrar arriba
27
- if (spaceBelow < menuHeight && spaceAbove > spaceBelow) {
28
- setMenuPosition("top");
29
- }
30
- else {
31
- setMenuPosition("bottom"); // Preferir abajo si cabe o si es el que más espacio tiene, o por defecto
32
- }
33
- }
34
- }, [isOpen]);
35
- // Recalcular posición cuando cambia isOpen (y cuando el contenido podría haber cambiado el tamaño)
36
- useEffect(() => {
37
- calculatePosition();
38
- // Podríamos necesitar un ResizeObserver para ser más robustos si el contenido cambia
39
- }, [calculatePosition]);
40
- // Recalcular posición al hacer scroll o redimensionar
41
- useEffect(() => {
42
- if (isOpen) {
43
- const handleScroll = () => {
44
- calculatePosition();
45
- // Forzar actualización del estilo del menú
46
- setScrollUpdate((prev) => prev + 1);
47
- };
48
- window.addEventListener("scroll", handleScroll, true);
49
- window.addEventListener("resize", handleScroll);
50
- return () => {
51
- window.removeEventListener("scroll", handleScroll, true);
52
- window.removeEventListener("resize", handleScroll);
53
- };
54
- }
55
- }, [isOpen, calculatePosition]);
56
- // Cerrar menú al hacer click fuera
57
- useEffect(() => {
58
- const handleClickOutside = (event) => {
59
- if (isOpen &&
60
- triggerRef.current &&
61
- menuRef.current &&
62
- !triggerRef.current.contains(event.target) &&
63
- !menuRef.current.contains(event.target)) {
64
- setIsOpen(false);
65
- }
66
- };
67
- if (isOpen) {
68
- document.addEventListener("mousedown", handleClickOutside);
69
- }
70
- return () => {
71
- document.removeEventListener("mousedown", handleClickOutside);
72
- };
73
- }, [isOpen]);
74
- // Cerrar menú al presionar Escape
75
- useEffect(() => {
76
- const handleEscape = (event) => {
77
- if (event.key === "Escape" && isOpen) {
78
- setIsOpen(false);
79
- }
80
- };
81
- if (isOpen) {
82
- document.addEventListener("keydown", handleEscape);
83
- }
84
- return () => {
85
- document.removeEventListener("keydown", handleEscape);
86
- };
87
- }, [isOpen]);
88
- // Limpiar timeout al desmontar
89
- useEffect(() => {
90
- return () => {
91
- if (hoverTimeoutRef.current) {
92
- window.clearTimeout(hoverTimeoutRef.current);
93
- }
94
- };
95
- }, []);
96
- const handleToggle = () => {
97
- setIsOpen(!isOpen);
98
- };
99
- const handleMouseEnter = () => {
100
- if (!openOnHover)
101
- return;
102
- if (hoverTimeoutRef.current) {
103
- window.clearTimeout(hoverTimeoutRef.current);
104
- hoverTimeoutRef.current = null;
105
- }
106
- setIsOpen(true);
107
- };
108
- const handleMouseLeave = () => {
109
- if (!openOnHover)
110
- return;
111
- hoverTimeoutRef.current = window.setTimeout(() => {
112
- setIsOpen(false);
113
- }, 150); // Pequeño delay para permitir mover el mouse al panel
114
- };
115
- const menuStyles = useMemo(() => {
116
- if (!isOpen || !triggerRef.current) {
117
- return {};
118
- }
119
- const triggerRect = triggerRef.current.getBoundingClientRect();
120
- const menuMargin = 4;
121
- // Asegurar que el menú no se salga de la pantalla horizontalmente
122
- let leftPosition = triggerRect.left;
123
- const menuMinWidth = 160;
124
- const viewportWidth = window.innerWidth;
125
- // Si el menú se sale por la derecha, ajustar la posición
126
- // Nota: Como el ancho es dinámico (basado en children), idealmente deberíamos medirlo.
127
- // Usaremos menuRef si está disponible o un estimado.
128
- let currentMenuWidth = menuMinWidth;
129
- if (menuRef.current) {
130
- currentMenuWidth = menuRef.current.getBoundingClientRect().width;
131
- }
132
- if (leftPosition + currentMenuWidth > viewportWidth) {
133
- leftPosition = viewportWidth - currentMenuWidth - 8; // 8px de margen
134
- }
135
- // Asegurar que no se salga por la izquierda
136
- if (leftPosition < 8) {
137
- leftPosition = 8;
138
- }
139
- if (menuPosition === "top") {
140
- return {
141
- position: "fixed",
142
- bottom: window.innerHeight - triggerRect.top + menuMargin,
143
- left: leftPosition,
144
- minWidth: Math.max(triggerRect.width, menuMinWidth), // Mantener el minWidth del trigger o 160
145
- };
146
- }
147
- else {
148
- return {
149
- position: "fixed",
150
- top: triggerRect.bottom + menuMargin,
151
- left: leftPosition,
152
- minWidth: Math.max(triggerRect.width, menuMinWidth),
153
- };
154
- }
155
- // scrollUpdate se usa intencionalmente para forzar el recálculo en scroll
156
- // eslint-disable-next-line react-hooks/exhaustive-deps
157
- }, [isOpen, menuPosition, scrollUpdate]);
158
- return (_jsxs("div", { className: "relative inline-block", ref: triggerRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [_jsx("div", { onClick: handleToggle, className: "cursor-pointer", children: renderNode ? (renderNode) : (_jsx(Button, { variant: "ghost", icon: "fa-ellipsis-h" })) }), isOpen &&
159
- (typeof document !== "undefined" && document.body
160
- ? createPortal(_jsx("div", { ref: menuRef, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: (e) => e.stopPropagation(), className: "fixed z-[2000] bg-[var(--color-bg-default)] border border-[var(--color-border-default)] rounded-md shadow-[var(--shadow-lg)] py-1 min-w-[160px] font-[var(--font-default)]", style: menuStyles, children: children }), document.body)
161
- : null)] }));
162
- };