@griddo/ax 12.1.0 → 12.2.0-rc.0

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 (40) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/ContextMenu/ContextMenu.test.tsx +381 -0
  3. package/src/__tests__/components/FloatingMenu/FloatingMenu.test.tsx +3 -3
  4. package/src/__tests__/components/FloatingPanel/FloatingPanel.test.tsx +2 -2
  5. package/src/__tests__/components/SideModal/SideModal.test.tsx +2 -2
  6. package/src/__tests__/hooks/modals.test.tsx +86 -1
  7. package/src/components/ActionMenu/index.tsx +26 -11
  8. package/src/components/ActionMenu/style.tsx +15 -8
  9. package/src/components/ContextMenu/index.tsx +102 -0
  10. package/src/components/ContextMenu/style.tsx +15 -0
  11. package/src/components/Fields/ComponentContainer/index.tsx +9 -2
  12. package/src/components/FloatingMenu/index.tsx +1 -1
  13. package/src/components/FloatingMenu/style.tsx +1 -1
  14. package/src/components/index.tsx +4 -1
  15. package/src/hooks/index.tsx +2 -1
  16. package/src/hooks/modals.tsx +51 -4
  17. package/src/modules/Analytics/DimensionItem/index.tsx +5 -3
  18. package/src/modules/Analytics/GroupItem/index.tsx +5 -3
  19. package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +5 -2
  20. package/src/modules/Content/PageItem/index.tsx +15 -3
  21. package/src/modules/FileDrive/FolderItem/index.tsx +5 -3
  22. package/src/modules/FileDrive/GridItem/index.tsx +5 -3
  23. package/src/modules/FileDrive/ListItem/index.tsx +5 -3
  24. package/src/modules/Forms/FormCategoriesList/CategoryItem/index.tsx +5 -2
  25. package/src/modules/Forms/FormList/FormItem/index.tsx +5 -2
  26. package/src/modules/MediaGallery/FolderItem/index.tsx +5 -3
  27. package/src/modules/MediaGallery/GridItem/index.tsx +5 -3
  28. package/src/modules/MediaGallery/ListItem/index.tsx +5 -3
  29. package/src/modules/Navigation/Defaults/Item/index.tsx +5 -3
  30. package/src/modules/Navigation/Menus/List/Table/Item/index.tsx +5 -2
  31. package/src/modules/Redirects/RedirectItem/index.tsx +5 -3
  32. package/src/modules/Settings/Integrations/IntegrationForm/VariableItem/index.tsx +5 -3
  33. package/src/modules/Settings/Integrations/IntegrationItem/CopyModal/index.tsx +1 -0
  34. package/src/modules/Settings/Integrations/IntegrationItem/index.tsx +5 -3
  35. package/src/modules/Settings/Languages/Table/Item/index.tsx +5 -3
  36. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +5 -2
  37. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +5 -2
  38. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +5 -2
  39. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +5 -3
  40. package/src/modules/Users/UserList/UserItem/index.tsx +5 -3
@@ -0,0 +1,102 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+
3
+ import { ActionMenuContent } from "@ax/components";
4
+ import { useHandleClickOutside } from "@ax/hooks";
5
+ import type { IActionMenuOption } from "@ax/types";
6
+
7
+ import * as S from "./style";
8
+
9
+ const ContextMenu = (props: IContextMenuProps): JSX.Element | null => {
10
+ const { position, options, onClose } = props;
11
+ const menuRef = useRef<HTMLDivElement>(null);
12
+ const [adjustedPosition, setAdjustedPosition] = useState<{ x: number; y: number } | null>(null);
13
+ const [isVisible, setIsVisible] = useState(false);
14
+
15
+ // Maneja los clicks fuera del menú para cerrarlo
16
+ const handleClickOutside = (event: MouseEvent) => {
17
+ // No cerrar si el click fue dentro del menú
18
+ if (menuRef.current?.contains(event.target as Node)) {
19
+ return;
20
+ }
21
+
22
+ event.stopPropagation();
23
+ onClose?.();
24
+ };
25
+
26
+ useHandleClickOutside(!!position, handleClickOutside);
27
+
28
+ useLayoutEffect(() => {
29
+ if (!position || !menuRef.current) {
30
+ setAdjustedPosition(null);
31
+ setIsVisible(false);
32
+ return;
33
+ }
34
+
35
+ const calculatePosition = () => {
36
+ const menuRect = menuRef.current?.getBoundingClientRect();
37
+ if (!menuRect || menuRect.width === 0 || menuRect.height === 0) return;
38
+
39
+ const viewportWidth = window.innerWidth;
40
+ const viewportHeight = window.innerHeight;
41
+ const padding = 8;
42
+
43
+ let x = position.x;
44
+ let y = position.y;
45
+
46
+ // Ajustar si se sale por la derecha
47
+ if (x + menuRect.width > viewportWidth - padding) {
48
+ x = viewportWidth - menuRect.width - padding;
49
+ }
50
+
51
+ // Ajustar si se sale por abajo
52
+ if (y + menuRect.height > viewportHeight - padding) {
53
+ y = viewportHeight - menuRect.height - padding;
54
+ }
55
+
56
+ // Asegurar que no esté fuera de los bordes izquierdo y superior
57
+ x = Math.max(padding, x);
58
+ y = Math.max(padding, y);
59
+
60
+ setAdjustedPosition({ x, y });
61
+ setIsVisible(true);
62
+ };
63
+
64
+ calculatePosition();
65
+ }, [position]);
66
+
67
+ if (!position) return null;
68
+
69
+ const handleActionClick = (action: () => void) => {
70
+ return () => {
71
+ action();
72
+ onClose?.();
73
+ };
74
+ };
75
+
76
+ const wrappedOptions = options.map((option) => {
77
+ if (!option) return null;
78
+ return {
79
+ ...option,
80
+ action: handleActionClick(option.action),
81
+ };
82
+ });
83
+
84
+ return (
85
+ <S.ContextMenuContainer
86
+ ref={menuRef}
87
+ x={adjustedPosition?.x ?? 0}
88
+ y={adjustedPosition?.y ?? 0}
89
+ isVisible={isVisible}
90
+ >
91
+ <ActionMenuContent options={wrappedOptions} />
92
+ </S.ContextMenuContainer>
93
+ );
94
+ };
95
+
96
+ interface IContextMenuProps {
97
+ position: { x: number; y: number } | null;
98
+ options: (IActionMenuOption | undefined | null)[];
99
+ onClose?: () => void;
100
+ }
101
+
102
+ export default ContextMenu;
@@ -0,0 +1,15 @@
1
+ import styled from "styled-components";
2
+
3
+ const ContextMenuContainer = styled.div<{ x: number; y: number; isVisible: boolean }>`
4
+ position: fixed;
5
+ top: ${(p) => p.y}px;
6
+ left: ${(p) => p.x}px;
7
+ z-index: 1000;
8
+ box-shadow: ${(p) => p.theme.shadow.shadowL};
9
+ visibility: ${(p) => (p.isVisible ? "visible" : "hidden")};
10
+ opacity: ${(p) => (p.isVisible ? 1 : 0)};
11
+ transition: opacity 0.1s ease-in-out;
12
+ border-radius: ${(p) => p.theme.radii.s};
13
+ `;
14
+
15
+ export { ContextMenuContainer };
@@ -1,8 +1,8 @@
1
1
  import type React from "react";
2
2
 
3
- import { CheckField, Icon, SideModal } from "@ax/components";
3
+ import { CheckField, ContextMenu, Icon, SideModal } from "@ax/components";
4
4
  import { getDisplayName, isEmptyContainer, trimText } from "@ax/helpers";
5
- import { useModal } from "@ax/hooks";
5
+ import { useContextMenu, useModal } from "@ax/hooks";
6
6
  import type { ICheck, IComponent, ModuleCategoryInfo } from "@ax/types";
7
7
 
8
8
  import { useSortable } from "@dnd-kit/sortable";
@@ -48,6 +48,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
48
48
  actions || {};
49
49
 
50
50
  const { isOpen, toggleModal } = useModal();
51
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
51
52
 
52
53
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
53
54
  id: editorID,
@@ -180,6 +181,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
180
181
  disabled={disabled}
181
182
  className={`editorId-${editorID} ${className}`}
182
183
  onClick={handleClick}
184
+ onContextMenu={handleContextMenu}
183
185
  isActive={isActive}
184
186
  cssTransform={transform}
185
187
  transition={transition}
@@ -231,6 +233,11 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
231
233
  theme={theme}
232
234
  />
233
235
  )}
236
+ <ContextMenu
237
+ position={contextMenu}
238
+ options={isArray ? actionArrayMenuOptions : actionMenuOptions}
239
+ onClose={closeContextMenu}
240
+ />
234
241
  </>
235
242
  );
236
243
  };
@@ -1,5 +1,5 @@
1
1
  import type React from "react";
2
- import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from "react";
2
+ import { type ReactNode, useCallback, useLayoutEffect, useRef, useState } from "react";
3
3
 
4
4
  import { useHandleClickOutside } from "@ax/hooks";
5
5
 
@@ -41,7 +41,7 @@ const Menu = styled.div<{ hasMargin: boolean; width?: number }>`
41
41
  min-width: ${(p) => `calc(${p.theme.spacing.m} * 7)`};
42
42
  background: ${(p) => p.theme.color.uiBackground02};
43
43
  box-shadow: ${(p) => p.theme.shadow.shadowL};
44
- border-radius: ${(p) => p.theme.spacing.xxs};
44
+ border-radius: ${(p) => p.theme.radii.s};
45
45
  `;
46
46
 
47
47
  export { Wrapper, ButtonWrapper, MenuWrapper, Menu };
@@ -1,4 +1,4 @@
1
- import ActionMenu from "./ActionMenu";
1
+ import ActionMenu, { ActionMenuContent } from "./ActionMenu";
2
2
  import Avatar from "./Avatar";
3
3
  import BackFolder from "./BackFolder";
4
4
  import Breadcrumb from "./Breadcrumb";
@@ -10,6 +10,7 @@ import CategoryCell from "./CategoryCell";
10
10
  import Circle from "./Circle";
11
11
  import ContentItem from "./ContentItem";
12
12
  import ConfigPanel from "./ConfigPanel";
13
+ import ContextMenu from "./ContextMenu";
13
14
  import DragAndDrop from "./DragAndDrop";
14
15
  import ElementsTooltip from "./ElementsTooltip";
15
16
  import EmptyState from "./EmptyState";
@@ -133,6 +134,7 @@ import UserRolesAndSites from "./UserRolesAndSites";
133
134
 
134
135
  export {
135
136
  ActionMenu,
137
+ ActionMenuContent,
136
138
  AIReferenceField,
137
139
  ArrayFieldGroup,
138
140
  AsyncCheckGroup,
@@ -156,6 +158,7 @@ export {
156
158
  ContentItem,
157
159
  ConditionalField,
158
160
  ConfigPanel,
161
+ ContextMenu,
159
162
  CustomizeFilters,
160
163
  DateField,
161
164
  DateFilter,
@@ -10,7 +10,7 @@ import {
10
10
  } from "./forms";
11
11
  import { useOnMessageReceivedFromIframe, useOnMessageReceivedFromOutside } from "./iframe";
12
12
  import { useURLSearchParam } from "./location";
13
- import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
13
+ import { useContextMenu, useHandleClickOutside, useModal, useModals, useToast } from "./modals";
14
14
  import { useNetworkStatus } from "./network";
15
15
  import { useResizable } from "./resize";
16
16
  import { useGlobalPermission, usePermission, usePermissions, usePermissionsForPage, usePermissionsForSite } from "./users";
@@ -20,6 +20,7 @@ export {
20
20
  useAdaptiveText,
21
21
  useBulkSelection,
22
22
  useCategoryColors,
23
+ useContextMenu,
23
24
  useDebounce,
24
25
  useDebouncedCallback,
25
26
  useEmptyState,
@@ -84,13 +84,13 @@ const useModals = <T extends string>(modalKeys: readonly T[], bodyBlock = true)
84
84
  const useHandleClickOutside = (isOpen: boolean, handleClickOutside: (e: MouseEvent) => void) => {
85
85
  useEffect(() => {
86
86
  if (isOpen) {
87
- document.addEventListener("mousedown", handleClickOutside);
87
+ document.addEventListener("click", handleClickOutside, true);
88
88
  } else {
89
- document.removeEventListener("mousedown", handleClickOutside);
89
+ document.removeEventListener("click", handleClickOutside, true);
90
90
  }
91
91
 
92
92
  return () => {
93
- document.removeEventListener("mousedown", handleClickOutside);
93
+ document.removeEventListener("click", handleClickOutside, true);
94
94
  };
95
95
  }, [isOpen, handleClickOutside]);
96
96
  };
@@ -126,4 +126,51 @@ const useToast = () => {
126
126
  return { isVisible, setIsVisible, toggleToast, state };
127
127
  };
128
128
 
129
- export { useModal, useModals, useHandleClickOutside, useToast };
129
+ const useContextMenu = () => {
130
+ const [contextMenu, setContextMenu] = useState<IContextMenuPosition | null>(null);
131
+
132
+ const closeContextMenu = useCallback(() => {
133
+ setContextMenu(null);
134
+ }, []);
135
+
136
+ const handleContextMenu = (e: React.MouseEvent) => {
137
+ e.preventDefault();
138
+ setContextMenu({ x: e.clientX, y: e.clientY });
139
+ };
140
+
141
+ // Registra este context menu como el activo y cierra el anterior si existe
142
+ useEffect(() => {
143
+ if (contextMenu) {
144
+ const unregister = registerContextMenu(closeContextMenu);
145
+ return unregister;
146
+ }
147
+ }, [contextMenu, closeContextMenu]);
148
+
149
+ return {
150
+ contextMenu,
151
+ handleContextMenu,
152
+ closeContextMenu,
153
+ };
154
+ };
155
+
156
+ let activeContextMenuClose: (() => void) | null = null;
157
+
158
+ const registerContextMenu = (onClose: () => void) => {
159
+ if (activeContextMenuClose) {
160
+ activeContextMenuClose();
161
+ }
162
+ activeContextMenuClose = onClose;
163
+
164
+ return () => {
165
+ if (activeContextMenuClose === onClose) {
166
+ activeContextMenuClose = null;
167
+ }
168
+ };
169
+ };
170
+
171
+ interface IContextMenuPosition {
172
+ x: number;
173
+ y: number;
174
+ }
175
+
176
+ export { useModal, useModals, useHandleClickOutside, useToast, useContextMenu };
@@ -1,6 +1,6 @@
1
- import { Modal } from "@ax/components";
1
+ import { Modal, ContextMenu } from "@ax/components";
2
2
  import { splitAndJoin, trimText } from "@ax/helpers";
3
- import { useModal } from "@ax/hooks";
3
+ import { useModal, useContextMenu } from "@ax/hooks";
4
4
  import type { IDimension } from "@ax/types";
5
5
 
6
6
  import DimensionPanel from "../DimensionPanel";
@@ -12,6 +12,7 @@ const Item = (props: IProps): JSX.Element => {
12
12
 
13
13
  const { isOpen, toggleModal } = useModal();
14
14
  const { isOpen: isRemoveOpen, toggleModal: toggleRemoveModal } = useModal();
15
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
15
16
 
16
17
  const handleClick = () => toggleModal();
17
18
 
@@ -36,7 +37,7 @@ const Item = (props: IProps): JSX.Element => {
36
37
 
37
38
  return (
38
39
  <>
39
- <S.Component onClick={handleClick}>
40
+ <S.Component onClick={handleClick} onContextMenu={handleContextMenu}>
40
41
  <S.Name>{item.name}</S.Name>
41
42
  <S.Values>{trimText(valuesText, 70)}</S.Values>
42
43
  <S.StyledActionMenu icon="more" options={dimensionOptions} tooltip="Dimension actions" />
@@ -59,6 +60,7 @@ const Item = (props: IProps): JSX.Element => {
59
60
  </p>
60
61
  </S.ModalContent>
61
62
  </Modal>
63
+ <ContextMenu position={contextMenu} options={dimensionOptions} onClose={closeContextMenu} />
62
64
  </>
63
65
  );
64
66
  };
@@ -1,6 +1,6 @@
1
- import { Modal } from "@ax/components";
1
+ import { Modal, ContextMenu } from "@ax/components";
2
2
  import { splitAndJoin } from "@ax/helpers";
3
- import { useModal } from "@ax/hooks";
3
+ import { useModal, useContextMenu } from "@ax/hooks";
4
4
  import type { IDimension, IDimensionsGroup } from "@ax/types";
5
5
 
6
6
  import GroupPanel from "../GroupPanel";
@@ -12,6 +12,7 @@ const Item = (props: IProps): JSX.Element => {
12
12
 
13
13
  const { isOpen, toggleModal } = useModal();
14
14
  const { isOpen: isRemoveOpen, toggleModal: toggleRemoveModal } = useModal();
15
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
15
16
 
16
17
  const handleClick = () => toggleModal();
17
18
 
@@ -36,7 +37,7 @@ const Item = (props: IProps): JSX.Element => {
36
37
 
37
38
  return (
38
39
  <>
39
- <S.Component onClick={handleClick}>
40
+ <S.Component onClick={handleClick} onContextMenu={handleContextMenu}>
40
41
  <S.Name>{item.name}</S.Name>
41
42
  <S.ComponentInfo>
42
43
  <S.Values>
@@ -70,6 +71,7 @@ const Item = (props: IProps): JSX.Element => {
70
71
  </p>
71
72
  </S.ModalContent>
72
73
  </Modal>
74
+ <ContextMenu position={contextMenu} options={groupOptions} onClose={closeContextMenu} />
73
75
  </>
74
76
  );
75
77
  };
@@ -3,10 +3,10 @@ import { useState } from "react";
3
3
  import { connect } from "react-redux";
4
4
 
5
5
  import { structuredData } from "@ax/api";
6
- import { CheckField, Flag, FloatingMenu, Icon, LanguageMenu, Tag, TruncatedTooltip } from "@ax/components";
6
+ import { CheckField, ContextMenu, Flag, FloatingMenu, Icon, LanguageMenu, Tag, TruncatedTooltip } from "@ax/components";
7
7
  import { structuredDataActions } from "@ax/containers/StructuredData";
8
8
  import { isReqOk } from "@ax/helpers";
9
- import { useModals, usePermissions } from "@ax/hooks";
9
+ import { useContextMenu, useModals, usePermissions } from "@ax/hooks";
10
10
  import type { ICategoryGroup, ICheck, IDataLanguage, ILanguage, IRootState, IStructuredDataCategory } from "@ax/types";
11
11
 
12
12
  import { type AnimateLayoutChanges, useSortable } from "@dnd-kit/sortable";
@@ -39,6 +39,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
39
39
  } = props;
40
40
 
41
41
  const { isOpen, toggleModal } = useModals(["panel", "delete", "group"]);
42
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
42
43
  const [deleteGroupCategories, setDeleteGroupCategories] = useState(false);
43
44
  const [translation, setTranslation] = useState<{
44
45
  lang: { locale: string; id: number };
@@ -190,6 +191,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
190
191
  <S.CategoryRow
191
192
  selected={isSelected}
192
193
  onClick={handleClick}
194
+ onContextMenu={handleContextMenu}
193
195
  ref={setDraggableNodeRef}
194
196
  cssTransform={transform}
195
197
  transition={transition}
@@ -273,6 +275,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
273
275
  isLoading={isSaving}
274
276
  />
275
277
  )}
278
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
276
279
  </>
277
280
  );
278
281
  };
@@ -1,7 +1,17 @@
1
1
  import { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { CheckField, Flag, FloatingMenu, Icon, LanguageMenu, Tag, Tooltip, TruncatedTooltip } from "@ax/components";
4
+ import {
5
+ CheckField,
6
+ ContextMenu,
7
+ Flag,
8
+ FloatingMenu,
9
+ Icon,
10
+ LanguageMenu,
11
+ Tag,
12
+ Tooltip,
13
+ TruncatedTooltip,
14
+ } from "@ax/components";
5
15
  import { appActions } from "@ax/containers/App";
6
16
  import { pageEditorActions } from "@ax/containers/PageEditor";
7
17
  import { type ISetCurrentPageIDAction, pageStatus } from "@ax/containers/PageEditor/interfaces";
@@ -12,7 +22,7 @@ import {
12
22
  getTemplateDisplayName,
13
23
  slugify,
14
24
  } from "@ax/helpers";
15
- import { useModals, usePermissionsForPage } from "@ax/hooks";
25
+ import { useContextMenu, useModals, usePermissionsForPage } from "@ax/hooks";
16
26
  import type {
17
27
  ICheck,
18
28
  IColumn,
@@ -94,6 +104,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
94
104
  const [site, setSite] = useState(null);
95
105
  const [modalState, setModalState] = useState(initValue);
96
106
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
107
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
97
108
  const { toggleModal, isOpen } = useModals(["duplicate", "remove", "unpublish", "delete", "copy"]);
98
109
 
99
110
  const isAllowedTo = usePermissionsForPage(
@@ -465,7 +476,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
465
476
 
466
477
  return (
467
478
  <>
468
- <S.PageRow role="rowgroup" selected={isSelected} global={isGlobal}>
479
+ <S.PageRow role="rowgroup" selected={isSelected} global={isGlobal} onContextMenu={handleContextMenu}>
469
480
  <S.CheckCell role="cell">
470
481
  <CheckField name="check" value={page.id} checked={isSelected || hoverCheck} onChange={handleOnChange} />
471
482
  </S.CheckCell>
@@ -555,6 +566,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
555
566
  onDelete={handleDeleteItem}
556
567
  {...{ isTranslated, deleteAllVersions, setDeleteAllVersions, title: page.title, isDeleting: isSaving }}
557
568
  />
569
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
558
570
  </>
559
571
  );
560
572
  };
@@ -1,10 +1,10 @@
1
1
  import { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { Icon, Tooltip } from "@ax/components";
4
+ import { ContextMenu, Icon, Tooltip } from "@ax/components";
5
5
  import { fileDriveActions } from "@ax/containers/FileDrive";
6
6
  import { trimText } from "@ax/helpers";
7
- import { useModals } from "@ax/hooks";
7
+ import { useContextMenu, useModals } from "@ax/hooks";
8
8
  import type { IActionMenuOption, IFolder, IRootState } from "@ax/types";
9
9
 
10
10
  import { DeleteFolderModal, MoveItemModal, RenameFolderModal } from "../atoms";
@@ -28,6 +28,7 @@ const FolderItem = (props: IProps) => {
28
28
  const initState = { folderName, parentId };
29
29
  const [folderForm, setFolderForm] = useState(initState);
30
30
  const { isOpen, toggleModal } = useModals(["delete", "rename", "move"]);
31
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
31
32
 
32
33
  const setName = (name: string) => setFolderForm({ ...folderForm, folderName: name });
33
34
  const setParent = (folderID: number) => setFolderForm({ ...folderForm, parentId: folderID });
@@ -102,7 +103,7 @@ const FolderItem = (props: IProps) => {
102
103
 
103
104
  return (
104
105
  <>
105
- <S.Wrapper onClick={handleClick}>
106
+ <S.Wrapper onClick={handleClick} onContextMenu={handleContextMenu}>
106
107
  <S.IconWrapper>
107
108
  <Icon name="project" size="24" />
108
109
  </S.IconWrapper>
@@ -141,6 +142,7 @@ const FolderItem = (props: IProps) => {
141
142
  isMoving={isSaving}
142
143
  />
143
144
  )}
145
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
144
146
  </>
145
147
  );
146
148
  };
@@ -1,9 +1,9 @@
1
1
  import type React from "react";
2
2
  import { useState } from "react";
3
3
 
4
- import { CheckField, Tag, Tooltip } from "@ax/components";
4
+ import { CheckField, ContextMenu, Tag, Tooltip } from "@ax/components";
5
5
  import { formatBytes, getFileIcon, getFormattedDateWithTimezone, trimText } from "@ax/helpers";
6
- import { useModals } from "@ax/hooks";
6
+ import { useContextMenu, useModals } from "@ax/hooks";
7
7
  import type { IActionMenuOption, ICheck, IFile } from "@ax/types";
8
8
 
9
9
  import { DeleteFileModal, MoveItemModal } from "../atoms";
@@ -31,6 +31,7 @@ const GridItem = (props: IProps) => {
31
31
  const [selectedFolder, setSelectedFolder] = useState<number>(currentFolderID || 0);
32
32
  const [isDeleting, setIsDeleting] = useState(false);
33
33
  const { isOpen, toggleModal } = useModals(["delete", "move"]);
34
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
34
35
 
35
36
  const handleChange = (value: ICheck) => onChange(value);
36
37
  const handleClick = () => onClick(file);
@@ -117,7 +118,7 @@ const GridItem = (props: IProps) => {
117
118
 
118
119
  return (
119
120
  <>
120
- <S.Wrapper onClick={handleClick} isTall={isSearching}>
121
+ <S.Wrapper onClick={handleClick} onContextMenu={handleContextMenu} isTall={isSearching}>
121
122
  <S.Header>
122
123
  <S.CheckWrapper onClick={handleCheckClick}>
123
124
  <CheckField name="check" value={id} checked={isSelected} onChange={handleChange} />
@@ -158,6 +159,7 @@ const GridItem = (props: IProps) => {
158
159
  isMoving={isSaving}
159
160
  />
160
161
  )}
162
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
161
163
  </>
162
164
  );
163
165
  };
@@ -1,8 +1,8 @@
1
1
  import { useState } from "react";
2
2
 
3
- import { CheckField, ElementsTooltip, Tag, Tooltip } from "@ax/components";
3
+ import { CheckField, ContextMenu, ElementsTooltip, Tag, Tooltip } from "@ax/components";
4
4
  import { formatBytes, getFileIcon, getFormattedDateWithTimezone, trimText } from "@ax/helpers";
5
- import { useModals } from "@ax/hooks";
5
+ import { useContextMenu, useModals } from "@ax/hooks";
6
6
  import type { IActionMenuOption, ICheck, IFile } from "@ax/types";
7
7
 
8
8
  import { DeleteFileModal, MoveItemModal } from "../atoms";
@@ -30,6 +30,7 @@ const ListItem = (props: IProps) => {
30
30
  const [selectedFolder, setSelectedFolder] = useState<number>(currentFolderID || 0);
31
31
  const [isDeleting, setIsDeleting] = useState(false);
32
32
  const { isOpen, toggleModal } = useModals(["delete", "move"]);
33
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
33
34
 
34
35
  const handleChange = (value: ICheck) => onChange(value);
35
36
  const handleClick = () => onClick(file);
@@ -109,7 +110,7 @@ const ListItem = (props: IProps) => {
109
110
 
110
111
  return (
111
112
  <>
112
- <S.ItemRow role="rowgroup" selected={isSelected}>
113
+ <S.ItemRow role="rowgroup" selected={isSelected} onContextMenu={handleContextMenu}>
113
114
  <S.CheckCell role="cell">
114
115
  <CheckField name="check" value={id} checked={isSelected || hoverCheck} onChange={handleChange} />
115
116
  </S.CheckCell>
@@ -170,6 +171,7 @@ const ListItem = (props: IProps) => {
170
171
  isMoving={isSaving}
171
172
  />
172
173
  )}
174
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
173
175
  </>
174
176
  );
175
177
  };
@@ -1,9 +1,9 @@
1
1
  import type React from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { CheckField, Icon, TruncatedTooltip } from "@ax/components";
4
+ import { CheckField, ContextMenu, Icon, TruncatedTooltip } from "@ax/components";
5
5
  import { formsActions } from "@ax/containers/Forms";
6
- import { useModals, usePermissions } from "@ax/hooks";
6
+ import { useContextMenu, useModals, usePermissions } from "@ax/hooks";
7
7
  import type { FormCategory, ICheck, IRootState } from "@ax/types";
8
8
 
9
9
  import { useSortable } from "@dnd-kit/sortable";
@@ -27,6 +27,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
27
27
  } = props;
28
28
 
29
29
  const { isOpen, toggleModal } = useModals(["edit", "delete"]);
30
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
30
31
 
31
32
  const isAllowedTo = usePermissions({
32
33
  editSiteCategory: "forms.editFormsCategories",
@@ -78,6 +79,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
78
79
  <S.CategoryRow
79
80
  selected={isSelected}
80
81
  onClick={handleClick}
82
+ onContextMenu={handleContextMenu}
81
83
  ref={setNodeRef}
82
84
  cssTransform={transform}
83
85
  transition={transition}
@@ -121,6 +123,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
121
123
  onDelete={removeCategory}
122
124
  isDeleting={isSaving}
123
125
  />
126
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
124
127
  </>
125
128
  );
126
129
  };
@@ -5,6 +5,7 @@ import { connect } from "react-redux";
5
5
  import {
6
6
  CategoryCell,
7
7
  CheckField,
8
+ ContextMenu,
8
9
  Flag,
9
10
  FloatingMenu,
10
11
  Icon,
@@ -16,7 +17,7 @@ import { appActions } from "@ax/containers/App";
16
17
  import { formsActions } from "@ax/containers/Forms";
17
18
  import { findFieldsErrors } from "@ax/forms";
18
19
  import { findObjectValue, getHumanLastModifiedDate } from "@ax/helpers";
19
- import { useModals, usePermissions } from "@ax/hooks";
20
+ import { useContextMenu, useModals, usePermissions } from "@ax/hooks";
20
21
  import type {
21
22
  FormContent,
22
23
  FormLanguage,
@@ -74,6 +75,7 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
74
75
  const [duplicateTitle, setDuplicateTitle] = useState("");
75
76
  const [site, setSite] = useState<number | null>(null);
76
77
  const { isOpen, toggleModal } = useModals(["delete", "unpublish", "duplicate", "copy", "use"]);
78
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
77
79
 
78
80
  const isAllowedTo = usePermissions({
79
81
  createForms: isSiteView ? "forms.createForms" : "global.forms.createForms",
@@ -260,7 +262,7 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
260
262
 
261
263
  return (
262
264
  <>
263
- <S.FormRow role="rowgroup" selected={isSelected} onClick={handleClick} clickable={isAllowedTo.editForms}>
265
+ <S.FormRow role="rowgroup" selected={isSelected} onClick={handleClick} onContextMenu={handleContextMenu} clickable={isAllowedTo.editForms}>
264
266
  <S.CheckCell role="cell" onClick={handleCheckClick}>
265
267
  <CheckField name={`form-${id}`} value={id} checked={isSelected || hoverCheck} onChange={handleOnChange} />
266
268
  </S.CheckCell>
@@ -322,6 +324,7 @@ const FormItem = (props: IFormItemProps): JSX.Element => {
322
324
  formInUse={formInUse}
323
325
  />
324
326
  )}
327
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
325
328
  </>
326
329
  );
327
330
  };
@@ -1,10 +1,10 @@
1
1
  import { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { Icon, Tooltip } from "@ax/components";
4
+ import { ContextMenu, Icon, Tooltip } from "@ax/components";
5
5
  import { galleryActions } from "@ax/containers/Gallery";
6
6
  import { trimText } from "@ax/helpers";
7
- import { useModals } from "@ax/hooks";
7
+ import { useContextMenu, useModals } from "@ax/hooks";
8
8
  import type { IActionMenuOption, IFolder, IGetFolderParams, IRootState } from "@ax/types";
9
9
 
10
10
  import { DeleteFolderModal, MoveItemModal, RenameFolderModal } from "../atoms";
@@ -29,6 +29,7 @@ const FolderItem = (props: IProps) => {
29
29
  const initState = { folderName, parentId };
30
30
  const [folderForm, setFolderForm] = useState(initState);
31
31
  const { isOpen, toggleModal } = useModals(["delete", "rename", "move"]);
32
+ const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
32
33
 
33
34
  const setName = (name: string) => setFolderForm({ ...folderForm, folderName: name });
34
35
  const setParent = (folderID: number) => setFolderForm({ ...folderForm, parentId: folderID });
@@ -104,7 +105,7 @@ const FolderItem = (props: IProps) => {
104
105
 
105
106
  return (
106
107
  <>
107
- <S.Wrapper onClick={handleClick}>
108
+ <S.Wrapper onClick={handleClick} onContextMenu={handleContextMenu}>
108
109
  <S.IconWrapper>
109
110
  <Icon name="project" size="24" />
110
111
  </S.IconWrapper>
@@ -143,6 +144,7 @@ const FolderItem = (props: IProps) => {
143
144
  isMoving={isSaving}
144
145
  />
145
146
  )}
147
+ <ContextMenu position={contextMenu} options={menuOptions} onClose={closeContextMenu} />
146
148
  </>
147
149
  );
148
150
  };