@griddo/ax 10.6.0 → 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.
- package/package.json +2 -2
- package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/MixableComponentArray.test.tsx +3 -3
- package/src/components/Button/style.tsx +1 -1
- package/src/components/ConfigPanel/Form/ConnectedField/NavConnectedField/index.tsx +2 -2
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +2 -2
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +3 -3
- package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +4 -3
- package/src/components/Fields/ImageField/index.tsx +19 -2
- package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +8 -5
- package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/index.tsx +15 -5
- package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/style.tsx +1 -0
- package/src/components/Gallery/GalleryPanel/index.tsx +2 -11
- package/src/components/MenuGroup/index.tsx +81 -0
- package/src/components/MenuGroup/style.tsx +46 -0
- package/src/components/MenuItem/index.tsx +14 -7
- package/src/components/MenuItem/style.tsx +48 -26
- package/src/components/Nav/index.tsx +2 -2
- package/src/components/SideModal/index.tsx +2 -1
- package/src/components/Tooltip/index.tsx +1 -1
- package/src/components/index.tsx +2 -0
- package/src/containers/Gallery/actions.tsx +10 -2
- package/src/containers/Sites/actions.tsx +27 -0
- package/src/containers/Sites/constants.tsx +1 -0
- package/src/containers/Sites/interfaces.tsx +6 -0
- package/src/containers/Sites/reducer.tsx +4 -0
- package/src/helpers/index.tsx +2 -2
- package/src/helpers/themes.tsx +18 -13
- package/src/modules/Categories/CategoriesList/CategoryNav/NavItem/index.tsx +2 -2
- package/src/modules/Content/BulkHeader/TableHeader/index.tsx +1 -5
- package/src/modules/Content/BulkHeader/TableHeader/style.tsx +6 -0
- package/src/modules/Content/ContentFilters/index.tsx +66 -41
- package/src/modules/Content/ContentFilters/style.tsx +4 -38
- package/src/modules/Content/ContentFilters/utils.tsx +47 -10
- package/src/modules/Content/OptionTable/index.tsx +13 -14
- package/src/modules/Content/index.tsx +26 -7
- package/src/modules/Content/utils.tsx +5 -5
- package/src/modules/Navigation/Defaults/Nav/index.tsx +4 -6
- package/src/modules/Navigation/Menus/List/Nav/index.tsx +2 -2
- package/src/modules/Settings/ContentTypes/DataPacks/Nav/index.tsx +6 -10
- package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +1 -6
- package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/style.tsx +6 -0
- package/src/modules/StructuredData/StructuredDataList/ContentFilters/index.tsx +33 -41
- package/src/modules/StructuredData/StructuredDataList/ContentFilters/style.tsx +4 -38
- package/src/modules/StructuredData/StructuredDataList/ContentFilters/utils.tsx +44 -11
- package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +5 -5
- package/src/modules/StructuredData/StructuredDataList/index.tsx +1 -2
- package/src/modules/Users/UserForm/index.tsx +13 -27
- package/src/routes/multisite.tsx +1 -1
- package/src/routes/site.tsx +1 -1
- package/src/types/index.tsx +15 -0
- package/src/modules/Content/ContentFilters/constants.tsx +0 -15
- 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.6.
|
|
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": "
|
|
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,
|
|
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 ?
|
|
72
|
+
const filteredWhiteList = whiteList ? filterThemeModules(themeElements, whiteList) : whiteList;
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<FieldContainer
|
package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
86
|
-
const filteredWhiteList = 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 &&
|
|
40
|
-
(!isGlobalTab && usePermission("mediaGallery.deleteImages"));
|
|
45
|
+
(isGlobalTab && isAllowedtoDeleteGlobalImagesInSite) || (!isGlobalTab && isAllowedtoDeleteSiteImages);
|
|
41
46
|
const isAllowedToEdit =
|
|
42
|
-
(isGlobalTab &&
|
|
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
|
|
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;
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
23
|
-
{
|
|
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.
|
|
26
|
-
<
|
|
27
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
color: inherit;
|
|
44
|
+
:hover:before {
|
|
45
|
+
background-color: ${(p) => p.theme.color.overlayHoverPrimary};
|
|
46
|
+
opacity: 1;
|
|
47
|
+
}
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
:active:before {
|
|
50
|
+
background-color: ${(p) => p.theme.color.overlayPressedPrimary};
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
&.selected {
|
|
55
|
+
${ExtendedAction} {
|
|
56
|
+
display: ${(p) => (p.onlyOnHover ? "none" : "block")};
|
|
42
57
|
}
|
|
43
|
-
|
|
44
|
-
|
|
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="#"
|
|
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}`}
|
|
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}
|
package/src/components/index.tsx
CHANGED
|
@@ -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,
|