@griddo/ax 1.65.27 → 1.66.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 (57) hide show
  1. package/package.json +2 -2
  2. package/src/GlobalStore.tsx +4 -3
  3. package/src/api/structuredData.tsx +15 -1
  4. package/src/components/Browser/index.tsx +13 -16
  5. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +12 -0
  6. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +11 -0
  7. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +13 -0
  8. package/src/components/ConfigPanel/Form/index.tsx +3 -1
  9. package/src/components/ConfigPanel/index.tsx +19 -5
  10. package/src/components/ConfigPanel/style.tsx +5 -0
  11. package/src/components/FieldContainer/index.tsx +4 -0
  12. package/src/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/index.tsx +49 -0
  13. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +51 -11
  14. package/src/components/Fields/ComponentArray/MixableComponentArray/style.tsx +14 -1
  15. package/src/components/Fields/ComponentContainer/index.tsx +24 -5
  16. package/src/components/Fields/ImageField/index.tsx +18 -5
  17. package/src/components/Image/index.tsx +25 -0
  18. package/src/components/Notification/index.tsx +3 -1
  19. package/src/components/index.tsx +2 -0
  20. package/src/containers/Navigation/Defaults/actions.tsx +27 -9
  21. package/src/containers/PageEditor/actions.tsx +104 -5
  22. package/src/containers/PageEditor/constants.tsx +2 -0
  23. package/src/containers/PageEditor/interfaces.tsx +12 -0
  24. package/src/containers/PageEditor/reducer.tsx +8 -0
  25. package/src/containers/PageEditor/utils.tsx +2 -2
  26. package/src/helpers/index.tsx +6 -0
  27. package/src/helpers/schemas.tsx +36 -7
  28. package/src/modules/App/Routing/index.tsx +1 -1
  29. package/src/modules/Content/OptionTable/index.tsx +44 -43
  30. package/src/modules/Content/OptionTable/store.tsx +1 -1
  31. package/src/modules/Content/OptionTable/style.tsx +27 -12
  32. package/src/modules/Content/PageItem/index.tsx +14 -4
  33. package/src/modules/Content/atoms.tsx +19 -2
  34. package/src/modules/Content/index.tsx +37 -14
  35. package/src/modules/Content/utils.tsx +27 -12
  36. package/src/modules/GlobalEditor/Editor/index.tsx +12 -1
  37. package/src/modules/GlobalEditor/index.tsx +20 -2
  38. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +13 -0
  39. package/src/modules/Navigation/Defaults/atoms.tsx +28 -0
  40. package/src/modules/Navigation/Defaults/index.tsx +30 -4
  41. package/src/modules/Navigation/Defaults/style.tsx +32 -1
  42. package/src/modules/PageEditor/Editor/index.tsx +16 -1
  43. package/src/modules/PageEditor/index.tsx +14 -1
  44. package/src/modules/PublicPreview/index.tsx +15 -18
  45. package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/index.tsx +35 -0
  46. package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/style.tsx +22 -0
  47. package/src/modules/Settings/Globals/NavigationModules/SideModal/index.tsx +111 -0
  48. package/src/modules/Settings/Globals/NavigationModules/SideModal/style.tsx +64 -0
  49. package/src/modules/Settings/Globals/NavigationModules/index.tsx +89 -0
  50. package/src/modules/Settings/Globals/NavigationModules/style.tsx +36 -0
  51. package/src/modules/Settings/Globals/index.tsx +38 -1
  52. package/src/modules/Sites/SitesList/SiteItem/index.tsx +7 -5
  53. package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +14 -3
  54. package/src/modules/StructuredData/StructuredDataList/OptionTable/style.tsx +11 -2
  55. package/src/modules/StructuredData/StructuredDataList/atoms.tsx +19 -2
  56. package/src/modules/StructuredData/StructuredDataList/index.tsx +4 -13
  57. package/src/types/index.tsx +11 -0
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
  import { RouteComponentProps } from "react-router-dom";
4
4
 
5
- import { IErrorItem, IRootState, ISavePageParams, IUserEditing } from "@ax/types";
5
+ import { IErrorItem, INotification, IRootState, ISavePageParams, IUserEditing } from "@ax/types";
6
6
  import { MainWrapper, Loading, ErrorToast, Notification, Modal } from "@ax/components";
7
7
  import { pageEditorActions } from "@ax/containers/PageEditor";
8
8
  import { structuredDataActions } from "@ax/containers/StructuredData";
@@ -48,6 +48,7 @@ const GlobalEditor = (props: IProps) => {
48
48
  const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
49
49
  const [isReadOnly, setIsReadOnly] = useState(false);
50
50
  const [selectedTab, setSelectedTab] = useState("edit");
51
+ const [notification, setNotification] = useState<INotification | null>(null);
51
52
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent.editorContent, isNewTranslation);
52
53
 
53
54
  const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
@@ -442,10 +443,27 @@ const GlobalEditor = (props: IProps) => {
442
443
  />
443
444
  </S.NotificationWrapper>
444
445
  )}
446
+ {notification && (
447
+ <S.NotificationWrapper>
448
+ <Notification
449
+ type={notification.type}
450
+ text={notification.text}
451
+ btnText={notification.btnText}
452
+ onClick={notification.onClick}
453
+ resetError={() => setNotification(null)}
454
+ />
455
+ </S.NotificationWrapper>
456
+ )}
445
457
  <ErrorToast size="l" />
446
458
  {selectedTab === "edit" ? (
447
459
  <S.Content>
448
- <Editor isGlobal={true} isEditable={isEditable} isReadOnly={isReadOnly} theme={theme} />
460
+ <Editor
461
+ isGlobal={true}
462
+ isEditable={isEditable}
463
+ isReadOnly={isReadOnly}
464
+ theme={theme}
465
+ setNotification={setNotification}
466
+ />
449
467
  </S.Content>
450
468
  ) : (
451
469
  <Preview theme={theme} />
@@ -31,6 +31,7 @@ const DefaultsEditor = (props: IProps) => {
31
31
  setHeader,
32
32
  setFooter,
33
33
  setHistoryPush,
34
+ currentSiteInfo,
34
35
  } = props;
35
36
 
36
37
  const { isOpen, toggleModal } = useModal();
@@ -52,6 +53,16 @@ const DefaultsEditor = (props: IProps) => {
52
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
54
  }, [lang]);
54
55
 
56
+ useEffect(() => {
57
+ const navigationModuleComponent = currentSiteInfo.navigationModules?.[editorContent?.type];
58
+ const currentNavigation = currentDefaultsContent?.find((item: any) => item.id === editorContent?.id);
59
+ if (navigationModuleComponent && editorContent && currentNavigation) {
60
+ const isNavigationModuleChanged = navigationModuleComponent !== currentNavigation.component;
61
+ isNavigationModuleChanged && setIsDirty(true);
62
+ }
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [currentSiteInfo, editorContent?.id]);
65
+
55
66
  const save = () => {
56
67
  isNew || isNewTranslation
57
68
  ? createNavigation().then((isSaved: boolean) => {
@@ -160,6 +171,7 @@ const mapStateToProps = (state: IRootState) => ({
160
171
  navLanguages: state.navigation.currentNavigationLanguages,
161
172
  currentDefaultsContent: state.navigation.currentDefaultsContent,
162
173
  isNewTranslation: state.navigation.isNewTranslation,
174
+ currentSiteInfo: state.sites.currentSiteInfo,
163
175
  });
164
176
 
165
177
  interface IStateProps {
@@ -174,6 +186,7 @@ interface IStateProps {
174
186
  footer: number | null;
175
187
  navLanguages: any[];
176
188
  isNewTranslation: boolean;
189
+ currentSiteInfo: any;
177
190
  }
178
191
 
179
192
  const mapDispatchToProps = {
@@ -0,0 +1,28 @@
1
+ import React from "react";
2
+ import * as S from "./style";
3
+
4
+ const NavigationModulesWarning = (props: INavigationModulesWarning): JSX.Element => {
5
+ const { goTo } = props;
6
+
7
+ return (
8
+ <S.NavigationModulesWarning>
9
+ <S.NavigationModulesTitle>Navigation Modules</S.NavigationModulesTitle>
10
+ <S.NavigationModulesDescription>
11
+ Create as many headers as you need for your site. You can decide on which page to use each one. To change the
12
+ header design, you have to go to{" "}
13
+ <S.SiteSettingsLink>
14
+ <span onClick={goTo} onKeyPress={goTo} role="checkbox" aria-checked="false" tabIndex={0}>
15
+ Site Settings
16
+ </span>
17
+ </S.SiteSettingsLink>
18
+ .
19
+ </S.NavigationModulesDescription>
20
+ </S.NavigationModulesWarning>
21
+ );
22
+ };
23
+
24
+ interface INavigationModulesWarning {
25
+ goTo(): void;
26
+ }
27
+
28
+ export { NavigationModulesWarning };
@@ -5,14 +5,15 @@ import { appActions } from "@ax/containers/App";
5
5
  import { menuActions } from "@ax/containers/Navigation";
6
6
  import { IRootState, IHeader, IFooter } from "@ax/types";
7
7
  import { useBulkSelection, useToast } from "@ax/hooks";
8
- import { capitalize } from "@ax/helpers";
8
+ import { capitalize, isMultipleNavigationModules } from "@ax/helpers";
9
9
  import { navigationActions } from "@ax/containers/Navigation";
10
- import { MainWrapper, TableList, ErrorToast, Toast } from "@ax/components";
10
+ import { MainWrapper, TableList, ErrorToast, Toast, Notification } from "@ax/components";
11
11
 
12
12
  import DefaultItem from "./Item";
13
13
  import DefaultNav from "./Nav";
14
14
  import BulkHeader from "./BulkHeader";
15
15
 
16
+ import { NavigationModulesWarning } from "./atoms";
16
17
  import * as S from "./style";
17
18
 
18
19
  const DefaultsList = (props: IProps): JSX.Element => {
@@ -33,6 +34,7 @@ const DefaultsList = (props: IProps): JSX.Element => {
33
34
  deleteNavigation,
34
35
  getMenus,
35
36
  resetDefaultsValues,
37
+ currentSiteInfo,
36
38
  } = props;
37
39
 
38
40
  const [page, setPage] = useState(1);
@@ -40,6 +42,7 @@ const DefaultsList = (props: IProps): JSX.Element => {
40
42
  const { isVisible, toggleToast, setIsVisible } = useToast();
41
43
  const [isScrolling, setIsScrolling] = useState(false);
42
44
  const tableRef = useRef<HTMLDivElement>(null);
45
+ const [isNavigationNotificationOpen, setIsNavigationNotificationOpen] = useState(false);
43
46
 
44
47
  const navIds = currentDefaultsContent && currentDefaultsContent.map((nav: any) => nav.id);
45
48
 
@@ -58,6 +61,8 @@ const DefaultsList = (props: IProps): JSX.Element => {
58
61
 
59
62
  const currentType = selectedDefault === "Headers" ? "header" : "footer";
60
63
 
64
+ const showNavigationModulesWarning = isMultipleNavigationModules();
65
+
61
66
  const getParams = useCallback(() => {
62
67
  return {
63
68
  page,
@@ -92,13 +97,23 @@ const DefaultsList = (props: IProps): JSX.Element => {
92
97
  // eslint-disable-next-line react-hooks/exhaustive-deps
93
98
  }, []);
94
99
 
100
+ useEffect(() => {
101
+ const isNavigationModulesChanged =
102
+ currentSiteInfo.navigationModules?.[currentType] &&
103
+ currentDefaultsContent.some(
104
+ (navigation) => navigation.component !== currentSiteInfo.navigationModules[currentType]
105
+ );
106
+ setIsNavigationNotificationOpen(isNavigationModulesChanged);
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [currentDefaultsContent]);
109
+
95
110
  const handleClick = (selectedDefault: string) => {
96
111
  getContents(selectedDefault);
97
112
  };
98
113
 
99
114
  const setContent = (item: any) => {
100
- const { component, id } = item;
101
- const isHeader = component === "Header";
115
+ const { type, id } = item;
116
+ const isHeader = type === "header";
102
117
  isHeader ? setHeader(id) : setFooter(id);
103
118
  };
104
119
 
@@ -166,6 +181,8 @@ const DefaultsList = (props: IProps): JSX.Element => {
166
181
  />
167
182
  );
168
183
 
184
+ const goToSiteSettings = () => setHistoryPush("/sites/settings/globals");
185
+
169
186
  return (
170
187
  <MainWrapper
171
188
  title="Navigation modules"
@@ -178,6 +195,13 @@ const DefaultsList = (props: IProps): JSX.Element => {
178
195
  <DefaultNav current={selectedDefault} defaultTypes={defaultTypes} onClick={handleClick} />
179
196
  <S.TableWrapper>
180
197
  <ErrorToast />
198
+ {isNavigationNotificationOpen && (
199
+ <Notification
200
+ type="warning"
201
+ text="The design of the navigation modules has changed. Please, check the previously created and save them."
202
+ />
203
+ )}
204
+ {showNavigationModulesWarning && <NavigationModulesWarning goTo={goToSiteSettings} />}
181
205
  <TableList
182
206
  tableHeader={TableHeader}
183
207
  pagination={pagination}
@@ -217,6 +241,7 @@ const mapStateToProps = (state: IRootState) => ({
217
241
  selectedDefault: state.navigation.selectedDefault,
218
242
  currentDefaultsContent: state.navigation.currentDefaultsContent,
219
243
  totalItems: state.navigation.totalItems,
244
+ currentSiteInfo: state.sites.currentSiteInfo,
220
245
  });
221
246
 
222
247
  interface IDispatchProps {
@@ -239,6 +264,7 @@ interface IDefaultsProps {
239
264
  selectedDefault: string;
240
265
  currentDefaultsContent: (IHeader | IFooter)[];
241
266
  totalItems: number;
267
+ currentSiteInfo: any;
242
268
  }
243
269
 
244
270
  type IProps = IDefaultsProps & IDispatchProps;
@@ -20,4 +20,35 @@ const PaginationWrapper = styled.span`
20
20
  margin-top: ${(p) => p.theme.spacing.m};
21
21
  `;
22
22
 
23
- export { DefaultListWrapper, PaginationWrapper, TableWrapper }
23
+ const NavigationModulesWarning = styled.div`
24
+ padding: ${(p) => p.theme.spacing.m};
25
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
26
+ `;
27
+
28
+ const NavigationModulesTitle = styled.div`
29
+ ${(p) => p.theme.textStyle.headingXS};
30
+ margin-bottom: ${(p) => p.theme.spacing.xs};
31
+ `;
32
+
33
+ const NavigationModulesDescription = styled.div`
34
+ ${(p) => p.theme.textStyle.uiM};
35
+ `;
36
+
37
+ const SiteSettingsLink = styled.div`
38
+ display: inline;
39
+ ${(p) => p.theme.textStyle.uiS};
40
+ color: ${(p) => p.theme.color.interactive01};
41
+ span {
42
+ cursor: pointer;
43
+ }
44
+ `;
45
+
46
+ export {
47
+ DefaultListWrapper,
48
+ PaginationWrapper,
49
+ TableWrapper,
50
+ NavigationModulesWarning,
51
+ NavigationModulesTitle,
52
+ NavigationModulesDescription,
53
+ SiteSettingsLink,
54
+ };
@@ -5,7 +5,7 @@ import { pageEditorActions } from "@ax/containers/PageEditor";
5
5
  import { sitesActions } from "@ax/containers/Sites";
6
6
  import { appActions } from "@ax/containers/App";
7
7
  import { ConfigPanel, ResizePanel } from "@ax/components";
8
- import { IBreadcrumbItem, IRootState, ISchema, ISite, IUserEditing } from "@ax/types";
8
+ import { IBreadcrumbItem, INotification, IRootState, ISchema, ISite, IUserEditing } from "@ax/types";
9
9
  import PageBrowser from "../PageBrowser";
10
10
 
11
11
  const Editor = (props: IProps) => {
@@ -35,6 +35,10 @@ const Editor = (props: IProps) => {
35
35
  isReadOnly,
36
36
  userEditing,
37
37
  site,
38
+ lastElementAddedId,
39
+ copyModule,
40
+ pasteModule,
41
+ setNotification,
38
42
  } = props;
39
43
 
40
44
  const actions = {
@@ -47,6 +51,9 @@ const Editor = (props: IProps) => {
47
51
  replaceElementsInCollectionAction: replaceElementsInCollection,
48
52
  getGlobalFromLocalPageAction: getGlobalFromLocalPage,
49
53
  saveCurrentSiteInfoAction: saveCurrentSiteInfo,
54
+ copyModuleAction: copyModule,
55
+ pasteModuleAction: pasteModule,
56
+ setNotificationAction: setNotification,
50
57
  };
51
58
 
52
59
  return (
@@ -71,6 +78,7 @@ const Editor = (props: IProps) => {
71
78
  isReadOnly={isReadOnly}
72
79
  userEditing={userEditing}
73
80
  theme={site.theme}
81
+ lastElementAddedId={lastElementAddedId}
74
82
  />
75
83
  }
76
84
  />
@@ -87,6 +95,7 @@ interface IEditorStateProps {
87
95
  isLoading: boolean;
88
96
  userEditing: IUserEditing | null;
89
97
  site: ISite;
98
+ lastElementAddedId: null | number;
90
99
  }
91
100
 
92
101
  interface IPageBrowserDispatchProps {
@@ -102,6 +111,9 @@ interface IPageBrowserDispatchProps {
102
111
  setHistoryPush(path: string, isEditor: boolean): void;
103
112
  getGlobalFromLocalPage(): void;
104
113
  saveCurrentSiteInfo(): void;
114
+ copyModule(editorID: number): boolean;
115
+ pasteModule(editorID: number): Promise<{ error?: INotification }>;
116
+ setNotification: (notification: INotification) => void;
105
117
  isTemplateActivated: boolean;
106
118
  isGlobal: boolean;
107
119
  isEditable: boolean;
@@ -120,6 +132,7 @@ const mapStateToProps = (state: IRootState): IEditorStateProps => ({
120
132
  isLoading: state.app.isLoading,
121
133
  userEditing: state.pageEditor.userEditing,
122
134
  site: state.sites.currentSiteInfo,
135
+ lastElementAddedId: state.pageEditor.lastElementAddedId,
123
136
  });
124
137
 
125
138
  const mapDispatchToProps = {
@@ -135,6 +148,8 @@ const mapDispatchToProps = {
135
148
  setHistoryPush: appActions.setHistoryPush,
136
149
  getGlobalFromLocalPage: pageEditorActions.getGlobalFromLocalPage,
137
150
  saveCurrentSiteInfo: sitesActions.saveCurrentSiteInfo,
151
+ copyModule: pageEditorActions.copyModule,
152
+ pasteModule: pageEditorActions.pasteModule,
138
153
  };
139
154
 
140
155
  export default connect(mapStateToProps, mapDispatchToProps)(Editor);
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
  import { RouteComponentProps } from "react-router-dom";
4
4
 
5
- import { IErrorItem, IPageLanguage, IRootState, ISavePageParams, IUserEditing } from "@ax/types";
5
+ import { IErrorItem, INotification, IRootState, ISavePageParams, IUserEditing, IPageLanguage } from "@ax/types";
6
6
  import { MainWrapper, Loading, ErrorToast, Notification, Modal } from "@ax/components";
7
7
  import { pageEditorActions } from "@ax/containers/PageEditor";
8
8
  import { appActions } from "@ax/containers/App";
@@ -49,6 +49,7 @@ const PageEditor = (props: IProps) => {
49
49
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
50
50
  const [isReadOnly, setIsReadOnly] = useState(false);
51
51
  const [selectedTab, setSelectedTab] = useState("edit");
52
+ const [notification, setNotification] = useState<INotification | null>(null);
52
53
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent.editorContent, isNewTranslation);
53
54
  const { isOpen, toggleModal } = useModal();
54
55
  const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
@@ -485,6 +486,17 @@ const PageEditor = (props: IProps) => {
485
486
  <Notification type="warning" text={modifiedNotificationText} />
486
487
  </S.NotificationWrapper>
487
488
  )}
489
+ {notification && (
490
+ <S.NotificationWrapper>
491
+ <Notification
492
+ type={notification.type}
493
+ text={notification.text}
494
+ btnText={notification.btnText}
495
+ onClick={notification.onClick}
496
+ resetError={() => setNotification(null)}
497
+ />
498
+ </S.NotificationWrapper>
499
+ )}
488
500
  <ErrorToast size="l" />
489
501
  {selectedTab === "edit" ? (
490
502
  <S.Content>
@@ -494,6 +506,7 @@ const PageEditor = (props: IProps) => {
494
506
  isEditable={isEditable}
495
507
  pageTitle={pageName}
496
508
  isReadOnly={isReadOnly}
509
+ setNotification={setNotification}
497
510
  />
498
511
  </S.Content>
499
512
  ) : (
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
2
2
  import { useParams } from "react-router-dom";
3
3
 
4
4
  import * as components from "components";
5
- import { providers } from "components";
5
+ import { SiteProvider } from "components";
6
6
  import { Preview } from "@griddo/core";
7
7
  import { pages } from "@ax/api";
8
8
  import { getDefaultTheme, isReqOk } from "@ax/helpers";
@@ -43,7 +43,6 @@ const PublicPreview = () => {
43
43
 
44
44
  const API_URL = process.env.REACT_APP_API_ENDPOINT;
45
45
  const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_ENDPOINT;
46
- const { SiteProvider, AnimationProvider } = providers;
47
46
 
48
47
  const globalTheme = getDefaultTheme();
49
48
  const theme = state && state.site ? state.siteInfo.theme : globalTheme;
@@ -64,22 +63,20 @@ const PublicPreview = () => {
64
63
  publicApiUrl={PUBLIC_API_URL}
65
64
  siteId={state && state.site}
66
65
  >
67
- <AnimationProvider showOnScroll={{ active: false }}>
68
- <S.Wrapper ref={(ref: any) => ((window as any).browserRef = ref)}>
69
- {state && (
70
- <Preview
71
- isPage={true}
72
- apiUrl={API_URL}
73
- library={components}
74
- content={state}
75
- header={state && state.headerContent}
76
- footer={state && state.footerContent}
77
- languageId={state && state.language}
78
- pageLanguages={state && state.pageLanguages}
79
- />
80
- )}
81
- </S.Wrapper>
82
- </AnimationProvider>
66
+ <S.Wrapper ref={(ref: any) => ((window as any).browserRef = ref)}>
67
+ {state && (
68
+ <Preview
69
+ isPage={true}
70
+ apiUrl={API_URL}
71
+ library={components}
72
+ content={state}
73
+ header={state && state.headerContent}
74
+ footer={state && state.footerContent}
75
+ languageId={state && state.language}
76
+ pageLanguages={state && state.pageLanguages}
77
+ />
78
+ )}
79
+ </S.Wrapper>
83
80
  </SiteProvider>
84
81
  );
85
82
  };
@@ -0,0 +1,35 @@
1
+ import React, { memo } from "react";
2
+
3
+ import { getThumbnailProps, filterImageText } from "@ax/helpers";
4
+
5
+ import * as S from "./style";
6
+
7
+ const getThumbnailData = (option: any, theme: string) => {
8
+ option = filterImageText(option.component);
9
+ return getThumbnailProps(option, false, theme);
10
+ };
11
+
12
+ const SideModalOption = (props: IProps) => {
13
+ const { option, handleClick, theme, selected } = props;
14
+
15
+ const thumbnailProps = getThumbnailData(option, theme);
16
+
17
+ const setOption = () => {
18
+ handleClick(option);
19
+ };
20
+
21
+ return (
22
+ <S.Item onClick={setOption} selected={selected}>
23
+ <S.Thumbnail {...thumbnailProps} />
24
+ </S.Item>
25
+ );
26
+ };
27
+
28
+ interface IProps {
29
+ option: any;
30
+ handleClick: any;
31
+ theme: string;
32
+ selected: boolean;
33
+ }
34
+
35
+ export default memo(SideModalOption);
@@ -0,0 +1,22 @@
1
+ import styled from "styled-components";
2
+
3
+ export const Item = styled.li<{ selected: boolean }>`
4
+ cursor: pointer;
5
+ padding: ${(p) => p.theme.spacing.xs};
6
+ margin-bottom: ${(p) => p.theme.spacing.s};
7
+ box-shadow: ${(p) => p.theme.shadow.shadowS};
8
+ border-radius: ${(p) => p.theme.radii.s};
9
+ ${(p) => p.theme.textStyle.uiS};
10
+ background-color: ${(p) => p.theme.color.interactiveBackground};
11
+ border: 2px solid ${(p) => (p.selected ? p.theme.color.interactive01 : "transparent")};
12
+ &:hover {
13
+ background: ${(p) => p.theme.color.overlayHoverPrimary};
14
+ }
15
+ &:focus {
16
+ background-color: ${(p) => p.theme.color.overlayFocusPrimary};
17
+ }
18
+ `;
19
+
20
+ export const Thumbnail = styled.img`
21
+ cursor: pointer;
22
+ `;
@@ -0,0 +1,111 @@
1
+ import React, { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ import { useHandleClickOutside } from "@ax/hooks";
5
+ import { Button, IconAction, NoteField } from "@ax/components";
6
+
7
+ import SideModalOption from "./SideModalOption";
8
+ import * as S from "./style";
9
+
10
+ const SideModal = (props: ISideModalProps): JSX.Element | null => {
11
+ const {
12
+ navigationModules,
13
+ form,
14
+ isOpen,
15
+ toggleModal,
16
+ theme,
17
+ setNavigationModulesValue,
18
+ isNavigationModulesChanged,
19
+ setIsNavigationModulesChanged,
20
+ } = props;
21
+
22
+ const [previousNavigationModules, setPreviousNavigationModules] = useState(form.navigationModules);
23
+
24
+ useEffect(() => {
25
+ isOpen && setPreviousNavigationModules(form.navigationModules);
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ }, [isOpen]);
28
+
29
+ const closeDiscardingChanges = () => {
30
+ setNavigationModulesValue(previousNavigationModules);
31
+ toggleModal();
32
+ };
33
+
34
+ const save = () => {
35
+ setIsNavigationModulesChanged(true);
36
+ toggleModal();
37
+ };
38
+
39
+ const wrapperRef = useRef<HTMLDivElement>(null);
40
+ const handleClickOutside = (e: any) => {
41
+ if (wrapperRef.current?.contains(e.target)) return;
42
+ closeDiscardingChanges();
43
+ };
44
+
45
+ useHandleClickOutside(isOpen, handleClickOutside);
46
+
47
+ const getNavigationOptions = (type: string): JSX.Element => {
48
+ return (
49
+ <S.NavigationOptionsWrapper>
50
+ <S.NavigationOptionsTitle>{type}</S.NavigationOptionsTitle>
51
+ {navigationModules[type].map((option: any, index: number) => {
52
+ const isSelected = form?.navigationModules && form?.navigationModules[type] === option.component;
53
+ const isDefault = !(form?.navigationModules && form?.navigationModules[type]) && option.defaultNavigation;
54
+ const handleClick = (option: any) =>
55
+ setNavigationModulesValue({ ...form.navigationModules, [type]: option.component });
56
+ return (
57
+ <SideModalOption
58
+ option={option}
59
+ selected={isSelected || isDefault}
60
+ handleClick={handleClick}
61
+ theme={theme}
62
+ key={`${option}${index}`}
63
+ />
64
+ );
65
+ })}
66
+ </S.NavigationOptionsWrapper>
67
+ );
68
+ };
69
+
70
+ const noteFieldText = isNavigationModulesChanged
71
+ ? "If you change the design, this will be changed in all headers and footers of all pages of the site."
72
+ : "Select the design you want for this site and create as many headers and footers as you need.";
73
+
74
+ const buttonText = isNavigationModulesChanged ? "Change design" : "Select design";
75
+
76
+ return isOpen
77
+ ? createPortal(
78
+ <S.Wrapper ref={wrapperRef}>
79
+ <S.Header>
80
+ <S.Title>Navigation modules design</S.Title>
81
+ <S.ButtonWrapper>
82
+ <IconAction icon="close" onClick={closeDiscardingChanges} />
83
+ </S.ButtonWrapper>
84
+ </S.Header>
85
+ <S.Content>
86
+ <NoteField value={{ text: noteFieldText }} />
87
+ {["header", "footer"].map((type: string) => getNavigationOptions(type))}
88
+ </S.Content>
89
+ <S.Footer>
90
+ <Button type="button" onClick={save} buttonStyle="solid">
91
+ {buttonText}
92
+ </Button>
93
+ </S.Footer>
94
+ </S.Wrapper>,
95
+ document.body
96
+ )
97
+ : null;
98
+ };
99
+
100
+ interface ISideModalProps {
101
+ isOpen: boolean;
102
+ navigationModules: Record<string, any[]>;
103
+ toggleModal: () => void;
104
+ theme: string;
105
+ form: any;
106
+ setNavigationModulesValue: (value: any) => void;
107
+ isNavigationModulesChanged: boolean;
108
+ setIsNavigationModulesChanged: Dispatch<SetStateAction<boolean>>;
109
+ }
110
+
111
+ export default SideModal;
@@ -0,0 +1,64 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ position: fixed;
5
+ width: calc(6 * ${(p) => p.theme.spacing.xl});
6
+ right: 0;
7
+ top: 0;
8
+ z-index: 1000;
9
+ height: 100vh;
10
+ background: ${(p) => p.theme.colors.uiBackground01};
11
+ box-shadow: ${(p) => p.theme.shadow.rightPanel};
12
+ `;
13
+
14
+ const Header = styled.div`
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ height: ${(p) => p.theme.spacing.xl};
19
+ width: 100%;
20
+ padding: 0 ${(p) => p.theme.spacing.m};
21
+ background-color: ${(p) => p.theme.colors.uiBackground02};
22
+ border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
23
+ h6 {
24
+ ${(p) => p.theme.textStyle.headingM}
25
+ color: ${(p) => p.theme.colors.textHighEmphasis};
26
+ text-transform: capitalize;
27
+ }
28
+ `;
29
+
30
+ const Title = styled.h6``;
31
+
32
+ const Content = styled.div`
33
+ list-style: none;
34
+ padding: ${(p) => p.theme.spacing.m};
35
+ height: ${(p) => `calc(100vh - (${p.theme.spacing.xl} * 2))`};
36
+ width: ${(p) => `calc(${p.theme.spacing.xl} * 6)`};
37
+ overflow: auto;
38
+ `;
39
+
40
+ const ButtonWrapper = styled.div`
41
+ margin: 0 0 0 auto;
42
+ `;
43
+
44
+ const NavigationOptionsWrapper = styled.div`
45
+ margin: ${(p) => p.theme.spacing.m} 0 ${(p) => p.theme.spacing.l};
46
+ `;
47
+
48
+ const NavigationOptionsTitle = styled.div`
49
+ ${(p) => p.theme.textStyle.headingXSS}
50
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
51
+ text-transform: uppercase;
52
+ padding-bottom: ${(p) => p.theme.spacing.xs};
53
+ margin-bottom: ${(p) => p.theme.spacing.s};
54
+ border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
55
+ `;
56
+
57
+ const Footer = styled.div`
58
+ padding: ${(p) => p.theme.spacing.s} ${(p) => p.theme.spacing.m} 0;
59
+ display: flex;
60
+ justify-content: flex-end;
61
+ border-top: 1px solid ${(p) => p.theme.color.uiLine};
62
+ `;
63
+
64
+ export { Wrapper, Content, Header, Title, ButtonWrapper, NavigationOptionsWrapper, NavigationOptionsTitle, Footer };