@griddo/ax 11.14.1 → 11.14.2-rc.1

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 (142) 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 +169 -0
  12. package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
  13. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
  14. package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
  15. package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
  16. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
  17. package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
  18. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
  19. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
  20. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
  21. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
  22. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
  23. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
  24. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
  25. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
  26. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
  27. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
  28. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
  29. package/src/api/sites.tsx +4 -4
  30. package/src/components/Avatar/index.tsx +26 -5
  31. package/src/components/Avatar/style.tsx +20 -10
  32. package/src/components/Browser/index.tsx +7 -1
  33. package/src/components/ConfigPanel/index.tsx +5 -4
  34. package/src/components/ElementsTooltip/index.tsx +96 -34
  35. package/src/components/ElementsTooltip/style.tsx +12 -1
  36. package/src/components/Fields/FileField/index.tsx +16 -17
  37. package/src/components/Fields/HeadingField/index.tsx +1 -1
  38. package/src/components/Fields/ImageField/index.tsx +9 -38
  39. package/src/components/Fields/ImageField/style.tsx +12 -1
  40. package/src/components/Fields/ToggleField/index.tsx +1 -1
  41. package/src/components/Fields/Wysiwyg/index.tsx +25 -20
  42. package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
  43. package/src/components/FileGallery/index.tsx +33 -28
  44. package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
  45. package/src/components/Gallery/index.tsx +0 -2
  46. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
  47. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
  48. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
  49. package/src/components/HeadingsPreviewModal/index.tsx +13 -3
  50. package/src/components/HeadingsPreviewModal/style.tsx +18 -0
  51. package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
  52. package/src/components/Image/index.tsx +2 -2
  53. package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
  54. package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
  55. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
  56. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
  57. package/src/components/ProfileImage/index.tsx +55 -0
  58. package/src/components/ProfileImage/style.tsx +58 -0
  59. package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
  60. package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
  61. package/src/components/ResizePanel/index.tsx +25 -4
  62. package/src/components/Tabs/style.tsx +1 -1
  63. package/src/components/Tag/index.tsx +0 -1
  64. package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
  65. package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
  66. package/src/components/UserRolesAndSites/index.tsx +102 -0
  67. package/src/components/UserRolesAndSites/style.tsx +67 -0
  68. package/src/components/index.tsx +6 -0
  69. package/src/constants/index.ts +13 -1
  70. package/src/containers/App/actions.tsx +8 -1
  71. package/src/containers/Sites/actions.tsx +26 -0
  72. package/src/containers/Sites/constants.tsx +1 -0
  73. package/src/containers/Sites/interfaces.tsx +6 -0
  74. package/src/containers/Sites/reducer.tsx +5 -1
  75. package/src/containers/Users/reducer.tsx +6 -5
  76. package/src/guards/routeLeaving/index.tsx +9 -11
  77. package/src/helpers/images.tsx +50 -3
  78. package/src/helpers/index.tsx +2 -1
  79. package/src/hooks/forms.tsx +45 -48
  80. package/src/hooks/modals.tsx +4 -3
  81. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
  82. package/src/modules/App/Routing/Logout/index.tsx +3 -5
  83. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
  84. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
  85. package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
  86. package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
  87. package/src/modules/CreatePass/index.tsx +1 -1
  88. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
  89. package/src/modules/FileDrive/FileModal/index.tsx +8 -9
  90. package/src/modules/FileDrive/index.tsx +1 -18
  91. package/src/modules/Forms/FormEditor/index.tsx +1 -1
  92. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
  93. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
  94. package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
  95. package/src/modules/MediaGallery/index.tsx +1 -3
  96. package/src/modules/Settings/Globals/constants.tsx +942 -106
  97. package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
  98. package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
  99. package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
  100. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
  101. package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
  102. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
  103. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
  104. package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
  105. package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
  106. package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
  107. package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
  108. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
  109. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
  110. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
  111. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
  112. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
  113. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
  114. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
  115. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
  116. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
  117. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
  118. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
  119. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
  120. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
  121. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
  122. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
  123. package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
  124. package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
  125. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
  126. package/src/modules/Sites/SitesList/atoms.tsx +4 -4
  127. package/src/modules/Sites/SitesList/hooks.tsx +149 -16
  128. package/src/modules/Sites/SitesList/index.tsx +127 -125
  129. package/src/modules/Sites/SitesList/style.tsx +1 -117
  130. package/src/modules/Sites/SitesList/utils.tsx +9 -2
  131. package/src/modules/Sites/index.tsx +19 -8
  132. package/src/modules/Users/Profile/index.tsx +169 -31
  133. package/src/modules/Users/Profile/style.tsx +81 -1
  134. package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
  135. package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
  136. package/src/modules/Users/UserForm/atoms.tsx +3 -3
  137. package/src/modules/Users/UserForm/index.tsx +25 -29
  138. package/src/modules/Users/UserForm/style.tsx +15 -2
  139. package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
  140. package/src/routes/index.tsx +1 -0
  141. package/src/types/index.tsx +2 -0
  142. /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
@@ -1,3 +1,4 @@
1
+ import { useEffect, useRef } from "react";
1
2
  import FroalaEditorComponent from "react-froala-wysiwyg";
2
3
  import { connect } from "react-redux";
3
4
 
@@ -12,7 +13,7 @@ import "./vendors";
12
13
 
13
14
  import * as S from "./style";
14
15
 
15
- const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
16
+ const Wysiwyg = (props: IWysiwygProps) => {
16
17
  const {
17
18
  value,
18
19
  error,
@@ -28,12 +29,20 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
28
29
  } = props;
29
30
 
30
31
  const imageSite = site ? site.id : "global";
32
+ const editorRef = useRef<any>(null);
33
+ const errorRef = useRef(error);
34
+ errorRef.current = error;
35
+
36
+ useEffect(() => {
37
+ if (!editorRef.current) return;
38
+ disabled ? editorRef.current.edit.off() : editorRef.current.edit.on();
39
+ }, [disabled]);
31
40
 
32
41
  const handleChange = (model: string) => {
33
42
  onChange(model);
34
43
  const stripedHtml = decodeEntities(model);
35
44
  setTimeout(() => {
36
- error && handleValidation && handleValidation(stripedHtml);
45
+ errorRef.current && handleValidation?.(stripedHtml);
37
46
  }, 300);
38
47
  };
39
48
 
@@ -49,28 +58,26 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
49
58
  Authorization: `bearer ${token}`,
50
59
  },
51
60
  events: {
52
- initialized() {
53
- // biome-ignore lint/complexity/noUselessThisAlias: TODO: fix this
54
- const editor: any = this;
61
+ initialized(this: any) {
62
+ editorRef.current = this;
63
+
55
64
  if (disabled) {
56
- setTimeout(() => {
57
- editor.edit.off();
58
- }, 1000);
65
+ this.edit.off();
59
66
  }
60
67
  },
61
- "image.beforeUpload": async function (images: FileList) {
62
- // biome-ignore lint/complexity/noUselessThisAlias: TODO: fix this
63
- const editor: any = this;
64
- const result = await uploadImage(images[0], imageSite);
65
- if (result) {
66
- editor.image.insert(result.url, true, null, editor.image.get(), null);
68
+ "image.beforeUpload": async function (this: any, images: FileList) {
69
+ try {
70
+ const result = await uploadImage(images[0], imageSite);
71
+ if (result) {
72
+ this.image.insert(result.url, true, null, this.image.get(), null);
73
+ }
74
+ } catch {
75
+ this.popups.hideAll();
67
76
  }
68
77
  return false;
69
78
  },
70
- blur: function () {
71
- // biome-ignore lint/complexity/noUselessThisAlias: TODO: fix this
72
- const editor: any = this;
73
- const html = editor.html.get();
79
+ blur: function (this: any) {
80
+ const html = this.html.get();
74
81
  const stripedHtml = decodeEntities(html);
75
82
  handleValidation?.(stripedHtml);
76
83
  },
@@ -88,9 +95,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
88
95
 
89
96
  interface IProps {
90
97
  value: string;
91
- title: string;
92
98
  full?: boolean;
93
- helptext?: string;
94
99
  error?: boolean;
95
100
  placeholder?: string;
96
101
  token: string;
@@ -1,25 +1,33 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
 
3
- import { IFile } from "@ax/types";
3
+ import FileDragAndDrop from "@ax/modules/FileDrive/FileDragAndDrop";
4
+ import type { IFile } from "@ax/types";
4
5
 
5
6
  import DetailPanel from "./DetailPanel";
6
- import FileDragAndDrop from "@ax/modules/FileDrive/FileDragAndDrop";
7
7
 
8
8
  import * as S from "./style";
9
9
 
10
10
  const GalleryPanel = (props: IGalleryPanelProps) => {
11
- const { selectedFile, validFormats, setFile, scope, currentFolderID, isAllowedToAdd, isAllowedToEdit, handleUpload } =
12
- props;
11
+ const {
12
+ selectedFile,
13
+ setFile,
14
+ scope,
15
+ currentFolderID,
16
+ isAllowedToAdd,
17
+ isAllowedToEdit,
18
+ customFormats,
19
+ handleUpload,
20
+ } = props;
13
21
 
14
22
  return (
15
23
  <S.GalleryPanel>
16
24
  {!selectedFile ? (
17
25
  <FileDragAndDrop
18
- validFormats={validFormats}
19
26
  folderID={currentFolderID}
20
27
  handleUpload={handleUpload}
21
28
  siteID={scope}
22
29
  isAllowedToUpload={isAllowedToAdd}
30
+ customFormats={customFormats}
23
31
  />
24
32
  ) : (
25
33
  <DetailPanel selectedFile={selectedFile} setFile={setFile} isAllowedToEdit={isAllowedToEdit} />
@@ -30,7 +38,7 @@ const GalleryPanel = (props: IGalleryPanelProps) => {
30
38
 
31
39
  export interface IGalleryPanelProps {
32
40
  selectedFile: IFile | null;
33
- validFormats: string[];
41
+ customFormats?: string[];
34
42
  setFile: (fileData: any) => void;
35
43
  scope: number | "global";
36
44
  currentFolderID: number | null;
@@ -1,43 +1,51 @@
1
- import React, { useEffect, useState, memo, useRef, useCallback, useLayoutEffect } from "react";
1
+ import { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { IFile, IFilesFolder, IFolder, IFolderTree, IGetFolderParams, IQueryValue, IRootState, ISite } from "@ax/types";
5
4
  import {
6
- Loader,
7
- Tabs,
8
- SearchField,
5
+ BackFolder,
9
6
  EmptyState,
10
7
  ErrorToast,
11
- Notification,
12
- Tooltip,
8
+ FilterTagsBar,
13
9
  Icon,
14
- BackFolder,
10
+ Loader,
11
+ Notification,
12
+ SearchField,
15
13
  SearchTagsBar,
16
- FilterTagsBar,
14
+ Tabs,
15
+ Tooltip,
17
16
  } from "@ax/components";
18
17
  import { fileDriveActions } from "@ax/containers/FileDrive";
19
18
  import { usePermission, useResizable } from "@ax/hooks";
20
-
21
- import GalleryPanel from "./GalleryPanel";
22
- import GridItem from "./GridItem";
23
- import FolderItem from "./FolderItem";
24
-
25
- import * as S from "./style";
26
-
27
19
  // refactor
28
20
  import Breadcrumb from "@ax/modules/FileDrive/Breadcrumb";
29
- import FolderTree from "@ax/modules/FileDrive/FolderTree";
30
- import Type from "@ax/modules/FileDrive/FileFilters/Type";
31
21
  import SortBy from "@ax/modules/FileDrive/FileFilters/SortBy";
22
+ import Type from "@ax/modules/FileDrive/FileFilters/Type";
23
+ import FolderTree from "@ax/modules/FileDrive/FolderTree";
32
24
  import { useFilterQuery, useSortedListStatus } from "@ax/modules/FileDrive/hooks";
33
25
  import { getSortedListStatus } from "@ax/modules/FileDrive/utils";
26
+ import type {
27
+ IFile,
28
+ IFilesFolder,
29
+ IFolder,
30
+ IFolderTree,
31
+ IGetFolderParams,
32
+ IQueryValue,
33
+ IRootState,
34
+ ISite,
35
+ } from "@ax/types";
36
+
37
+ import FolderItem from "./FolderItem";
38
+ import GalleryPanel from "./GalleryPanel";
39
+ import GridItem from "./GridItem";
40
+
41
+ import * as S from "./style";
34
42
 
35
43
  const FileGallery = (props: IProps): JSX.Element => {
36
44
  const {
37
45
  currentFolderContent,
38
46
  site,
39
47
  currentFolderID,
40
- validFormats,
48
+ customFormats,
41
49
  breadcrumb,
42
50
  toggleModal,
43
51
  getFolderContent,
@@ -288,13 +296,10 @@ const FileGallery = (props: IProps): JSX.Element => {
288
296
  </S.EmptyWrapper>
289
297
  ) : (
290
298
  <S.Grid>
291
- {items &&
292
- items.map((item: IFile) => {
293
- const isSelected = item.id === selectedFile?.id;
294
- return (
295
- <GridItem file={item} onClick={handleClick} key={item.fileName} isSelected={isSelected} />
296
- );
297
- })}
299
+ {items?.map((item: IFile) => {
300
+ const isSelected = item.id === selectedFile?.id;
301
+ return <GridItem file={item} onClick={handleClick} key={item.fileName} isSelected={isSelected} />;
302
+ })}
298
303
  </S.Grid>
299
304
  )}
300
305
  </S.SectionWrapper>
@@ -304,7 +309,7 @@ const FileGallery = (props: IProps): JSX.Element => {
304
309
  </S.ContentWrapper>
305
310
  <GalleryPanel
306
311
  selectedFile={selectedFile}
307
- validFormats={validFormats}
312
+ customFormats={customFormats}
308
313
  setFile={setFile}
309
314
  scope={galleryScope}
310
315
  currentFolderID={currentFolderID}
@@ -321,7 +326,7 @@ export interface IGalleryProps {
321
326
  currentFolderContent: IFilesFolder | null;
322
327
  currentFolderID: number | null;
323
328
  isLoading: boolean;
324
- validFormats: string[];
329
+ customFormats?: string[];
325
330
  breadcrumb: IFolderTree[];
326
331
  toggleModal: () => void;
327
332
  addFile: (file: IFile) => void;
@@ -1,30 +1,20 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
 
3
- import { IGetFolderParams, IImage } from "@ax/types";
3
+ import { ImageDragAndDrop } from "@ax/components";
4
+ import type { IGetFolderParams, IImage } from "@ax/types";
4
5
 
5
6
  import DetailPanel from "./DetailPanel";
6
- import ImageDragAndDrop from "@ax/modules/MediaGallery/ImageDragAndDrop";
7
7
 
8
8
  import * as S from "./style";
9
9
 
10
10
  const GalleryPanel = (props: IGalleryPanelProps) => {
11
- const {
12
- selectedImage,
13
- validFormats,
14
- setImage,
15
- scope,
16
- currentFolderID,
17
- isAllowedToAdd,
18
- isAllowedToEdit,
19
- handleUpload,
20
- getParams,
21
- } = props;
11
+ const { selectedImage, setImage, scope, currentFolderID, isAllowedToAdd, isAllowedToEdit, handleUpload, getParams } =
12
+ props;
22
13
 
23
14
  return (
24
15
  <S.GalleryPanel>
25
16
  {!selectedImage ? (
26
17
  <ImageDragAndDrop
27
- validFormats={validFormats}
28
18
  folderID={currentFolderID}
29
19
  handleUpload={handleUpload}
30
20
  siteID={scope}
@@ -44,7 +34,6 @@ const GalleryPanel = (props: IGalleryPanelProps) => {
44
34
 
45
35
  export interface IGalleryPanelProps {
46
36
  selectedImage: IImage | null;
47
- validFormats: string[];
48
37
  setImage: (fileData: any) => void;
49
38
  scope: number | "global";
50
39
  currentFolderID: number | null;
@@ -60,7 +60,6 @@ const Gallery = (props: IProps): JSX.Element => {
60
60
  const galleryScope = isLocalTab && site ? site.id : "global";
61
61
  const hasFolders = !!folders?.length;
62
62
  const isRoot = !breadcrumb?.length;
63
- const validFormats = ["jpeg", "jpg", "png", "svg", "gif"];
64
63
 
65
64
  const galleryRef = useRef<HTMLDivElement>(null);
66
65
 
@@ -345,7 +344,6 @@ const Gallery = (props: IProps): JSX.Element => {
345
344
  </S.ContentWrapper>
346
345
  <GalleryPanel
347
346
  selectedImage={selectedImage}
348
- validFormats={validFormats}
349
347
  setImage={setImage}
350
348
  scope={galleryScope}
351
349
  currentFolderID={currentFolderID}
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  import { Icon, Tooltip } from "@ax/components";
4
4
 
@@ -7,13 +7,19 @@ import type { IHeadingError } from "../../utils";
7
7
  import * as S from "./style";
8
8
 
9
9
  const ErrorsItem = (props: IErrorsItemProps) => {
10
- const { error, onSelectHeading, onDelete } = props;
10
+ const { error, onSelectHeading, onDelete, onNavigateHeading, parentIsOpen } = props;
11
11
  const { message, description, headingIds } = error;
12
12
 
13
13
  const [isOpen, setIsOpen] = useState(false);
14
14
  const [isDeleted, setIsDeleted] = useState(false);
15
15
  const [currentIndex, setCurrentIndex] = useState<number | null>(null);
16
16
 
17
+ useEffect(() => {
18
+ if (!parentIsOpen) {
19
+ setIsOpen(false);
20
+ }
21
+ }, [parentIsOpen]);
22
+
17
23
  const handlePrevious = () => {
18
24
  if (!currentIndex) return;
19
25
  const newIndex = currentIndex - 1;
@@ -32,6 +38,7 @@ const ErrorsItem = (props: IErrorsItemProps) => {
32
38
  setIsOpen(!isOpen);
33
39
  setCurrentIndex(0);
34
40
  onSelectHeading(headingIds[0])();
41
+ !isOpen && onNavigateHeading();
35
42
  };
36
43
 
37
44
  const errorText =
@@ -86,6 +93,8 @@ interface IErrorsItemProps {
86
93
  error: IHeadingError;
87
94
  onSelectHeading: (id: number) => () => void;
88
95
  onDelete: () => void;
96
+ onNavigateHeading: () => void;
97
+ parentIsOpen: boolean;
89
98
  }
90
99
 
91
100
  export default ErrorsItem;
@@ -12,6 +12,7 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
12
12
 
13
13
  const [isDeleted, setIsDeleted] = useState(false);
14
14
  const [deletedErrorIndices, setDeletedErrorIndices] = useState<Set<number>>(new Set());
15
+ const [isSticky, setIsSticky] = useState(false);
15
16
 
16
17
  const allErrorsDeleted = useMemo(
17
18
  () => deletedErrorIndices.size === errors.length && errors.length > 0,
@@ -22,22 +23,37 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
22
23
  setDeletedErrorIndices((prev) => new Set([...prev, index]));
23
24
  };
24
25
 
26
+ const handleNavigateHeading = () => {
27
+ setIsSticky(true);
28
+ };
29
+
25
30
  if (isDeleted || allErrorsDeleted) {
26
31
  return <></>;
27
32
  }
28
33
 
29
34
  return (
30
- <S.ErrorsWrapper>
35
+ <S.ErrorsWrapper isSticky={isSticky}>
31
36
  <S.ErrorsHeader>
32
37
  <S.WarningWrapper>
33
38
  <Icon name="warning" size="16" />
34
39
  </S.WarningWrapper>
35
40
  <S.HeaderText>SEO Alerts</S.HeaderText>
36
41
  <S.HeaderActions>
37
- <S.ToggleWrapper onClick={() => setIsOpen(!isOpen)}>
42
+ <S.ToggleWrapper
43
+ onClick={() => {
44
+ setIsOpen(!isOpen);
45
+ setIsSticky(false);
46
+ }}
47
+ >
38
48
  <Icon name={isOpen ? "UpArrow" : "DownArrow"} size="24" />
39
49
  </S.ToggleWrapper>
40
- <S.IconWrapper onClick={() => setIsDeleted(true)}>
50
+ <S.IconWrapper
51
+ onClick={() => {
52
+ setIsDeleted(true);
53
+ setIsSticky(false);
54
+ setIsOpen(false);
55
+ }}
56
+ >
41
57
  <Icon name="close" size="16" />
42
58
  </S.IconWrapper>
43
59
  </S.HeaderActions>
@@ -53,6 +69,8 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
53
69
  error={error}
54
70
  onSelectHeading={onSelectHeading}
55
71
  onDelete={() => handleErrorDelete(index)}
72
+ onNavigateHeading={handleNavigateHeading}
73
+ parentIsOpen={isOpen}
56
74
  />
57
75
  ))}
58
76
  </S.ErrorListWrapper>
@@ -1,7 +1,7 @@
1
1
  import styled from "styled-components";
2
2
 
3
- const ErrorsWrapper = styled.div`
4
- position: sticky;
3
+ const ErrorsWrapper = styled.div<{ isSticky: boolean }>`
4
+ position: ${(p) => (p.isSticky ? "sticky" : "static")};
5
5
  top: 0;
6
6
  background-color: ${(p) => p.theme.colors.uiBackground03};
7
7
  width: 100%;
@@ -1,11 +1,11 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
 
3
- import { EmptyState, FloatingPanel } from "@ax/components";
3
+ import { EmptyState, FloatingPanel, ToggleField } from "@ax/components";
4
4
  import type { HeadingFilter, HeadingNode } from "@ax/types";
5
5
 
6
6
  import ErrorsBanner from "./ErrorsBanner";
7
7
  import HeadingItem from "./HeadingItem";
8
- import { analyzeHeadings, extractUniqueHeadingTypes, filterHeadings, parseHeadingsTree } from "./utils";
8
+ import { analyzeHeadings, extractUniqueHeadingTypes, filterHeadings, hasHiddenHeadings, parseHeadingsTree } from "./utils";
9
9
 
10
10
  import * as S from "./style";
11
11
 
@@ -17,12 +17,14 @@ const HeadingsPreviewModal = (props: IHeadingsPreviewProps) => {
17
17
 
18
18
  const [headings, setHeadings] = useState<HeadingNode[]>([]);
19
19
  const [selected, setSelected] = useState<number | null>(null);
20
+ const [toggleStatus, setToggleStatus] = useState<boolean>(true);
20
21
  const [isErrorsBannerOpen, setIsErrorsBannerOpen] = useState(false);
21
22
  const [errorsResetKey, setErrorsResetKey] = useState(0);
22
23
  const listRef = useRef<HTMLDivElement>(null);
23
24
 
24
25
  const filters = useMemo<HeadingFilter[]>(() => ["all", ...extractUniqueHeadingTypes(headings)], [headings]);
25
- const filteredHeadings = useMemo(() => filterHeadings(headings, headingsFilter), [headings, headingsFilter]);
26
+ const filteredHeadings = useMemo(() => filterHeadings(headings, headingsFilter, toggleStatus), [headings, headingsFilter, toggleStatus]);
27
+ const showToggleWrapper = useMemo(() => hasHiddenHeadings(headings, headingsFilter), [headings, headingsFilter]);
26
28
  const errors = useMemo(() => analyzeHeadings(filteredHeadings, isFiltering), [filteredHeadings, isFiltering]);
27
29
 
28
30
  const getSEOHeadings = useCallback(() => {
@@ -117,6 +119,14 @@ const HeadingsPreviewModal = (props: IHeadingsPreviewProps) => {
117
119
  </S.FilterItem>
118
120
  ))}
119
121
  </S.FiltersWrapper>
122
+ {showToggleWrapper && (
123
+ <S.ToggleWrapper>
124
+ <S.ToggleText>
125
+ Show <strong>hidden headings</strong> (not visible on page)
126
+ </S.ToggleText>
127
+ <ToggleField name="hideHidden" value={toggleStatus} onChange={setToggleStatus} size="s" />
128
+ </S.ToggleWrapper>
129
+ )}
120
130
  <S.HeadingsListWrapper ref={listRef}>
121
131
  {filteredHeadings.map((head, index) => (
122
132
  <HeadingItem
@@ -45,6 +45,22 @@ const FiltersWrapper = styled.div`
45
45
  }
46
46
  `;
47
47
 
48
+ const ToggleWrapper = styled.div`
49
+ display: flex;
50
+ background-color: ${(p) => p.theme.colors.uiBackground03};
51
+ border-radius: ${(p) => p.theme.radii.s};
52
+ padding: ${(p) => p.theme.spacing.xs};
53
+ margin-bottom: ${(p) => p.theme.spacing.xs};
54
+ gap: ${(p) => p.theme.spacing.xs};
55
+ width: 100%;
56
+ justify-content: space-between;
57
+ align-items: center;
58
+ `;
59
+
60
+ const ToggleText = styled.div`
61
+ ${(p) => p.theme.textStyle.uiXS};
62
+ `;
63
+
48
64
  const FilterText = styled.div`
49
65
  ${(p) => p.theme.textStyle.uiS};
50
66
  color: ${(p) => p.theme.colors.textHighEmphasis};
@@ -79,4 +95,6 @@ export {
79
95
  FilterItem,
80
96
  HeadingsListWrapper,
81
97
  EmptyWrapper,
98
+ ToggleWrapper,
99
+ ToggleText,
82
100
  };
@@ -275,11 +275,29 @@ const extractUniqueHeadingTypes = (headings: HeadingNode[]): HeadingFilter[] =>
275
275
  return Array.from(types).sort();
276
276
  };
277
277
 
278
- const filterHeadings = (headings: HeadingNode[], selectedType: string): HeadingNode[] => {
279
- if (selectedType === "all") return headings;
278
+ const filterHeadings = (headings: HeadingNode[], selectedType: string, includeHidden = true): HeadingNode[] => {
279
+ if (selectedType === "all" && includeHidden) return headings;
280
280
 
281
+ if (selectedType === "all") {
282
+ // Filter by visibility but keep hierarchy
283
+ const filterNode = (node: HeadingNode): HeadingNode | null => {
284
+ const isVisible = !node.isHidden;
285
+ const filteredChildren = node.children.map(filterNode).filter((n): n is HeadingNode => n !== null);
286
+
287
+ if (isVisible) {
288
+ return { ...node, children: filteredChildren };
289
+ }
290
+
291
+ return filteredChildren.length > 0 ? { ...node, children: filteredChildren } : null;
292
+ };
293
+
294
+ return headings.map(filterNode).filter((n): n is HeadingNode => n !== null);
295
+ }
296
+
297
+ // For specific types, flatten the result
281
298
  return flattenHeadings(headings)
282
299
  .filter((h) => h.tag === selectedType)
300
+ .filter((h) => includeHidden || !h.isHidden)
283
301
  .map((h) => ({
284
302
  level: h.level,
285
303
  tag: h.tag,
@@ -289,6 +307,16 @@ const filterHeadings = (headings: HeadingNode[], selectedType: string): HeadingN
289
307
  }));
290
308
  };
291
309
 
310
+ const hasHiddenHeadings = (headings: HeadingNode[], filterType = "all"): boolean => {
311
+ const check = (node: HeadingNode): boolean => {
312
+ const matchesType = filterType === "all" || node.tag === filterType;
313
+ if (matchesType && node.isHidden) return true;
314
+ return node.children.length > 0 && node.children.some(check);
315
+ };
316
+
317
+ return headings.some(check);
318
+ };
319
+
292
320
  interface IHeadingError {
293
321
  message: string;
294
322
  description: React.ReactNode;
@@ -303,5 +331,5 @@ interface IFlatHeading {
303
331
  isHidden: boolean;
304
332
  }
305
333
 
306
- export { parseHeadingsTree, extractUniqueHeadingTypes, filterHeadings, analyzeHeadings, getHeadColor };
334
+ export { parseHeadingsTree, extractUniqueHeadingTypes, filterHeadings, analyzeHeadings, getHeadColor, hasHiddenHeadings };
307
335
  export type { IHeadingError };
@@ -1,5 +1,5 @@
1
- import { GriddoImage, GriddoImageCommonProps } from "@griddo/core";
2
- import React from "react";
1
+ import { GriddoImage, type GriddoImageCommonProps } from "@griddo/core";
2
+
3
3
  import { createCloudinaryUrl, isCloudinary } from "./utils";
4
4
 
5
5
  const DAM_DEFAULTS = {
@@ -0,0 +1,95 @@
1
+ import { memo, useCallback, useState } from "react";
2
+ import type { Area } from "react-easy-crop";
3
+ import Cropper from "react-easy-crop";
4
+
5
+ import { Button } from "@ax/components";
6
+
7
+ import * as S from "./style";
8
+
9
+ const MIN_ZOOM = 1;
10
+ const MAX_ZOOM = 3;
11
+ const ZOOM_STEP = 0.1;
12
+
13
+ const CropStep = (props: IProps) => {
14
+ const { imageSrc, onConfirm, onCancel } = props;
15
+
16
+ const [crop, setCrop] = useState({ x: 0, y: 0 });
17
+ const [zoom, setZoom] = useState(1);
18
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
19
+ const [objectFit, setObjectFit] = useState<"vertical-cover" | "horizontal-cover">("vertical-cover");
20
+
21
+ const onCropComplete = useCallback((_: Area, pixels: Area) => {
22
+ setCroppedAreaPixels(pixels);
23
+ }, []);
24
+
25
+ const onMediaLoaded = useCallback((mediaSize: { width: number; height: number }) => {
26
+ const isPortrait = mediaSize.height > mediaSize.width;
27
+ setObjectFit(isPortrait ? "vertical-cover" : "horizontal-cover");
28
+ }, []);
29
+
30
+ const handleConfirm = () => {
31
+ if (croppedAreaPixels) onConfirm(croppedAreaPixels);
32
+ };
33
+
34
+ const handleZoomIn = () => setZoom((z) => Math.min(z + ZOOM_STEP, MAX_ZOOM));
35
+ const handleZoomOut = () => setZoom((z) => Math.max(z - ZOOM_STEP, MIN_ZOOM));
36
+
37
+ return (
38
+ <S.Wrapper>
39
+ <S.CropArea>
40
+ <S.CropContent>
41
+ <S.CropContainer>
42
+ <Cropper
43
+ image={imageSrc}
44
+ crop={crop}
45
+ zoom={zoom}
46
+ aspect={1}
47
+ cropShape="round"
48
+ showGrid={false}
49
+ onCropChange={setCrop}
50
+ onZoomChange={setZoom}
51
+ onCropComplete={onCropComplete}
52
+ onMediaLoaded={onMediaLoaded}
53
+ objectFit={objectFit}
54
+ />
55
+ </S.CropContainer>
56
+ <S.ZoomControls>
57
+ <S.ZoomButton type="button" onClick={handleZoomIn} aria-label="Zoom in">
58
+ +
59
+ </S.ZoomButton>
60
+ <S.ZoomSlider
61
+ type="range"
62
+ min={MIN_ZOOM}
63
+ max={MAX_ZOOM}
64
+ step={ZOOM_STEP}
65
+ value={zoom}
66
+ onChange={(e) => setZoom(Number(e.target.value))}
67
+ aria-label="Zoom"
68
+ />
69
+ <S.ZoomButton type="button" onClick={handleZoomOut} aria-label="Zoom out">
70
+ {"\u2212"}
71
+ </S.ZoomButton>
72
+ </S.ZoomControls>
73
+ </S.CropContent>
74
+ </S.CropArea>
75
+ <S.CancelButtonContainer>
76
+ <Button type="button" buttonStyle="minimal" icon="refresh" onClick={onCancel}>
77
+ Upload new image
78
+ </Button>
79
+ </S.CancelButtonContainer>
80
+ <S.Actions>
81
+ <Button type="button" buttonStyle="solid" onClick={handleConfirm}>
82
+ Done
83
+ </Button>
84
+ </S.Actions>
85
+ </S.Wrapper>
86
+ );
87
+ };
88
+
89
+ interface IProps {
90
+ imageSrc: string;
91
+ onConfirm: (croppedAreaPixels: Area) => void;
92
+ onCancel: () => void;
93
+ }
94
+
95
+ export default memo(CropStep);