@griddo/ax 11.14.2-rc.0 → 11.14.2

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 (148) hide show
  1. package/config/jest/reactEasyCropMock.js +15 -0
  2. package/config/jest/reactTimezoneMock.js +13 -0
  3. package/package.json +221 -219
  4. package/public/img/welcome.svg +127 -0
  5. package/src/__tests__/components/Browser/Browser.test.tsx +27 -51
  6. package/src/__tests__/components/CategoryCell/CategoryCell.test.tsx +10 -5
  7. package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +27 -14
  8. package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +2 -0
  9. package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +138 -1
  10. package/src/__tests__/components/ImageDragAndDrop/CropStep/CropStep.test.tsx +84 -0
  11. package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +173 -0
  12. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +3 -4
  13. package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
  14. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
  15. package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
  16. package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
  17. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
  18. package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
  19. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
  20. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
  21. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
  22. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
  23. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
  24. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
  25. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
  26. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
  27. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
  28. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
  29. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
  30. package/src/api/sites.tsx +4 -4
  31. package/src/components/Avatar/index.tsx +26 -5
  32. package/src/components/Avatar/style.tsx +20 -10
  33. package/src/components/Browser/index.tsx +7 -1
  34. package/src/components/ConfigPanel/index.tsx +11 -7
  35. package/src/components/ElementsTooltip/index.tsx +96 -34
  36. package/src/components/ElementsTooltip/style.tsx +12 -1
  37. package/src/components/Fields/FileField/index.tsx +16 -18
  38. package/src/components/Fields/HeadingField/index.tsx +1 -1
  39. package/src/components/Fields/ImageField/index.tsx +9 -38
  40. package/src/components/Fields/ImageField/style.tsx +12 -1
  41. package/src/components/Fields/ToggleField/index.tsx +1 -1
  42. package/src/components/Fields/Wysiwyg/index.tsx +25 -20
  43. package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
  44. package/src/components/FileGallery/index.tsx +33 -28
  45. package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
  46. package/src/components/Gallery/index.tsx +0 -2
  47. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
  48. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
  49. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
  50. package/src/components/HeadingsPreviewModal/index.tsx +13 -3
  51. package/src/components/HeadingsPreviewModal/style.tsx +18 -0
  52. package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
  53. package/src/components/Image/index.tsx +2 -2
  54. package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
  55. package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
  56. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
  57. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
  58. package/src/components/KeywordsPreviewModal/atoms.tsx +2 -2
  59. package/src/components/KeywordsPreviewModal/index.tsx +6 -6
  60. package/src/components/KeywordsPreviewModal/utils.tsx +2 -2
  61. package/src/components/ProfileImage/index.tsx +55 -0
  62. package/src/components/ProfileImage/style.tsx +58 -0
  63. package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
  64. package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
  65. package/src/components/ResizePanel/index.tsx +25 -4
  66. package/src/components/Tabs/style.tsx +1 -1
  67. package/src/components/Tag/index.tsx +0 -1
  68. package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
  69. package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
  70. package/src/components/UserRolesAndSites/index.tsx +102 -0
  71. package/src/components/UserRolesAndSites/style.tsx +67 -0
  72. package/src/components/index.tsx +6 -0
  73. package/src/constants/index.ts +13 -1
  74. package/src/containers/App/actions.tsx +8 -1
  75. package/src/containers/Sites/actions.tsx +26 -0
  76. package/src/containers/Sites/constants.tsx +1 -0
  77. package/src/containers/Sites/interfaces.tsx +6 -0
  78. package/src/containers/Sites/reducer.tsx +5 -1
  79. package/src/containers/Users/reducer.tsx +6 -5
  80. package/src/guards/routeLeaving/index.tsx +9 -11
  81. package/src/helpers/images.tsx +50 -3
  82. package/src/helpers/index.tsx +2 -1
  83. package/src/hooks/forms.tsx +45 -48
  84. package/src/hooks/index.tsx +2 -1
  85. package/src/hooks/modals.tsx +4 -3
  86. package/src/hooks/window.ts +50 -2
  87. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
  88. package/src/modules/App/Routing/Logout/index.tsx +3 -5
  89. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
  90. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
  91. package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
  92. package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
  93. package/src/modules/CreatePass/index.tsx +1 -1
  94. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
  95. package/src/modules/FileDrive/FileModal/index.tsx +8 -9
  96. package/src/modules/FileDrive/index.tsx +1 -18
  97. package/src/modules/Forms/FormEditor/index.tsx +1 -1
  98. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
  99. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
  100. package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
  101. package/src/modules/MediaGallery/index.tsx +1 -3
  102. package/src/modules/Settings/Globals/constants.tsx +942 -106
  103. package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
  104. package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
  105. package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
  106. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
  107. package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
  108. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
  109. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
  110. package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
  111. package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
  112. package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
  113. package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
  114. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
  115. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
  116. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
  117. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
  118. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
  119. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
  120. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
  121. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
  122. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
  123. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
  124. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
  125. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
  126. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
  127. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
  128. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
  129. package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
  130. package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
  131. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
  132. package/src/modules/Sites/SitesList/atoms.tsx +4 -4
  133. package/src/modules/Sites/SitesList/hooks.tsx +149 -16
  134. package/src/modules/Sites/SitesList/index.tsx +127 -125
  135. package/src/modules/Sites/SitesList/style.tsx +1 -117
  136. package/src/modules/Sites/SitesList/utils.tsx +9 -2
  137. package/src/modules/Sites/index.tsx +19 -8
  138. package/src/modules/Users/Profile/index.tsx +169 -31
  139. package/src/modules/Users/Profile/style.tsx +81 -1
  140. package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
  141. package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
  142. package/src/modules/Users/UserForm/atoms.tsx +3 -3
  143. package/src/modules/Users/UserForm/index.tsx +25 -29
  144. package/src/modules/Users/UserForm/style.tsx +15 -2
  145. package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
  146. package/src/routes/index.tsx +1 -0
  147. package/src/types/index.tsx +2 -0
  148. /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
@@ -28,7 +28,7 @@ import { getFieldByPath } from "./fields";
28
28
  import { getFileIcon, getNewBreadcrumb, updatePropertyById } from "./files";
29
29
  import { protectFormKeys } from "./forms";
30
30
  import { compressImage, imageResizeCropAndCompress } from "./imageResize";
31
- import { formatBytes, getImageFromHtml, getImageFromIFrame, imageToBase64 } from "./images";
31
+ import { formatBytes, getCroppedImg, getImageFromHtml, getImageFromIFrame, imageToBase64 } from "./images";
32
32
  import {
33
33
  areEquals,
34
34
  deepClone,
@@ -136,6 +136,7 @@ export {
136
136
  formatBytes,
137
137
  formatDate,
138
138
  getActivatedDataPacksIds,
139
+ getCroppedImg,
139
140
  getCurrentPageStructuredData,
140
141
  getDataPackSchema,
141
142
  getDateRange,
@@ -2,7 +2,6 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  import { cleanPageValues } from "@ax/forms";
4
4
  import { deepClone, isEmptyObj } from "@ax/helpers";
5
- import type { FormContent, IUser } from "@ax/types";
6
5
 
7
6
  import isEqual from "lodash.isequal";
8
7
 
@@ -197,60 +196,58 @@ const useIsDirty = (
197
196
  return { isDirty, setIsDirty, resetDirty };
198
197
  };
199
198
 
200
- const cleanModified = (updatedValues: any, originalValues: any) => {
201
- delete updatedValues.modified;
199
+ const useShouldBeSaved = (form: Record<string, any> | null | undefined, debug = DEBUG_DIRTY) => {
200
+ const [isDirty, setIsDirtyState] = useState(false);
201
+ const originalRef = useRef<Record<string, any>>();
202
202
 
203
- delete originalValues.modified;
204
-
205
- return {
206
- cleanUpdatedValues: updatedValues,
207
- cleanOriginalValues: originalValues,
208
- };
209
- };
210
-
211
- const useShouldBeSaved = (form: Record<string, unknown> | IUser | FormContent, debug = DEBUG_DIRTY) => {
212
- const [isDirty, setIsDirty] = useState(false);
213
- const formRef = useRef();
214
-
215
- const stringValue = form && JSON.stringify(form);
203
+ // Intercept setIsDirty to update baseline when saved
204
+ const setIsDirty = useCallback(
205
+ (value: boolean) => {
206
+ setIsDirtyState(value);
207
+ if (value === false && form) {
208
+ originalRef.current = deepClone(form);
209
+ }
210
+ },
211
+ [form],
212
+ );
216
213
 
217
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO fix this
218
214
  useEffect(() => {
219
- if (!formRef.current) {
220
- formRef.current = stringValue && JSON.parse(stringValue);
215
+ if (!form) {
216
+ if (debug) console.log("[useShouldBeSaved] form=null isDirty=false");
217
+ setIsDirtyState(false);
218
+ return;
221
219
  }
222
- if (form !== null) {
223
- const { cleanUpdatedValues, cleanOriginalValues } = cleanModified(formRef.current, form);
224
- const isFormDirty =
225
- cleanOriginalValues.id && !cleanUpdatedValues.id ? false : !isEqual(cleanUpdatedValues, cleanOriginalValues);
226
220
 
227
- if (debug) {
228
- console.groupCollapsed(
229
- `[useShouldBeSaved] isDirty: %c${isFormDirty}`,
230
- isFormDirty ? "color: red; font-weight: bold" : "color: green",
231
- );
232
- console.log("Stored ref (cleaned):", cleanUpdatedValues);
233
- console.log("Current (cleaned):", cleanOriginalValues);
234
- if (cleanOriginalValues.id && !cleanUpdatedValues.id) {
235
- console.log("Suppressed: original has id, ref does not.");
236
- }
237
- const diffs = getChangedKeys(
238
- cleanUpdatedValues as Record<string, unknown>,
239
- cleanOriginalValues as Record<string, unknown>,
240
- );
241
- if (diffs.length > 0) {
242
- console.table(diffs);
243
- }
244
- console.groupEnd();
245
- }
221
+ // Initialize baseline on first render
222
+ if (!originalRef.current) {
223
+ originalRef.current = deepClone(form);
224
+ return;
225
+ }
246
226
 
247
- setIsDirty(isFormDirty);
248
- formRef.current = stringValue && JSON.parse(stringValue);
249
- } else {
250
- if (debug) console.log("[useShouldBeSaved] form=null → isDirty=false");
251
- setIsDirty(false);
227
+ // Compare against the baseline (original)
228
+ const original = { ...originalRef.current } as Record<string, unknown>;
229
+ const current = { ...form } as Record<string, unknown>;
230
+ delete original.modified;
231
+ delete current.modified;
232
+
233
+ const hasChanges = !isEqual(original, current);
234
+
235
+ if (debug) {
236
+ console.groupCollapsed(
237
+ `[useShouldBeSaved] isDirty: %c${hasChanges}`,
238
+ hasChanges ? "color: red; font-weight: bold" : "color: green",
239
+ );
240
+ console.log("Original (cleaned):", original);
241
+ console.log("Current (cleaned):", current);
242
+ const diffs = getChangedKeys(original as Record<string, unknown>, current as Record<string, unknown>);
243
+ if (diffs.length > 0) {
244
+ console.table(diffs);
245
+ }
246
+ console.groupEnd();
252
247
  }
253
- }, [form]);
248
+
249
+ setIsDirtyState(hasChanges);
250
+ }, [form, debug]);
254
251
 
255
252
  return { isDirty, setIsDirty };
256
253
  };
@@ -14,7 +14,7 @@ import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
14
14
  import { useNetworkStatus } from "./network";
15
15
  import { useResizable } from "./resize";
16
16
  import { useGlobalPermission, usePermission, usePermissions } from "./users";
17
- import { useWindowSize } from "./window";
17
+ import { useFirefoxScrollLock, useWindowSize } from "./window";
18
18
 
19
19
  export {
20
20
  useAdaptiveText,
@@ -24,6 +24,7 @@ export {
24
24
  useDebouncedCallback,
25
25
  useEmptyState,
26
26
  useEqualStructured,
27
+ useFirefoxScrollLock,
27
28
  useGlobalPermission,
28
29
  useHandleClickOutside,
29
30
  useIsDirty,
@@ -2,9 +2,10 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  const useModal = (initialState?: boolean, bodyBlock = true) => {
4
4
  const [isOpen, setIsOpen] = useState(initialState || false);
5
- const toggleModal = () => {
6
- setIsOpen(!isOpen);
7
- };
5
+
6
+ const toggleModal = useCallback(() => {
7
+ setIsOpen((prev) => !prev);
8
+ }, []);
8
9
 
9
10
  useEffect(() => {
10
11
  if (isOpen && bodyBlock) {
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect, useState } from "react";
1
+ import { useEffect, useLayoutEffect, useState } from "react";
2
2
 
3
3
  const useWindowSize = (): number[] => {
4
4
  const [size, setSize] = useState([0, 0]);
@@ -13,4 +13,52 @@ const useWindowSize = (): number[] => {
13
13
  return size;
14
14
  };
15
15
 
16
- export { useWindowSize };
16
+ /**
17
+ * Firefox auto-scrolls the window when typing in form fields if a scrollable
18
+ * element (e.g. Froala .fr-wrapper with overflow:auto) is present in the page.
19
+ * This hook blocks any window scroll not initiated by the user (wheel/touch)
20
+ * while a field inside the given container has focus.
21
+ *
22
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1422524
23
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1660932
24
+ */
25
+ const useFirefoxScrollLock = (containerRef: React.RefObject<HTMLElement | null>) => {
26
+ useEffect(() => {
27
+ if (!navigator.userAgent.includes("Firefox")) {
28
+ return;
29
+ }
30
+
31
+ let savedScrollY = window.scrollY;
32
+ let userScrolling = false;
33
+ let scrollTimer: ReturnType<typeof setTimeout>;
34
+
35
+ const markUserScroll = () => {
36
+ userScrolling = true;
37
+ clearTimeout(scrollTimer);
38
+ scrollTimer = setTimeout(() => {
39
+ userScrolling = false;
40
+ }, 150);
41
+ };
42
+
43
+ const onScroll = () => {
44
+ if (!userScrolling && containerRef.current?.contains(document.activeElement)) {
45
+ window.scrollTo(0, savedScrollY);
46
+ } else {
47
+ savedScrollY = window.scrollY;
48
+ }
49
+ };
50
+
51
+ window.addEventListener("wheel", markUserScroll, { passive: true });
52
+ window.addEventListener("touchmove", markUserScroll, { passive: true });
53
+ window.addEventListener("scroll", onScroll);
54
+
55
+ return () => {
56
+ window.removeEventListener("wheel", markUserScroll);
57
+ window.removeEventListener("touchmove", markUserScroll);
58
+ window.removeEventListener("scroll", onScroll);
59
+ clearTimeout(scrollTimer);
60
+ };
61
+ }, [containerRef]);
62
+ };
63
+
64
+ export { useFirefoxScrollLock, useWindowSize };
@@ -19,7 +19,7 @@ const UserItem = (props: IUserItemProps) => {
19
19
  <S.Item>
20
20
  <S.User>
21
21
  <S.AvatarWrapper>
22
- <Avatar image={image} name={name || ""} size="s" />
22
+ <Avatar image={image} name={name || ""} size={24} />
23
23
  </S.AvatarWrapper>
24
24
  <S.Name>{name}</S.Name>
25
25
  {!isOpen && <S.CounterWrapper>{logActivity.length} events created</S.CounterWrapper>}
@@ -1,17 +1,15 @@
1
- import React, { useEffect } from "react";
1
+ import { useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { ILogoutAction } from "@ax/containers/App/interfaces";
5
4
  import { appActions } from "@ax/containers/App";
5
+ import type { ILogoutAction } from "@ax/containers/App/interfaces";
6
6
 
7
7
  const Logout = (props: IProps) => {
8
8
  const { logout, setHistoryPush } = props;
9
9
 
10
10
  useEffect(() => {
11
+ logout();
11
12
  setHistoryPush("/login");
12
- return () => {
13
- logout();
14
- };
15
13
  }, [logout, setHistoryPush]);
16
14
 
17
15
  return <></>;
@@ -1,17 +1,17 @@
1
1
  import React, { useContext } from "react";
2
2
 
3
- import { IRouter } from "@ax/routes";
3
+ import { Avatar, Icon } from "@ax/components";
4
4
  import { Restricted } from "@ax/guards";
5
- import { Icon } from "@ax/components";
5
+ import type { IRouter } from "@ax/routes";
6
6
 
7
- import NavSubItem from "./NavSubItem";
8
7
  import { NavContext } from "./../context";
8
+ import NavSubItem from "./NavSubItem";
9
9
 
10
10
  import * as S from "./style";
11
11
 
12
12
  const NavItem = (props: IProps) => {
13
13
  const {
14
- route: { component, path, name, icon, routesGroups, onClick, url, target = "_blank" },
14
+ route: { component, path, name, icon, routesGroups, onClick, url, target = "_blank", avatar },
15
15
  location,
16
16
  type,
17
17
  isOpened,
@@ -20,89 +20,110 @@ const NavItem = (props: IProps) => {
20
20
 
21
21
  const { state, toggleSubmenu } = useContext(NavContext)!;
22
22
 
23
- const isSelected = location && (location.pathname === path || location.pathname.includes(path));
23
+ const isSelected = location ? location.pathname === path || location.pathname.includes(path) : false;
24
24
  const isSelectedClass = isSelected ? "selected" : "";
25
+ const hasImage = avatar ? "withImage" : "";
25
26
 
26
27
  const hasSubmenu = routesGroups && routesGroups.length > 0;
28
+ const isSubmenuOpen = !!state[name];
27
29
 
28
30
  const handleClick = () => {
29
31
  if (onClick) {
30
32
  onClick();
31
33
  } else if (url) {
32
34
  window.open(url, target);
33
- } else if (component && location.pathname !== path) {
35
+ } else if (component && location && location.pathname !== path) {
34
36
  setHistoryPush(path);
35
37
  } else if (isOpened && hasSubmenu) {
36
38
  toggleSubmenu(name);
37
39
  }
38
40
  };
39
41
 
40
- const subMenuIcon = state[name] ? "UpArrow" : "DownArrow";
42
+ const subMenuIcon = isSubmenuOpen ? "UpArrow" : "DownArrow";
41
43
 
42
44
  const DropdownClass = isOpened ? "inline" : "floating";
43
45
 
44
46
  return (
45
- <S.Item key={name} type={type} isOpened={isOpened} isSubmenuOpen={state[name]} active={isSelected}>
46
- <S.NavLink onClick={handleClick} className={isSelectedClass}>
47
- <S.Icon>{icon && <Icon name={icon} size="24px" />}</S.Icon>
48
- <S.LinkName active={isSelected || state[name]}>{name}</S.LinkName>
47
+ <S.Item key={name} type={type} isOpened={isOpened} isSubmenuOpen={isSubmenuOpen} active={isSelected}>
48
+ <S.NavLink onClick={handleClick} className={`${isSelectedClass} ${hasImage}`}>
49
+ {avatar ? (
50
+ <S.AvatarWithBorder>
51
+ <Avatar image={avatar.image} name={avatar.name} size={32} background bold />
52
+ </S.AvatarWithBorder>
53
+ ) : (
54
+ <S.Icon>{icon && <Icon name={icon} size="24px" />}</S.Icon>
55
+ )}
56
+ <S.LinkName active={isSelected || isSubmenuOpen}>{name}</S.LinkName>
49
57
  {hasSubmenu && isOpened && (
50
58
  <S.Arrow>
51
59
  <Icon name={subMenuIcon} />
52
60
  </S.Arrow>
53
61
  )}
54
62
  </S.NavLink>
55
- <S.Dropdown isSubmenu={hasSubmenu} isSubmenuOpen={state[name]} className={DropdownClass} type={type}>
63
+ <S.Dropdown isSubmenu={hasSubmenu} isSubmenuOpen={isSubmenuOpen} className={DropdownClass} type={type}>
56
64
  <S.Heading onClick={handleClick}>
57
65
  <div>{name}</div>
58
66
  </S.Heading>
59
- {routesGroups &&
60
- routesGroups.map((subGroup: any, groupI: any) => (
61
- <React.Fragment key={groupI}>
62
- {subGroup.routes && (
63
- <>
64
- {subGroup.routes.map((subLink: any, subI: any) => {
65
- const goToSubLink = () => {
66
- if (location.pathname !== subLink.path) {
67
- const isEditor = subLink.isEditor ? true : false;
68
- setHistoryPush(subLink.path, isEditor);
69
- }
70
- if (subLink.setDataAction) {
71
- const setCurrentData = (id: string) => subLink.setDataAction(id);
72
- setCurrentData(subLink.item.id);
73
- }
74
- };
75
-
76
- const isSelectedSubItem = location.pathname === subLink.path ? true : false;
77
-
78
- const navSubItem = (
79
- <NavSubItem key={subI} active={isSelectedSubItem} type={type}>
80
- <S.NavLink onClick={goToSubLink}>
81
- <S.LinkName active={isSelected}>{subLink.name}</S.LinkName>
82
- </S.NavLink>
83
- </NavSubItem>
84
- );
85
-
86
- return subLink.permission ? (
87
- <Restricted to={subLink.permission} key={subI}>
88
- {navSubItem}
89
- </Restricted>
90
- ) : (
91
- navSubItem
92
- );
93
- })}
94
- </>
95
- )}
96
- </React.Fragment>
97
- ))}
67
+ {routesGroups?.map((subGroup: IRouteGroup, groupI: number) => (
68
+ <React.Fragment key={`${subGroup.name}-${groupI}`}>
69
+ {subGroup.routes && (
70
+ <>
71
+ {subGroup.routes.map((subLink: ISubRoute, subI: number) => {
72
+ const goToSubLink = () => {
73
+ if (location && location.pathname !== subLink.path) {
74
+ const isEditor = !!subLink.isEditor;
75
+ setHistoryPush(subLink.path, isEditor);
76
+ }
77
+ if (subLink.setDataAction && subLink.item) {
78
+ subLink.setDataAction(subLink.item.id);
79
+ }
80
+ };
81
+
82
+ const isSelectedSubItem = location ? location.pathname === subLink.path : false;
83
+
84
+ const navSubItem = (
85
+ <NavSubItem key={`${subLink.path}-${subI}`} active={isSelectedSubItem} type={type}>
86
+ <S.NavLink onClick={goToSubLink}>
87
+ <S.LinkName active={isSelected}>{subLink.name}</S.LinkName>
88
+ </S.NavLink>
89
+ </NavSubItem>
90
+ );
91
+
92
+ return subLink.permission ? (
93
+ <Restricted to={subLink.permission} key={`${subLink.path}-${subI}`}>
94
+ {navSubItem}
95
+ </Restricted>
96
+ ) : (
97
+ navSubItem
98
+ );
99
+ })}
100
+ </>
101
+ )}
102
+ </React.Fragment>
103
+ ))}
98
104
  </S.Dropdown>
99
105
  </S.Item>
100
106
  );
101
107
  };
102
108
 
109
+ interface ISubRoute {
110
+ path: string;
111
+ component?: any;
112
+ name: string;
113
+ permission?: string;
114
+ isEditor?: boolean;
115
+ setDataAction?: (id: string) => void;
116
+ item?: { id: string };
117
+ }
118
+
119
+ interface IRouteGroup {
120
+ name: string;
121
+ routes: ISubRoute[];
122
+ }
123
+
103
124
  interface IProps {
104
125
  route: IRouter;
105
- location?: any;
126
+ location?: Location;
106
127
  type: string;
107
128
  isOpened: boolean;
108
129
  setHistoryPush(path: string, isEditor?: boolean): void;
@@ -1,6 +1,6 @@
1
1
  import styled from "styled-components";
2
2
 
3
- export const Heading = styled.li`
3
+ const Heading = styled.li`
4
4
  ${(p) => p.theme.textStyle.headingXXS};
5
5
  width: 100%;
6
6
  height: 100%;
@@ -8,7 +8,7 @@ export const Heading = styled.li`
8
8
  align-items: center;
9
9
  `;
10
10
 
11
- export const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean; type: string }>`
11
+ const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean; type: string }>`
12
12
  background-color: ${(p) =>
13
13
  p.type === "multisite" ? p.theme.color.uiMainMenuBackgroundDark : p.theme.color.uiSiteMenuDark};
14
14
  color: ${(p) => p.theme.color.textHighEmphasisInverse};
@@ -54,7 +54,7 @@ export const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean;
54
54
  }
55
55
  `;
56
56
 
57
- export const Arrow = styled.div`
57
+ const Arrow = styled.div`
58
58
  margin-left: auto;
59
59
  height: ${(p) => p.theme.spacing.m};
60
60
  width: ${(p) => p.theme.spacing.m};
@@ -64,13 +64,13 @@ export const Arrow = styled.div`
64
64
  }
65
65
  `;
66
66
 
67
- export const LinkName = styled.span<{ active: boolean }>`
67
+ const LinkName = styled.span<{ active: boolean }>`
68
68
  white-space: nowrap;
69
69
  overflow: hidden;
70
70
  color: ${(p) => (p.active ? p.theme.color.textHighEmphasisInverse : p.theme.color.textMediumEmphasisInverse)};
71
71
  `;
72
72
 
73
- export const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen: boolean; active: boolean }>`
73
+ const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen: boolean; active: boolean }>`
74
74
  position: relative;
75
75
  margin-left: ${(p) => (p.isSubmenuOpen ? "0" : p.theme.spacing.s)};
76
76
  margin-right: ${(p) => (p.isOpened && !p.isSubmenuOpen ? p.theme.spacing.s : "0")};
@@ -155,11 +155,16 @@ export const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen:
155
155
  }
156
156
  `;
157
157
 
158
- export const NavLink = styled.a`
158
+ const NavLink = styled.a`
159
159
  display: flex;
160
+
161
+ &.withImage {
162
+ padding: 2px;
163
+ margin-bottom: ${(p) => p.theme.spacing.s};
164
+ }
160
165
  `;
161
166
 
162
- export const Icon = styled.div`
167
+ const Icon = styled.div`
163
168
  display: flex;
164
169
  margin-right: ${(p) => p.theme.spacing.s};
165
170
  height: ${(p) => p.theme.spacing.m};
@@ -169,3 +174,12 @@ export const Icon = styled.div`
169
174
  fill: ${(p) => p.theme.color.textMediumEmphasisInverse};
170
175
  }
171
176
  `;
177
+
178
+ const AvatarWithBorder = styled.div`
179
+ display: flex;
180
+ border: 2px solid;
181
+ border-color: ${(p) => p.theme.color.textHighEmphasisInverse};
182
+ border-radius: 50%;
183
+ `;
184
+
185
+ export { Heading, Dropdown, Arrow, LinkName, Item, NavLink, Icon, AvatarWithBorder };