@griddo/ax 10.5.4 → 10.6.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 (52) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/MixableComponentArray.test.tsx +3 -3
  3. package/src/components/Button/style.tsx +1 -1
  4. package/src/components/ConfigPanel/Form/ConnectedField/NavConnectedField/index.tsx +2 -2
  5. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +2 -2
  6. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +3 -3
  7. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +4 -3
  8. package/src/components/Fields/ImageField/index.tsx +19 -2
  9. package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +8 -5
  10. package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/index.tsx +15 -5
  11. package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/style.tsx +1 -0
  12. package/src/components/Gallery/GalleryPanel/index.tsx +2 -11
  13. package/src/components/MenuGroup/index.tsx +81 -0
  14. package/src/components/MenuGroup/style.tsx +46 -0
  15. package/src/components/MenuItem/index.tsx +14 -7
  16. package/src/components/MenuItem/style.tsx +48 -26
  17. package/src/components/Nav/index.tsx +2 -2
  18. package/src/components/SideModal/index.tsx +2 -1
  19. package/src/components/Tooltip/index.tsx +1 -1
  20. package/src/components/index.tsx +2 -0
  21. package/src/containers/Gallery/actions.tsx +10 -2
  22. package/src/containers/Sites/actions.tsx +27 -0
  23. package/src/containers/Sites/constants.tsx +1 -0
  24. package/src/containers/Sites/interfaces.tsx +6 -0
  25. package/src/containers/Sites/reducer.tsx +4 -0
  26. package/src/helpers/index.tsx +2 -2
  27. package/src/helpers/themes.tsx +18 -13
  28. package/src/modules/Categories/CategoriesList/CategoryNav/NavItem/index.tsx +2 -2
  29. package/src/modules/Content/BulkHeader/TableHeader/index.tsx +1 -5
  30. package/src/modules/Content/BulkHeader/TableHeader/style.tsx +6 -0
  31. package/src/modules/Content/ContentFilters/index.tsx +66 -41
  32. package/src/modules/Content/ContentFilters/style.tsx +4 -38
  33. package/src/modules/Content/ContentFilters/utils.tsx +47 -10
  34. package/src/modules/Content/OptionTable/index.tsx +13 -14
  35. package/src/modules/Content/index.tsx +26 -7
  36. package/src/modules/Content/utils.tsx +5 -5
  37. package/src/modules/Navigation/Defaults/Nav/index.tsx +4 -6
  38. package/src/modules/Navigation/Menus/List/Nav/index.tsx +2 -2
  39. package/src/modules/Settings/ContentTypes/DataPacks/Nav/index.tsx +6 -10
  40. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +1 -6
  41. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/style.tsx +6 -0
  42. package/src/modules/StructuredData/StructuredDataList/ContentFilters/index.tsx +33 -41
  43. package/src/modules/StructuredData/StructuredDataList/ContentFilters/style.tsx +4 -38
  44. package/src/modules/StructuredData/StructuredDataList/ContentFilters/utils.tsx +44 -11
  45. package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +5 -5
  46. package/src/modules/StructuredData/StructuredDataList/index.tsx +1 -2
  47. package/src/modules/Users/UserForm/index.tsx +13 -27
  48. package/src/routes/multisite.tsx +1 -1
  49. package/src/routes/site.tsx +1 -1
  50. package/src/types/index.tsx +15 -0
  51. package/src/modules/Content/ContentFilters/constants.tsx +0 -15
  52. package/src/modules/StructuredData/StructuredDataList/ContentFilters/constants.tsx +0 -21
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "10.5.4",
4
+ "version": "10.6.1",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -232,5 +232,5 @@
232
232
  "publishConfig": {
233
233
  "access": "public"
234
234
  },
235
- "gitHead": "aee8c57f859479efdec9e7e1d1acfdcd4caa34b8"
235
+ "gitHead": "7c09ff5f84d88f828072c1e328345173f2cdf992"
236
236
  }
@@ -37,7 +37,7 @@ const testModule = {
37
37
  // @ts-ignore
38
38
  mixableProps.value = testModule;
39
39
  mixableProps.activatedModules = ["HeroSection"];
40
- mixableProps.whiteList = [];
40
+ mixableProps.whiteList = ["Module1"];
41
41
  mixableProps.moduleCopy = null;
42
42
 
43
43
  describe("ComponentArraySelector component rendering", () => {
@@ -123,7 +123,7 @@ describe("ComponentArraySelector component rendering", () => {
123
123
  otherMixableModule.component = "HeroCard";
124
124
  otherMixableModule.title = "Hero Section";
125
125
  otherMixableModule.modules = [];
126
- mixableProps.whiteList = [];
126
+ mixableProps.whiteList = ["Module1"];
127
127
  mixableProps.value = [mixableModule, otherMixableModule];
128
128
  mixableProps.field = {
129
129
  contentType: "modules",
@@ -302,7 +302,7 @@ describe("ComponentArraySelector component events trigger", () => {
302
302
  mixableProps.field = { type: "components", title: "", key: "elements" };
303
303
  mixableProps.value = [mixableModule];
304
304
  mixableProps.activatedModules = ["HeroSection"];
305
- mixableProps.whiteList = [];
305
+ mixableProps.whiteList = ["Module1"];
306
306
  mixableProps.moduleCopy = null;
307
307
  mixableProps.actions = {
308
308
  addModuleAction: jest.fn(),
@@ -148,4 +148,4 @@ const Label = styled.span<{ icon?: string; backIcon?: string }>`
148
148
  padding-right: ${(p) => (p.backIcon ? p.theme.spacing.s : `0`)};
149
149
  `;
150
150
 
151
- export { Button, TextButton, LineButton, MinimalButton, Label }
151
+ export { Button, TextButton, LineButton, MinimalButton, Label };
@@ -5,7 +5,7 @@ import { FieldContainer } from "@ax/components";
5
5
  import { navigationActions } from "@ax/containers/Navigation";
6
6
  import { getInnerFields } from "@ax/forms";
7
7
  import { IRootState } from "@ax/types";
8
- import { areEqual, filterThemeElements } from "@ax/helpers";
8
+ import { areEqual, filterThemeModules } from "@ax/helpers";
9
9
 
10
10
  const NavConnectedField = (props: any) => {
11
11
  const {
@@ -69,7 +69,7 @@ const NavConnectedField = (props: any) => {
69
69
  );
70
70
  }
71
71
 
72
- const filteredWhiteList = whiteList ? filterThemeElements(themeElements, whiteList, "modules") : whiteList;
72
+ const filteredWhiteList = whiteList ? filterThemeModules(themeElements, whiteList) : whiteList;
73
73
 
74
74
  return (
75
75
  <FieldContainer
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { IDataPack, IErrorItem, IRootState, ISchemaField, ISite, IThemeElements } from "@ax/types";
5
- import { filterThemeElements, getModuleCategories } from "@ax/helpers";
5
+ import { filterThemeModules, getModuleCategories } from "@ax/helpers";
6
6
  import Field from "../Field";
7
7
 
8
8
  import * as S from "./style";
@@ -61,7 +61,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
61
61
  }, []);
62
62
 
63
63
  const mappedWhiteList: string[] = whiteList ? [...whiteList, ...addedModules].sort() : [...addedModules.sort()];
64
- const filteredWhiteList = filterThemeElements(themeElements, mappedWhiteList, "modules");
64
+ const filteredWhiteList = filterThemeModules(themeElements, mappedWhiteList);
65
65
  const categories = getModuleCategories(filteredWhiteList);
66
66
 
67
67
  return {
@@ -6,7 +6,7 @@ import {
6
6
  isModuleDisabled,
7
7
  slugify,
8
8
  areEqual,
9
- filterThemeElements,
9
+ filterThemeModules,
10
10
  isTemplateExcludedFromTheme,
11
11
  } from "@ax/helpers";
12
12
  import { IRootState } from "@ax/types";
@@ -82,8 +82,8 @@ const PageConnectedField = (props: any) => {
82
82
 
83
83
  const isFieldReadOnly = (["parent", "slug"].includes(objKey) && isPageHome) || parentIsReadOnly || field.readonly;
84
84
 
85
- const filteredActivatedModules = filterThemeElements(themeElements, activatedModules, "modules");
86
- const filteredWhiteList = whiteList ? filterThemeElements(themeElements, whiteList, "modules") : whiteList;
85
+ const filteredActivatedModules = filterThemeModules(themeElements, activatedModules);
86
+ const filteredWhiteList = whiteList ? filterThemeModules(themeElements, whiteList) : whiteList;
87
87
 
88
88
  const isDisabled =
89
89
  (!isGlobal &&
@@ -50,6 +50,9 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
50
50
  const fixedValue = Array.isArray(value) ? value : containerToComponentArray(value);
51
51
  const componentIDs: number[] = fixedValue.map((element: any) => element.editorID);
52
52
 
53
+ const type = getTypefromKey(objKey);
54
+ const { contentType = type } = field;
55
+
53
56
  const { modulesToPaste, unavailableModules } = getModulesToPaste(
54
57
  moduleCopy,
55
58
  whiteList,
@@ -58,8 +61,6 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
58
61
  field
59
62
  );
60
63
 
61
- const type = getTypefromKey(objKey);
62
- const { contentType = type } = field;
63
64
  const { isOpen, toggleModal } = useModal();
64
65
  const [isBulkOpen, setIsBulkOpen] = useState(false);
65
66
  const [draggingId, setDraggingId] = useState<number | null>(null);
@@ -102,7 +103,7 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
102
103
 
103
104
  const selectItems = () => (checkState.isAllSelected ? resetBulkSelection() : selectAllItems());
104
105
 
105
- const showAddItemButton = (!maxItems || fixedValue.length < maxItems) && !disabled;
106
+ const showAddItemButton = (!maxItems || fixedValue.length < maxItems) && !disabled && whiteList.length > 0;
106
107
 
107
108
  const showPasteModuleButton =
108
109
  showAddItemButton &&
@@ -4,6 +4,7 @@ import { Icon, IconAction, Gallery, Modal, Image, FieldsBehavior } from "@ax/com
4
4
  import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
5
5
  import { IImage, ISite } from "@ax/types";
6
6
  import { useModal } from "@ax/hooks";
7
+ import GalleryDragAndDrop from "@ax/components/Gallery/GalleryPanel/GalleryDragAndDrop";
7
8
 
8
9
  import * as S from "./style";
9
10
 
@@ -20,17 +21,21 @@ const ImageField = (props: IImageFieldProps) => {
20
21
  setIsGalleryOpened,
21
22
  cropPreview = false,
22
23
  fullWidth = false,
24
+ noGallery = false,
23
25
  } = props;
24
26
 
25
27
  const isLinkableImage = selectedContent && selectedContent.component === "LinkableImage";
26
28
  const hasImage = value && Object.prototype.hasOwnProperty.call(value, "url");
27
29
  const isModalOpen = isLinkableImage && !hasImage;
28
30
  const { isOpen, toggleModal } = useModal(isModalOpen);
31
+ const { isOpen: isOpenDD, toggleModal: toggleModalDD } = useModal(isModalOpen);
29
32
  const [previewSrc, setPreviewSrc] = useState<string>();
30
33
  const [previewHeight, setPreviewHeight] = useState<{ height: string | number }>({ height: "auto" });
31
34
  const previewRef = useRef<HTMLDivElement>(null);
32
35
  const [imageLoaded, setImageLoaded] = useState(false);
33
36
 
37
+ const validFormats = ["jpeg", "jpg", "png", "svg", "gif"];
38
+
34
39
  const imageUrl = value ? (typeof value === "string" ? value : value.url) : "";
35
40
  const imagePosition = value && typeof value === "object" ? value.position : "center";
36
41
 
@@ -73,20 +78,21 @@ const ImageField = (props: IImageFieldProps) => {
73
78
  onChange(img);
74
79
  setIsGalleryOpened && setIsGalleryOpened();
75
80
  error && handleValidation && handleValidation(url, validators);
81
+ isOpenDD && toggleModalDD();
76
82
  }
77
83
  };
78
84
 
79
85
  const handleClick = () => {
80
86
  if (!disabled) {
81
87
  setIsGalleryOpened && setIsGalleryOpened();
82
- toggleModal();
88
+ noGallery ? toggleModalDD() : toggleModal();
83
89
  }
84
90
  };
85
91
 
86
92
  const handleChange = () => {
87
93
  if (!disabled) {
88
94
  setIsGalleryOpened && setIsGalleryOpened();
89
- toggleModal();
95
+ noGallery ? toggleModalDD() : toggleModal();
90
96
  }
91
97
  };
92
98
 
@@ -172,6 +178,16 @@ const ImageField = (props: IImageFieldProps) => {
172
178
  <Modal isOpen={isOpen} hide={toggleModal} size="XL" title="Select image">
173
179
  <Gallery getImageSelected={getImageSelected} toggleModal={toggleModal} site={site} />
174
180
  </Modal>
181
+ <Modal isOpen={isOpenDD} hide={toggleModalDD} size="M" title="Upload Media">
182
+ <GalleryDragAndDrop
183
+ site={site ? site.id : "global"}
184
+ isImageSelected={false}
185
+ allowUpload={true}
186
+ selectImage={getImageSelected}
187
+ validFormats={validFormats}
188
+ visible={false}
189
+ />
190
+ </Modal>
175
191
  </>
176
192
  );
177
193
  };
@@ -188,6 +204,7 @@ export interface IImageFieldProps {
188
204
  site: ISite;
189
205
  cropPreview?: boolean;
190
206
  fullWidth?: boolean;
207
+ noGallery?: boolean;
191
208
  }
192
209
 
193
210
  export default memo(ImageField);
@@ -35,13 +35,16 @@ const GalleryDetailPanel = (props: IProps) => {
35
35
  const [addToGlobal, setAddToGlobal] = useState({ value: "addToGlobal", isChecked: false });
36
36
  const [deletedToast, setDeletedToast] = useState(false);
37
37
 
38
+ const isAllowedtoDeleteGlobalImagesInSite = usePermission("mediaGallery.deleteGlobalImagesInSite");
39
+ const isAllowedtoDeleteSiteImages = usePermission("mediaGallery.deleteImages");
40
+ const isAllowedtoEditGlobalImagesInSite = usePermission("mediaGallery.editGlobalImagesInSite");
41
+ const isAllowedtoEditSiteImages = usePermission("mediaGallery.editImages");
42
+ const isAllowedToUploadGlobal = usePermission("mediaGallery.addGlobalImagesFromSite");
43
+
38
44
  const isAllowedToDelete =
39
- (isGlobalTab && usePermission("mediaGallery.deleteGlobalImagesInSite")) ||
40
- (!isGlobalTab && usePermission("mediaGallery.deleteImages"));
45
+ (isGlobalTab && isAllowedtoDeleteGlobalImagesInSite) || (!isGlobalTab && isAllowedtoDeleteSiteImages);
41
46
  const isAllowedToEdit =
42
- (isGlobalTab && usePermission("mediaGallery.editGlobalImagesInSite")) ||
43
- (!isGlobalTab && usePermission("mediaGallery.editImages"));
44
- const isAllowedToUploadGlobal = usePermission("mediaGallery.addGlobalImagesFromSite");
47
+ (isGlobalTab && isAllowedtoEditGlobalImagesInSite) || (!isGlobalTab && isAllowedtoEditSiteImages);
45
48
 
46
49
  const setInitForm = (imageSelected: IImage) => {
47
50
  const form = {
@@ -1,4 +1,4 @@
1
- import React, { memo, useRef, useState } from "react";
1
+ import React, { memo, useEffect, useRef, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { galleryActions } from "@ax/containers/Gallery";
@@ -21,6 +21,8 @@ const GalleryDragAndDrop = (props: IProps) => {
21
21
  errorMsg,
22
22
  uploadImage,
23
23
  selectImage,
24
+ visible,
25
+ resetGalleryState,
24
26
  } = props;
25
27
  const validExtensions = validFormats.map((format) => `.${format}`).join(",");
26
28
 
@@ -31,6 +33,10 @@ const GalleryDragAndDrop = (props: IProps) => {
31
33
  const [uploadingState, setUploadingState] = useState({ total: 0, ready: 0 });
32
34
  const [progress, setProgress] = useState(0);
33
35
 
36
+ useEffect(() => {
37
+ resetGalleryState();
38
+ }, [resetGalleryState]);
39
+
34
40
  const handleDragEnter = () => {
35
41
  setDropDepth((depth) => depth + 1);
36
42
  };
@@ -75,14 +81,14 @@ const GalleryDragAndDrop = (props: IProps) => {
75
81
  let lastImage: IImage | undefined;
76
82
  while (files.length) {
77
83
  const file = files.shift();
78
- lastImage = file && (await uploadImage(file, site, setProgress));
84
+ lastImage = file && (await uploadImage(file, site, setProgress, visible));
79
85
  setUploadingState((state) => ({ total: state.total, ready: state.ready + 1 }));
80
86
  }
81
87
 
82
88
  setInDropZone(false);
83
89
  setUploadingState({ total: 0, ready: 0 });
84
90
  setTimeout(async () => {
85
- await refreshImages();
91
+ refreshImages && (await refreshImages());
86
92
  lastImage && selectImage(lastImage);
87
93
  }, 3000);
88
94
  } catch (error) {
@@ -191,11 +197,12 @@ export interface IGalleryDragAndDropProps {
191
197
  validFormats: string[];
192
198
  site: number | "global";
193
199
  allowUpload: boolean;
194
- refreshImages: () => Promise<void>;
200
+ refreshImages?: () => Promise<void>;
195
201
  isUploading: boolean;
196
202
  isSuccess: boolean;
197
203
  isError: boolean;
198
204
  errorMsg: string;
205
+ visible?: boolean;
199
206
  }
200
207
 
201
208
  const mapStateToProps = (state: IRootState) => ({
@@ -208,16 +215,19 @@ const mapStateToProps = (state: IRootState) => ({
208
215
  export interface IDispatchProps {
209
216
  selectImage(item: IImage | null): void;
210
217
  uploadError: (error: boolean, msg?: string) => Promise<void>;
218
+ resetGalleryState: () => Promise<void>;
211
219
  uploadImage: (
212
220
  imageFiles: File | File[],
213
221
  site: number | string,
214
- setProgress?: (progress: number) => void
222
+ setProgress?: (progress: number) => void,
223
+ visible?: boolean
215
224
  ) => Promise<IImage>;
216
225
  }
217
226
 
218
227
  const mapDispatchToProps = {
219
228
  uploadError: galleryActions.uploadError,
220
229
  uploadImage: galleryActions.uploadImage,
230
+ resetGalleryState: galleryActions.resetGalleryState,
221
231
  };
222
232
 
223
233
  type IProps = IGalleryDragAndDropProps & IDispatchProps;
@@ -10,6 +10,7 @@ export const Wrapper = styled.div<{ hidden: boolean }>`
10
10
  height: 100%;
11
11
  width: 100%;
12
12
  padding: ${(p) => p.theme.spacing.m};
13
+ background-color: ${(p) => p.theme.color.uiBarBackground};
13
14
  `;
14
15
 
15
16
  export const StatusWrapper = styled.div`
@@ -8,17 +8,8 @@ import DetailPanel from "./DetailPanel";
8
8
  import * as S from "./style";
9
9
 
10
10
  const GalleryPanel = (props: IGalleryPanelProps) => {
11
- const {
12
- imageSelected,
13
- validFormats,
14
- setImage,
15
- isGlobalTab,
16
- scope,
17
- selectedTab,
18
- site,
19
- refreshImages,
20
- selectImage
21
- } = props;
11
+ const { imageSelected, validFormats, setImage, isGlobalTab, scope, selectedTab, site, refreshImages, selectImage } =
12
+ props;
22
13
 
23
14
  const isAllowedToUpload = usePermission("mediaGallery.addImages");
24
15
 
@@ -0,0 +1,81 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { NavLink } from "react-router-dom";
3
+
4
+ import { Icon, MenuItem, Tooltip } from "@ax/components";
5
+ import { IContentFilter } from "@ax/types";
6
+
7
+ import * as S from "./style";
8
+
9
+ const MenuGroup = (props: IProps): JSX.Element => {
10
+ const { filter, current, isAllowedToCreate, onClick, addNew } = props;
11
+
12
+ const [isOpen, setIsOpen] = useState(true);
13
+ const [isSelected, setIsSelected] = useState(false);
14
+
15
+ useEffect(() => {
16
+ const selected = filter.items && filter.items.some((filter) => filter.value === current);
17
+ if (selected) {
18
+ setIsSelected(true);
19
+ } else {
20
+ setIsSelected(false);
21
+ }
22
+ }, [current, filter]);
23
+
24
+ const toggleOpen = () => setIsOpen(!isOpen);
25
+
26
+ const handleClick = (value: string, fromPage = false, firstTemplate: string | null = null) =>
27
+ onClick(value, fromPage, firstTemplate);
28
+
29
+ const icon = isOpen ? "UpArrow" : "DownArrow";
30
+
31
+ const extendedAction = {
32
+ icon: "add",
33
+ action: addNew,
34
+ onlyOnHover: false,
35
+ };
36
+
37
+ return (
38
+ <S.Item>
39
+ <Tooltip content={filter.description}>
40
+ <S.NavLink onClick={toggleOpen} selected={isSelected && !isOpen}>
41
+ <S.Title>{filter.label}</S.Title>
42
+ <S.Arrow>
43
+ <Icon name={icon} />
44
+ </S.Arrow>
45
+ </S.NavLink>
46
+ </Tooltip>
47
+ <S.Dropdown isOpen={isOpen}>
48
+ {filter.items &&
49
+ filter.items.map((filter) => {
50
+ const { label, value, fromPage, firstTemplate, editable } = filter;
51
+
52
+ const isSelected = value === current;
53
+ const selectedClass = isSelected ? "selected" : "";
54
+
55
+ return (
56
+ <MenuItem
57
+ key={filter.value}
58
+ onClick={() => handleClick(value, fromPage, firstTemplate)}
59
+ extendedAction={editable && isAllowedToCreate ? extendedAction : null}
60
+ className={selectedClass}
61
+ >
62
+ <NavLink to="#">
63
+ <S.Link active={isSelected}>{label}</S.Link>
64
+ </NavLink>
65
+ </MenuItem>
66
+ );
67
+ })}
68
+ </S.Dropdown>
69
+ </S.Item>
70
+ );
71
+ };
72
+
73
+ interface IProps {
74
+ filter: IContentFilter;
75
+ current: string | undefined;
76
+ isAllowedToCreate: boolean;
77
+ onClick: (value: string, fromPage: boolean, firstTemplate: string | null) => void;
78
+ addNew: () => void;
79
+ }
80
+
81
+ export default MenuGroup;
@@ -0,0 +1,46 @@
1
+ import styled from "styled-components";
2
+
3
+ const Item = styled.li`
4
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
5
+ margin-top: ${(p) => p.theme.spacing.xxs};
6
+ `;
7
+
8
+ const NavLink = styled.div<{ selected: boolean }>`
9
+ display: flex;
10
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
11
+ align-items: center;
12
+ height: 35px;
13
+ cursor: pointer;
14
+ border-radius: ${(p) => p.theme.radii.s};
15
+ background-color: ${(p) => (p.selected ? p.theme.color.overlayPressedPrimary : "transparent")};
16
+ padding: ${(p) => `0 ${p.theme.spacing.xs}`};
17
+ color: ${(p) => (p.selected ? p.theme.color.textHighEmphasis : p.theme.color.textMediumEmphasis)};
18
+ `;
19
+
20
+ const Title = styled.div`
21
+ ${(p) => p.theme.textStyle.uiM};
22
+ `;
23
+
24
+ const Arrow = styled.div`
25
+ margin-left: auto;
26
+ height: ${(p) => p.theme.spacing.m};
27
+ width: ${(p) => p.theme.spacing.m};
28
+ `;
29
+
30
+ const Dropdown = styled.ul<{ isOpen: boolean }>`
31
+ display: ${(p) => (p.isOpen ? "flex" : "none")};
32
+ transition: all 0.5s ease-in-out;
33
+ opacity: ${(p) => (p.isOpen ? "1" : "0")};
34
+ flex-direction: column;
35
+ border-left: 1px solid ${(p) => p.theme.color.uiLine};
36
+ padding-left: ${(p) => p.theme.spacing.xxs};
37
+ margin-left: ${(p) => p.theme.spacing.s};
38
+ margin-bottom: ${(p) => p.theme.spacing.s};
39
+ `;
40
+
41
+ const Link = styled.div<{ active: boolean }>`
42
+ ${(p) => p.theme.textStyle.uiS};
43
+ color: ${(p) => (p.active ? p.theme.color.textHighEmphasis : p.theme.color.textMediumEmphasis)};
44
+ `;
45
+
46
+ export { Item, NavLink, Title, Arrow, Dropdown, Link };
@@ -4,7 +4,7 @@ import { IconAction } from "@ax/components";
4
4
  import * as S from "./style";
5
5
 
6
6
  const MenuItem = (props: IMenuItemProps): JSX.Element => {
7
- const { children, onClick, active = false, extendedAction } = props;
7
+ const { children, onClick, extendedAction, className } = props;
8
8
 
9
9
  const handleOnClick = (e: React.MouseEvent<HTMLLIElement>) => {
10
10
  if (onClick !== undefined) {
@@ -19,12 +19,19 @@ const MenuItem = (props: IMenuItemProps): JSX.Element => {
19
19
  };
20
20
 
21
21
  return (
22
- <S.SubItem onClick={handleOnClick} active={active} data-testid="menu-subitem">
23
- {children}
22
+ <S.SubItem
23
+ onClick={handleOnClick}
24
+ className={className}
25
+ onlyOnHover={(extendedAction && extendedAction.onlyOnHover) ?? true}
26
+ data-testid="menu-subitem"
27
+ >
28
+ <div>{children}</div>
24
29
  {extendedAction && (
25
- <S.ExtendedAction onlyOnHover={extendedAction.onlyOnHover ?? true} data-testid="menu-extended-action">
26
- <IconAction icon={extendedAction.icon} size="s" onClick={handleExtendedAction} />
27
- </S.ExtendedAction>
30
+ <S.ExtendedWrapper>
31
+ <S.ExtendedAction data-testid="menu-extended-action">
32
+ <IconAction icon={extendedAction.icon} size="s" onClick={handleExtendedAction} />
33
+ </S.ExtendedAction>
34
+ </S.ExtendedWrapper>
28
35
  )}
29
36
  </S.SubItem>
30
37
  );
@@ -32,9 +39,9 @@ const MenuItem = (props: IMenuItemProps): JSX.Element => {
32
39
 
33
40
  export interface IMenuItemProps {
34
41
  children: JSX.Element | string;
35
- active?: boolean;
36
42
  onClick?: (e: React.MouseEvent<HTMLLIElement>) => void;
37
43
  extendedAction?: { icon: string; action: () => void; onlyOnHover?: boolean } | null;
44
+ className?: string;
38
45
  }
39
46
 
40
47
  export default MenuItem;
@@ -1,49 +1,71 @@
1
1
  import styled from "styled-components";
2
2
 
3
- const ExtendedAction = styled.div<{ onlyOnHover: boolean }>`
4
- display: ${(p) => (p.onlyOnHover ? "none" : "block")};
5
- position: absolute;
6
- right: ${(p) => p.theme.spacing.xxs};
7
- top: 50%;
8
- transform: translateY(-50%);
3
+ const ExtendedWrapper = styled.div`
4
+ width: 24px;
5
+ height: 24px;
6
+ margin-left: auto;
9
7
  `;
10
8
 
11
- const SubItem = styled.li<{ active: boolean }>`
12
- background-color: ${(p) => (p.active ? p.theme.color.overlayPressedPrimary : `transparent`)};
13
- display: block;
9
+ const ExtendedAction = styled.div`
10
+ display: none;
11
+ `;
12
+
13
+ const SubItem = styled.li<{ onlyOnHover: boolean }>`
14
14
  ${(p) => p.theme.textStyle.uiM};
15
+ display: flex;
16
+ align-items: center;
15
17
  color: ${(p) => p.theme.color.textHighEmphasis};
16
- clear: both;
17
18
  width: 100%;
18
19
  margin-bottom: ${(p) => p.theme.spacing.xxs};
19
20
  margin-top: ${(p) => p.theme.spacing.xxs};
20
21
  position: relative;
22
+ padding: ${(p) => `${p.theme.spacing.xxs} ${p.theme.spacing.xs}`};
23
+ border-radius: ${(p) => p.theme.radii.s};
24
+ min-height: 32px;
25
+ :before {
26
+ content: "";
27
+ border-radius: ${(p) => p.theme.radii.s};
28
+ position: absolute;
29
+ top: 0;
30
+ left: 0;
31
+ width: 100%;
32
+ height: 100%;
33
+ opacity: 0;
34
+ transition: opacity 0.1s;
35
+ }
21
36
 
22
- &:hover {
37
+ :hover {
23
38
  cursor: pointer;
24
-
25
39
  ${ExtendedAction} {
26
- display: block;
40
+ display: ${(p) => (p.onlyOnHover ? "block" : "none")};
27
41
  }
28
42
  }
29
43
 
30
- > a {
31
- display: flex;
32
- padding: ${(p) => p.theme.spacing.xs};
33
- border-radius: ${(p) => p.theme.radii.s};
34
- color: inherit;
44
+ :hover:before {
45
+ background-color: ${(p) => p.theme.color.overlayHoverPrimary};
46
+ opacity: 1;
47
+ }
35
48
 
36
- &.selected {
37
- background-color: ${(p) => p.theme.color.overlayHoverPrimary};
38
- }
49
+ :active:before {
50
+ background-color: ${(p) => p.theme.color.overlayPressedPrimary};
51
+ opacity: 1;
52
+ }
39
53
 
40
- &:hover {
41
- background-color: ${(p) => p.theme.color.overlayHoverPrimary};
54
+ &.selected {
55
+ ${ExtendedAction} {
56
+ display: ${(p) => (p.onlyOnHover ? "none" : "block")};
42
57
  }
43
- &:active {
44
- background-color: ${(p) => p.theme.color.overlayPressedPrimary};
58
+ :hover {
59
+ ${ExtendedAction} {
60
+ display: block;
61
+ }
45
62
  }
46
63
  }
64
+
65
+ &.selected:before {
66
+ background-color: ${(p) => p.theme.color.overlayPressedPrimary};
67
+ opacity: 1;
68
+ }
47
69
  `;
48
70
 
49
- export { ExtendedAction, SubItem };
71
+ export { ExtendedAction, SubItem, ExtendedWrapper };
@@ -19,8 +19,8 @@ const Nav = (props: INavProps): JSX.Element => {
19
19
  const handleClick = () => onClick(item.path);
20
20
 
21
21
  const menuItem = (
22
- <MenuItem onClick={handleClick} key={key}>
23
- <NavLink to="#" className={selectedClass}>
22
+ <MenuItem onClick={handleClick} key={key} className={selectedClass}>
23
+ <NavLink to="#">
24
24
  <S.Link active={isSelected} data-testid="nav-link">
25
25
  {item.title}
26
26
  </S.Link>
@@ -89,9 +89,10 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
89
89
  const { value, label } = category;
90
90
  const filterOptions = () => filterOptionsByCategory(value);
91
91
  const isSelected = value === options.category;
92
+ const selectedClass = isSelected ? "selected" : "";
92
93
 
93
94
  return (
94
- <MenuItem key={`${value}${i}`} active={isSelected}>
95
+ <MenuItem key={`${value}${i}`} className={selectedClass}>
95
96
  <S.NavLink onClick={filterOptions} data-testid="side-modal-nav-link">
96
97
  <S.Link data-testid="side-modal-link" active={isSelected}>
97
98
  {label}
@@ -112,7 +112,7 @@ interface IState {
112
112
  }
113
113
 
114
114
  export interface ITooltipProps {
115
- content?: string | boolean | JSX.Element[];
115
+ content?: string | boolean | JSX.Element[] | JSX.Element;
116
116
  children: any;
117
117
  hideOnClick?: boolean;
118
118
  bottom?: boolean;
@@ -89,6 +89,7 @@ import Loading from "./Loading";
89
89
  import Login from "./Login";
90
90
  import MainWrapper from "./MainWrapper";
91
91
  import MenuItem from "./MenuItem";
92
+ import MenuGroup from "./MenuGroup";
92
93
  import Modal from "./Modal";
93
94
  import Nav from "./Nav";
94
95
  import Notification from "./Notification";
@@ -200,6 +201,7 @@ export {
200
201
  Login,
201
202
  MainWrapper,
202
203
  MenuItem,
204
+ MenuGroup,
203
205
  Modal,
204
206
  Nav,
205
207
  Notification,