@griddo/ax 1.65.12 → 1.65.13

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.
@@ -33,7 +33,6 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
33
33
 
34
34
  const isPage = entity === "pages";
35
35
  const isCategories = entity === "categories";
36
-
37
36
  useEffect(() => {
38
37
  const getItems = async () => {
39
38
  let data = [];
@@ -123,21 +122,23 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
123
122
  const className = error ? "react-select-error" : "";
124
123
 
125
124
  return (
126
- <S.StyledSelect
127
- name={name}
128
- value={getObjectValue(value, state.items) || ""}
129
- cacheOptions
130
- loadOptions={loadOptions}
131
- defaultOptions={state.items}
132
- onInputChange={handleInputChange}
133
- onChange={handleChange}
134
- classNamePrefix="react-select"
135
- error={error}
136
- isDisabled={disabled}
137
- className={className}
138
- mandatory={mandatory}
139
- hasEmptyOption={state.hasEmptyOption}
140
- />
125
+ <div data-testid="asyncSelect">
126
+ <S.StyledSelect
127
+ name={name}
128
+ value={getObjectValue(value, state.items) || ""}
129
+ cacheOptions
130
+ loadOptions={loadOptions}
131
+ defaultOptions={state.items}
132
+ onInputChange={handleInputChange}
133
+ onChange={handleChange}
134
+ classNamePrefix="react-select"
135
+ error={error}
136
+ isDisabled={disabled}
137
+ className={className}
138
+ mandatory={mandatory}
139
+ hasEmptyOption={state.hasEmptyOption}
140
+ />
141
+ </div>
141
142
  );
142
143
  };
143
144
 
@@ -159,7 +160,7 @@ interface IAsyncSelectProps {
159
160
  error?: boolean;
160
161
  disabled?: boolean;
161
162
  onChange: (value: string) => void;
162
- site: ISite;
163
+ site: ISite | null;
163
164
  lang: string;
164
165
  selectedContent: any;
165
166
  mandatory?: boolean;
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import HeadingField from "./index";
3
+ import { ThemeProvider } from "styled-components";
4
+ import { parseTheme } from "@griddo/core";
5
+ import globalTheme from "../../../themes/theme.json";
6
+ import { render, screen, cleanup } from "@testing-library/react";
7
+
8
+ afterEach(cleanup);
9
+
10
+ const defaultProps = {
11
+ value: {
12
+ content: "",
13
+ tag: "",
14
+ },
15
+ title: "",
16
+ onChange: jest.fn(),
17
+ options: [
18
+ {
19
+ value: "",
20
+ label: "",
21
+ },
22
+ ],
23
+ showAdvanced: false,
24
+ };
25
+
26
+ describe("HeadingField component rendering", () => {
27
+ it("should render the component with only TextField", () => {
28
+ render(
29
+ <ThemeProvider theme={parseTheme(globalTheme)}>
30
+ <HeadingField {...defaultProps} />
31
+ </ThemeProvider>
32
+ );
33
+
34
+ const textFieldContainer = screen.getByTestId("textFieldContainer");
35
+
36
+ expect(textFieldContainer).toBeTruthy();
37
+ });
38
+ it("should render the component complete", () => {
39
+ const props = {
40
+ value: {
41
+ content: "value1",
42
+ tag: "tagValue",
43
+ },
44
+ title: "Title",
45
+ onChange: jest.fn(),
46
+ options: [
47
+ {
48
+ value: "value1",
49
+ label: "Value 1",
50
+ },
51
+ ],
52
+ showAdvanced: true,
53
+ };
54
+
55
+ render(
56
+ <ThemeProvider theme={parseTheme(globalTheme)}>
57
+ <HeadingField {...props} />
58
+ </ThemeProvider>
59
+ );
60
+
61
+ const textFieldContainer = screen.getByTestId("textFieldContainer");
62
+ const textFieldAdvancedWrapper = screen.getByTestId("textFieldAdvancedWrapper");
63
+ const contentWrapper = screen.getByTestId("contentWrapper");
64
+ const selectComponent = screen.getByTestId("selectComponent");
65
+
66
+ expect(textFieldContainer).toBeTruthy();
67
+ expect(textFieldAdvancedWrapper).toBeTruthy();
68
+ expect(contentWrapper).toBeTruthy();
69
+ expect(selectComponent).toBeTruthy();
70
+ });
71
+ });
@@ -16,7 +16,7 @@ const HeadingField = (props: IHeadingFieldProps): JSX.Element => {
16
16
  <>
17
17
  <TextField {...props} value={contentValue} onChange={handleChange} />
18
18
  {showAdvanced && (
19
- <S.AdvancedWrapper>
19
+ <S.AdvancedWrapper data-testid="textFieldAdvancedWrapper">
20
20
  <FieldsBehavior fieldType="Select" options={options} value={value.tag} onChange={handleSelectChange} />
21
21
  </S.AdvancedWrapper>
22
22
  )}
@@ -3,15 +3,28 @@ import React, { useState } from "react";
3
3
  import * as S from "./style";
4
4
 
5
5
  const Select = (props: ISelectProps): JSX.Element => {
6
- const { name, value, options, error, defaultValue, placeholder, isMulti, disabled, type, mandatory, onChange, alignRight } = props;
6
+ const {
7
+ name,
8
+ value,
9
+ options,
10
+ error,
11
+ defaultValue,
12
+ placeholder,
13
+ isMulti,
14
+ disabled,
15
+ type,
16
+ mandatory,
17
+ onChange,
18
+ alignRight,
19
+ } = props;
7
20
  const className = error ? `react-select-error ${type}` : type;
8
21
  const emptyOption = { value: null, label: placeholder || "Empty" };
9
-
22
+
10
23
  const isMandatory = !mandatory && type !== "inline";
11
24
  const [hasEmptyOption, setHasEmptyOption] = useState(isMandatory);
12
-
13
- const optionValues: any = hasEmptyOption ? [emptyOption , ...options] : options;
14
-
25
+
26
+ const optionValues: any = hasEmptyOption ? [emptyOption, ...options] : options;
27
+
15
28
  const handleChange = (selectedValue: IOptionProps) => {
16
29
  onChange(selectedValue.value);
17
30
  };
@@ -20,7 +33,7 @@ const Select = (props: ISelectProps): JSX.Element => {
20
33
  const inputValue = newValue.replace(/\W/g, "");
21
34
  setHasEmptyOption(isMandatory && inputValue.length === 0);
22
35
  return inputValue;
23
- }
36
+ };
24
37
 
25
38
  // tslint:disable-next-line: no-shadowed-variable
26
39
  const getObjectValue = (value: string, options: IOptionProps[]) => {
@@ -29,28 +42,30 @@ const Select = (props: ISelectProps): JSX.Element => {
29
42
  } else {
30
43
  return options.find((option) => option.value === value);
31
44
  }
32
- }
45
+ };
33
46
 
34
47
  const searchable = type === "inline" ? false : true;
35
48
 
36
49
  return (
37
- <S.StyledSelect
38
- name={name}
39
- value={getObjectValue(value, optionValues)}
40
- options={optionValues}
41
- isMulti={isMulti}
42
- isDisabled={disabled}
43
- placeholder={placeholder}
44
- defaultValue={defaultValue}
45
- className={className}
46
- classNamePrefix="react-select"
47
- error={error}
48
- onChange={handleChange}
49
- isSearchable={searchable}
50
- hasEmptyOption={hasEmptyOption}
51
- onInputChange={handleInputChange}
52
- alignRight={alignRight}
53
- />
50
+ <div data-testid="selectComponent">
51
+ <S.StyledSelect
52
+ name={name}
53
+ value={getObjectValue(value, optionValues)}
54
+ options={optionValues}
55
+ isMulti={isMulti}
56
+ isDisabled={disabled}
57
+ placeholder={placeholder}
58
+ defaultValue={defaultValue}
59
+ className={className}
60
+ classNamePrefix="react-select"
61
+ error={error}
62
+ onChange={handleChange}
63
+ isSearchable={searchable}
64
+ hasEmptyOption={hasEmptyOption}
65
+ onInputChange={handleInputChange}
66
+ alignRight={alignRight}
67
+ />
68
+ </div>
54
69
  );
55
70
  };
56
71
 
@@ -6,7 +6,7 @@ import { isReqOk } from "@ax/helpers";
6
6
  import { IPage, IUrlField, Field, ISelectOption } from "@ax/types";
7
7
  import { pages as pagesApi } from "@ax/api";
8
8
  import PageFinder from "./PageFinder";
9
- import { findAnchorsFromPage } from "./utils";
9
+ import { findAnchorsFromPage, findAnchorsFromTab, findTabsFromPage } from "./utils";
10
10
 
11
11
  import * as S from "./style";
12
12
 
@@ -14,8 +14,11 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
14
14
  const { value, onChange, showAdvanced, handlePanel, inFloatingPanel, disabled, handleValidation, validators } = props;
15
15
  const { isOpen, toggleModal } = useModal();
16
16
  const [anchorOptions, setAnchorOptions] = useState<ISelectOption[]>([]);
17
+ const [tabOptions, setTabOptions] = useState<ISelectOption[]>([]);
17
18
  const [isVisible, setIsVisible] = useState<boolean>(false);
19
+ const [isTabsVisible, setTabsVisible] = useState<boolean>(false);
18
20
  const [internalPageName, setInternalPageName] = useState(null);
21
+ const [pageData, setPageData] = useState<IPage | null>(null);
19
22
 
20
23
  const pageID = value && value.linkTo ? value.linkTo : null;
21
24
 
@@ -24,17 +27,23 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
24
27
  const response = await pagesApi.getPageInfo(id);
25
28
  if (isReqOk(response.status)) {
26
29
  const { data } = response;
30
+ setPageData(data);
27
31
  setInternalPageName(data.title);
28
32
 
29
33
  onChange({
30
34
  ...value,
31
35
  ...(!data.follow && { noFollow: true }),
32
- ...(!value?.title && { title: data.title })
36
+ ...(!value?.title && { title: data.title }),
33
37
  });
34
38
 
35
- const anchors: ISelectOption[] = findAnchorsFromPage(data);
39
+ const tabs: ISelectOption[] = findTabsFromPage(data);
40
+ setTabOptions(tabs);
41
+ if (tabs.length > 0) setTabsVisible(true);
42
+
43
+ const anchors: ISelectOption[] =
44
+ tabs.length && value?.subSlug ? findAnchorsFromTab(data, value.subSlug) : findAnchorsFromPage(data);
36
45
  setAnchorOptions(anchors);
37
- if (anchors.length > 0) setIsVisible(true);
46
+ if (anchors.length > 0 && (!tabs.length || (tabs.length && value?.subSlug))) setIsVisible(true);
38
47
  } else {
39
48
  setAnchorOptions([]);
40
49
  }
@@ -42,6 +51,7 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
42
51
 
43
52
  let isMounted = true;
44
53
  if (isMounted && pageID) {
54
+ setTabsVisible(false);
45
55
  setIsVisible(false);
46
56
  getPageInfo(pageID);
47
57
  }
@@ -79,6 +89,17 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
79
89
 
80
90
  const handleAnchorChange = (newValue: string) => onChange({ ...value, anchor: newValue });
81
91
 
92
+ const handleTabChange = (newValue: string) => {
93
+ onChange({ ...value, subSlug: newValue });
94
+ if (pageData && newValue) {
95
+ const anchors: ISelectOption[] = findAnchorsFromTab(pageData, newValue);
96
+ setAnchorOptions(anchors);
97
+ if (anchors.length > 0) setIsVisible(true);
98
+ } else {
99
+ setIsVisible(false);
100
+ }
101
+ };
102
+
82
103
  const validator = { format: "fullURL" };
83
104
  const defensiveHref = value ? value.href : "";
84
105
 
@@ -132,6 +153,19 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
132
153
  return (
133
154
  <>
134
155
  {field}
156
+ {value && value.linkTo && isTabsVisible && (
157
+ <S.AnchorWrapper>
158
+ <FieldsBehavior
159
+ title="Tab"
160
+ options={tabOptions}
161
+ name="tab"
162
+ fieldType="Select"
163
+ value={value.subSlug}
164
+ onChange={handleTabChange}
165
+ placeholder="Select Tab"
166
+ />
167
+ </S.AnchorWrapper>
168
+ )}
135
169
  {value && value.linkTo && isVisible && (
136
170
  <S.AnchorWrapper>
137
171
  <FieldsBehavior
@@ -1,6 +1,58 @@
1
+ import { getSchema } from "@ax/helpers";
1
2
  import { IPage, ISelectOption } from "@ax/types";
2
3
 
4
+ const findSchemaComponentArrays = (component: string) => {
5
+ const keys: string[] = [];
6
+ const schemaTabs = getSchema(component).configTabs;
7
+ schemaTabs.forEach((tab: any) => {
8
+ tab.fields.forEach((field: any) => {
9
+ if (field.type === "ComponentArray") {
10
+ keys.push(field.key);
11
+ }
12
+ });
13
+ });
14
+ return keys;
15
+ };
16
+
17
+ const findAnchorsFromModule = (module: any) => {
18
+ if (!module) return [];
19
+ let options: ISelectOption[] = [];
20
+
21
+ if (module.anchorID && module.anchorID.trim() !== "") {
22
+ const option = { value: module.anchorID, label: module.anchorID };
23
+ options.push(option);
24
+ }
25
+
26
+ const arrayKeys = findSchemaComponentArrays(module.component);
27
+ if (arrayKeys.length) {
28
+ arrayKeys.forEach((key: string) => {
29
+ module[key].forEach((element: any) => {
30
+ const subOptions = findAnchorsFromModule(element);
31
+ options = [...options, ...subOptions];
32
+ });
33
+ });
34
+ }
35
+
36
+ return options;
37
+ };
38
+
3
39
  const findAnchorsFromPage = (page: IPage): ISelectOption[] => {
40
+ let options: ISelectOption[] = [];
41
+ const { template } = page;
42
+ const sections = Object.keys(template)
43
+ .map((key: string) => template[key])
44
+ .filter((value: any) => typeof value === "object" && value !== null && value.component === "Section");
45
+
46
+ sections.forEach((section: any) => {
47
+ section.modules.forEach((module: any) => {
48
+ const sectionOptions = findAnchorsFromModule(module);
49
+ options = [...options, ...sectionOptions];
50
+ });
51
+ });
52
+ return options;
53
+ };
54
+
55
+ const findTabsFromPage = (page: IPage): ISelectOption[] => {
4
56
  const options: ISelectOption[] = [];
5
57
  const { template } = page;
6
58
  const sections = Object.keys(template)
@@ -9,20 +61,10 @@ const findAnchorsFromPage = (page: IPage): ISelectOption[] => {
9
61
 
10
62
  sections.forEach((section: any) => {
11
63
  section.modules.forEach((module: any) => {
12
- if (module.anchorID && module.anchorID.trim() !== "") {
13
- const option = { value: module.anchorID, label: module.anchorID };
14
- options.push(option);
15
- }
16
- if (module.elements) {
64
+ if (module.hasGriddoMultiPage && module.elements) {
17
65
  module.elements.forEach((element: any) => {
18
- element.componentModules &&
19
- Array.isArray(element.componentModules) &&
20
- element.componentModules.forEach((component: any) => {
21
- if (component.anchorID && component.anchorID.trim() !== "") {
22
- const option = { value: component.anchorID, label: component.anchorID };
23
- options.push(option);
24
- }
25
- });
66
+ const option = { value: element.sectionSlug, label: element.title };
67
+ options.push(option);
26
68
  });
27
69
  }
28
70
  });
@@ -30,4 +72,22 @@ const findAnchorsFromPage = (page: IPage): ISelectOption[] => {
30
72
  return options;
31
73
  };
32
74
 
33
- export { findAnchorsFromPage };
75
+ const findAnchorsFromTab = (page: IPage, tabSlug: string): ISelectOption[] => {
76
+ let options: ISelectOption[] = [];
77
+ const { template } = page;
78
+ const sections = Object.keys(template)
79
+ .map((key: string) => template[key])
80
+ .filter((value: any) => typeof value === "object" && value !== null && value.component === "Section");
81
+
82
+ sections.forEach((section: any) => {
83
+ section.modules.forEach((module: any) => {
84
+ if (module.hasGriddoMultiPage) {
85
+ const tab = module.elements.find((elem: any) => elem.sectionSlug === tabSlug);
86
+ options = findAnchorsFromModule(tab);
87
+ }
88
+ });
89
+ });
90
+ return options;
91
+ };
92
+
93
+ export { findAnchorsFromPage, findTabsFromPage, findAnchorsFromTab };
@@ -73,7 +73,7 @@ const FieldsBehavior = (props: any): JSX.Element => {
73
73
  };
74
74
  return (
75
75
  <S.Wrapper error={errorField} className={wrapperClass} showTitle={showTitle} id={objKey}>
76
- <S.Content error={errorField}>
76
+ <S.Content data-testid="contentWrapper" error={errorField}>
77
77
  <Field {...props} showAdvanced={showAdvanced} handleValidation={handleValidation} error={errorField} />
78
78
  </S.Content>
79
79
  <S.Header className="fieldHeader">
@@ -164,6 +164,7 @@ export interface IUrlField {
164
164
  noFollow?: boolean;
165
165
  anchor?: string;
166
166
  title?: string;
167
+ subSlug?: string;
167
168
  }
168
169
 
169
170
  export interface IHeadingField {
@@ -720,11 +721,11 @@ export interface IDimensionsGroup {
720
721
  }
721
722
 
722
723
  export interface ITemplate {
723
- dataPacks: string[],
724
- id: string,
725
- thumbnails: Record<string, string>,
726
- title: string,
727
- type: { label: string, value: string, mode: string },
724
+ dataPacks: string[];
725
+ id: string;
726
+ thumbnails: Record<string, string>;
727
+ title: string;
728
+ type: { label: string; value: string; mode: string };
728
729
  }
729
730
 
730
731
  export type Field =