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.
- package/AI_CONTEXT.md +1400 -217
- package/AI_INTEGRATION_GUIDE.md +343 -0
- package/INTEGRATION_GUIDE.md +60 -0
- package/README.md +5 -3
- package/dist/components/form-controls/Input.d.ts.map +1 -1
- package/dist/components/form-controls/index.d.ts +2 -2
- package/dist/components/form-controls/index.d.ts.map +1 -1
- package/dist/components/layout/Accordion.d.ts +1 -0
- package/dist/components/layout/Accordion.d.ts.map +1 -1
- package/dist/components/layout/DataTable.d.ts.map +1 -1
- package/dist/components/layout/DropdownMenu.d.ts +2 -1
- package/dist/components/layout/DropdownMenu.d.ts.map +1 -1
- package/dist/components/layout/DropdownPanel.d.ts +2 -1
- package/dist/components/layout/DropdownPanel.d.ts.map +1 -1
- package/dist/components/layout/Filter.d.ts +1 -0
- package/dist/components/layout/Filter.d.ts.map +1 -1
- package/dist/components/layout/Menu.d.ts +2 -1
- package/dist/components/layout/Menu.d.ts.map +1 -1
- package/dist/components/layout/TabsGroup.d.ts +1 -0
- package/dist/components/layout/TabsGroup.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11889 -24
- package/dist/index.js.map +1 -1
- package/dist/templates/forms/ContactForm.d.ts +1 -0
- package/dist/templates/forms/ContactForm.d.ts.map +1 -1
- package/dist/templates/forms/LoginForm.d.ts +1 -0
- package/dist/templates/forms/LoginForm.d.ts.map +1 -1
- package/dist/templates/forms/RegistrationForm.d.ts +1 -0
- package/dist/templates/forms/RegistrationForm.d.ts.map +1 -1
- package/dist/templates/layouts/DashboardLayout.d.ts +1 -0
- package/dist/templates/layouts/DashboardLayout.d.ts.map +1 -1
- package/dist/templates/layouts/SidebarLayout.d.ts +1 -0
- package/dist/templates/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/templates/patterns/FormPattern.d.ts +1 -0
- package/dist/templates/patterns/FormPattern.d.ts.map +1 -1
- package/dist/templates/patterns/ListPattern.d.ts +77 -0
- package/dist/templates/patterns/ListPattern.d.ts.map +1 -0
- package/package.json +6 -3
- package/dist/App.d.ts +0 -4
- package/dist/App.d.ts.map +0 -1
- package/dist/App.js +0 -30
- package/dist/components/ThemeSwitcher.js +0 -12
- package/dist/components/form-controls/AutocompleteInput.js +0 -680
- package/dist/components/form-controls/Button.js +0 -211
- package/dist/components/form-controls/Checkbox.js +0 -79
- package/dist/components/form-controls/CurrencyInput.js +0 -106
- package/dist/components/form-controls/DateInput.js +0 -578
- package/dist/components/form-controls/DatePicker.js +0 -144
- package/dist/components/form-controls/Input.js +0 -35
- package/dist/components/form-controls/LinkButton.js +0 -248
- package/dist/components/form-controls/Pagination.js +0 -23
- package/dist/components/form-controls/RadioButtonGroup.js +0 -220
- package/dist/components/form-controls/SearchSelectInput-OLD.d.ts +0 -68
- package/dist/components/form-controls/SearchSelectInput-OLD.d.ts.map +0 -1
- package/dist/components/form-controls/SearchSelectInput-OLD.js +0 -962
- package/dist/components/form-controls/SearchSelectInput.js +0 -336
- package/dist/components/form-controls/index.js +0 -11
- package/dist/components/index.js +0 -7
- package/dist/components/layout/Accordion.js +0 -67
- package/dist/components/layout/AppLayout.js +0 -230
- package/dist/components/layout/Card.js +0 -54
- package/dist/components/layout/Collection.js +0 -18
- package/dist/components/layout/DataField.js +0 -38
- package/dist/components/layout/DataTable.js +0 -164
- package/dist/components/layout/DropdownMenu.js +0 -176
- package/dist/components/layout/DropdownPanel.js +0 -162
- package/dist/components/layout/Filter.js +0 -629
- package/dist/components/layout/Menu.js +0 -21
- package/dist/components/layout/TabPanel.js +0 -11
- package/dist/components/layout/TabsGroup.js +0 -52
- package/dist/components/layout/index.js +0 -12
- package/dist/components/utils/Avatar.js +0 -77
- package/dist/components/utils/Badge.js +0 -151
- package/dist/components/utils/Dialog.js +0 -44
- package/dist/components/utils/FiltersDialog.js +0 -104
- package/dist/components/utils/Loader.js +0 -44
- package/dist/components/utils/RoadMap.js +0 -139
- package/dist/components/utils/Skeleton.js +0 -10
- package/dist/components/utils/Snackbar.js +0 -136
- package/dist/components/utils/SnackbarContainer.js +0 -26
- package/dist/components/utils/iconUtils.js +0 -40
- package/dist/components/utils/index.js +0 -9
- package/dist/contexts/AppLayoutContext.js +0 -104
- package/dist/contexts/AuthContext.js +0 -224
- package/dist/contexts/CrudContext.js +0 -333
- package/dist/contexts/SnackbarContext.js +0 -41
- package/dist/contexts/ThemeContext.js +0 -197
- package/dist/contexts/index.js +0 -13
- package/dist/contexts/presets.js +0 -311
- package/dist/contexts/types.js +0 -1
- package/dist/docs/AccordionDocs.d.ts +0 -4
- package/dist/docs/AccordionDocs.d.ts.map +0 -1
- package/dist/docs/AccordionDocs.js +0 -21
- package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts +0 -13
- package/dist/docs/AuthDocs.tsx/AuthDocs.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/AuthDocs.js +0 -18
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts +0 -2
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/AuthDocsContent.js +0 -22
- package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts +0 -24
- package/dist/docs/AuthDocs.tsx/mockAuthService.d.ts.map +0 -1
- package/dist/docs/AuthDocs.tsx/mockAuthService.js +0 -78
- package/dist/docs/AutocompleteInputDocs.d.ts +0 -4
- package/dist/docs/AutocompleteInputDocs.d.ts.map +0 -1
- package/dist/docs/AutocompleteInputDocs.js +0 -84
- package/dist/docs/AvatarDocs.d.ts +0 -4
- package/dist/docs/AvatarDocs.d.ts.map +0 -1
- package/dist/docs/AvatarDocs.js +0 -7
- package/dist/docs/BadgeDocs.d.ts +0 -4
- package/dist/docs/BadgeDocs.d.ts.map +0 -1
- package/dist/docs/BadgeDocs.js +0 -9
- package/dist/docs/ButtonDocs.d.ts +0 -4
- package/dist/docs/ButtonDocs.d.ts.map +0 -1
- package/dist/docs/ButtonDocs.js +0 -7
- package/dist/docs/CardDocs.d.ts +0 -4
- package/dist/docs/CardDocs.d.ts.map +0 -1
- package/dist/docs/CardDocs.js +0 -22
- package/dist/docs/CheckboxDocs.d.ts +0 -4
- package/dist/docs/CheckboxDocs.d.ts.map +0 -1
- package/dist/docs/CheckboxDocs.js +0 -7
- package/dist/docs/CurrencyInputDocs.d.ts +0 -4
- package/dist/docs/CurrencyInputDocs.d.ts.map +0 -1
- package/dist/docs/CurrencyInputDocs.js +0 -22
- package/dist/docs/DataFieldDocs.d.ts +0 -4
- package/dist/docs/DataFieldDocs.d.ts.map +0 -1
- package/dist/docs/DataFieldDocs.js +0 -7
- package/dist/docs/DataTableDocs.d.ts +0 -4
- package/dist/docs/DataTableDocs.d.ts.map +0 -1
- package/dist/docs/DataTableDocs.js +0 -244
- package/dist/docs/DateInputDocs.d.ts +0 -5
- package/dist/docs/DateInputDocs.d.ts.map +0 -1
- package/dist/docs/DateInputDocs.js +0 -19
- package/dist/docs/DatePickerDocs.d.ts +0 -5
- package/dist/docs/DatePickerDocs.d.ts.map +0 -1
- package/dist/docs/DatePickerDocs.js +0 -16
- package/dist/docs/DialogDocs.d.ts +0 -4
- package/dist/docs/DialogDocs.d.ts.map +0 -1
- package/dist/docs/DialogDocs.js +0 -13
- package/dist/docs/DocAdmin.d.ts +0 -4
- package/dist/docs/DocAdmin.d.ts.map +0 -1
- package/dist/docs/DocAdmin.js +0 -68
- package/dist/docs/DocsMenu.d.ts +0 -2
- package/dist/docs/DocsMenu.d.ts.map +0 -1
- package/dist/docs/DocsMenu.js +0 -5
- package/dist/docs/DocsRouter.d.ts +0 -4
- package/dist/docs/DocsRouter.d.ts.map +0 -1
- package/dist/docs/DocsRouter.js +0 -39
- package/dist/docs/DropdownMenuDocs.d.ts +0 -4
- package/dist/docs/DropdownMenuDocs.d.ts.map +0 -1
- package/dist/docs/DropdownMenuDocs.js +0 -66
- package/dist/docs/DropdownPanelDocs.d.ts +0 -4
- package/dist/docs/DropdownPanelDocs.d.ts.map +0 -1
- package/dist/docs/DropdownPanelDocs.js +0 -7
- package/dist/docs/ExampleFormDocs.d.ts +0 -4
- package/dist/docs/ExampleFormDocs.d.ts.map +0 -1
- package/dist/docs/ExampleFormDocs.js +0 -153
- package/dist/docs/FilterDocs.d.ts +0 -4
- package/dist/docs/FilterDocs.d.ts.map +0 -1
- package/dist/docs/FilterDocs.js +0 -130
- package/dist/docs/InputDocs.d.ts +0 -4
- package/dist/docs/InputDocs.d.ts.map +0 -1
- package/dist/docs/InputDocs.js +0 -17
- package/dist/docs/LinkButtonDocs.d.ts +0 -4
- package/dist/docs/LinkButtonDocs.d.ts.map +0 -1
- package/dist/docs/LinkButtonDocs.js +0 -7
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocs.js +0 -47
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaPersonas.js +0 -34
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresaSingle.js +0 -66
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresas.js +0 -7
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts +0 -10
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentEmpresasPersonasEditDialog.js +0 -39
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts +0 -2
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsContentPersonas.js +0 -57
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts +0 -9
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.d.ts.map +0 -1
- package/dist/docs/ListCrudDocs.tsx/ListCrudDocsEditDialog.js +0 -30
- package/dist/docs/LoaderDocs.d.ts +0 -4
- package/dist/docs/LoaderDocs.d.ts.map +0 -1
- package/dist/docs/LoaderDocs.js +0 -33
- package/dist/docs/MenuDocs.d.ts +0 -4
- package/dist/docs/MenuDocs.d.ts.map +0 -1
- package/dist/docs/MenuDocs.js +0 -26
- package/dist/docs/PaginationDocs.d.ts +0 -4
- package/dist/docs/PaginationDocs.d.ts.map +0 -1
- package/dist/docs/PaginationDocs.js +0 -38
- package/dist/docs/RadioButtonGroupDocs.d.ts +0 -4
- package/dist/docs/RadioButtonGroupDocs.d.ts.map +0 -1
- package/dist/docs/RadioButtonGroupDocs.js +0 -46
- package/dist/docs/RoadMapDocs.d.ts +0 -4
- package/dist/docs/RoadMapDocs.d.ts.map +0 -1
- package/dist/docs/RoadMapDocs.js +0 -171
- package/dist/docs/SearchSelectInputDocs.d.ts +0 -4
- package/dist/docs/SearchSelectInputDocs.d.ts.map +0 -1
- package/dist/docs/SearchSelectInputDocs.js +0 -168
- package/dist/docs/SkeletonDocs.d.ts +0 -4
- package/dist/docs/SkeletonDocs.d.ts.map +0 -1
- package/dist/docs/SkeletonDocs.js +0 -7
- package/dist/docs/SnackbarDocs.d.ts +0 -4
- package/dist/docs/SnackbarDocs.d.ts.map +0 -1
- package/dist/docs/SnackbarDocs.js +0 -69
- package/dist/docs/TabsGroupDocs.d.ts +0 -4
- package/dist/docs/TabsGroupDocs.d.ts.map +0 -1
- package/dist/docs/TabsGroupDocs.js +0 -38
- package/dist/docs/ThemeSwitcherDocs.d.ts +0 -4
- package/dist/docs/ThemeSwitcherDocs.d.ts.map +0 -1
- package/dist/docs/ThemeSwitcherDocs.js +0 -11
- package/dist/docs/docMockServices/empresaService.d.ts +0 -38
- package/dist/docs/docMockServices/empresaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/empresaService.js +0 -125
- package/dist/docs/docMockServices/index.d.ts +0 -9
- package/dist/docs/docMockServices/index.d.ts.map +0 -1
- package/dist/docs/docMockServices/index.js +0 -8
- package/dist/docs/docMockServices/initialData.d.ts +0 -6
- package/dist/docs/docMockServices/initialData.d.ts.map +0 -1
- package/dist/docs/docMockServices/initialData.js +0 -132
- package/dist/docs/docMockServices/interfaces.d.ts +0 -38
- package/dist/docs/docMockServices/interfaces.d.ts.map +0 -1
- package/dist/docs/docMockServices/interfaces.js +0 -1
- package/dist/docs/docMockServices/personaEmpresaService.d.ts +0 -43
- package/dist/docs/docMockServices/personaEmpresaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/personaEmpresaService.js +0 -151
- package/dist/docs/docMockServices/personaService.d.ts +0 -39
- package/dist/docs/docMockServices/personaService.d.ts.map +0 -1
- package/dist/docs/docMockServices/personaService.js +0 -190
- package/dist/helpers/currencyFormat.js +0 -3
- package/dist/helpers/getErrorMessage.js +0 -13
- package/dist/helpers/getInitialLetters.js +0 -5
- package/dist/helpers/getQueryString.js +0 -13
- package/dist/helpers/index.js +0 -9
- package/dist/helpers/mappers.js +0 -27
- package/dist/helpers/nameValueArrayToObject.js +0 -3
- package/dist/helpers/objectToQueryString.js +0 -3
- package/dist/helpers/queryStringToObject.js +0 -13
- package/dist/helpers/regularExpressions.js +0 -5
- package/dist/hooks/index.js +0 -6
- package/dist/hooks/useAsyncRequest.js +0 -53
- package/dist/hooks/useBreakpoint.js +0 -59
- package/dist/hooks/useElementScroll.js +0 -58
- package/dist/hooks/useEnum.js +0 -21
- package/dist/hooks/useGlobalThemeStyles.js +0 -40
- package/dist/hooks/useThemeOverride.js +0 -99
- package/dist/interfaces/index.js +0 -1
- package/dist/interfaces/name-value.interface.js +0 -1
- package/dist/interfaces/pagination.interface.js +0 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js +0 -6
- package/dist/services/apiClient.js +0 -216
- package/dist/services/index.js +0 -1
- package/dist/styles.d.ts +0 -2
- package/dist/styles.d.ts.map +0 -1
- package/dist/styles.js +0 -3
- package/dist/templates/forms/ContactForm.js +0 -58
- package/dist/templates/forms/LoginForm.js +0 -36
- package/dist/templates/forms/RegistrationForm.js +0 -54
- package/dist/templates/layouts/DashboardLayout.js +0 -26
- package/dist/templates/layouts/SidebarLayout.js +0 -28
- 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
|
-
};
|