@griddo/ax 1.72.11 → 1.73.2

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 (76) hide show
  1. package/package.json +2 -2
  2. package/scripts/griddo-sync-schemas.js +1 -1
  3. package/src/__tests__/components/ErrorCenter/ErrorCenter.test.tsx +186 -0
  4. package/src/__tests__/components/Flag/Flag.test.tsx +60 -0
  5. package/src/__tests__/components/FloatingMenu/FloatingMenu.test.tsx +712 -0
  6. package/src/__tests__/components/FloatingPanel/FloatingPanel.test.tsx +149 -0
  7. package/src/__tests__/components/GuardModal/GuardModal.test.tsx +31 -0
  8. package/src/__tests__/components/Icon/Icon.test.tsx +76 -0
  9. package/src/__tests__/components/IconAction/IconAction.test.tsx +91 -0
  10. package/src/__tests__/components/Notification/Notification.test.tsx +206 -0
  11. package/src/__tests__/components/Notification/SubNotification/Subnotification.test.tsx +46 -0
  12. package/src/__tests__/components/ReorderArrows/ReorderArrows.test.tsx +96 -0
  13. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +200 -0
  14. package/src/__tests__/components/SearchField/SearchField.test.tsx +375 -0
  15. package/src/api/analytics.tsx +4 -4
  16. package/src/components/ActionMenu/style.tsx +1 -0
  17. package/src/components/ConfigPanel/Form/ConnectedField/NavConnectedField/index.tsx +1 -1
  18. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +2 -1
  19. package/src/components/ConfigPanel/Form/index.tsx +22 -1
  20. package/src/components/ConfigPanel/Form/style.tsx +19 -0
  21. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +22 -3
  22. package/src/components/ConfigPanel/GlobalPageForm/style.tsx +18 -2
  23. package/src/components/ConfigPanel/NavigationForm/Field/index.tsx +25 -13
  24. package/src/components/ConfigPanel/index.tsx +8 -0
  25. package/src/components/ErrorCenter/index.tsx +8 -4
  26. package/src/components/Fields/DateField/DatePickerInput/index.tsx +30 -8
  27. package/src/components/Fields/DateField/index.tsx +8 -2
  28. package/src/components/Fields/Select/index.tsx +1 -0
  29. package/src/components/Flag/index.tsx +13 -11
  30. package/src/components/FloatingMenu/index.tsx +23 -7
  31. package/src/components/FloatingMenu/style.tsx +1 -0
  32. package/src/components/FloatingPanel/index.tsx +9 -3
  33. package/src/components/GuardModal/index.tsx +3 -3
  34. package/src/components/Icon/index.tsx +2 -1
  35. package/src/components/IconAction/index.tsx +3 -3
  36. package/src/components/MainWrapper/AppBar/index.tsx +3 -1
  37. package/src/components/MainWrapper/AppBar/style.tsx +3 -0
  38. package/src/components/MenuItem/index.tsx +1 -1
  39. package/src/components/Modal/index.tsx +1 -1
  40. package/src/components/Notification/SubNotification/index.tsx +33 -0
  41. package/src/components/Notification/SubNotification/style.tsx +49 -0
  42. package/src/components/Notification/index.tsx +31 -17
  43. package/src/components/Notification/style.tsx +33 -8
  44. package/src/components/ReorderArrows/index.tsx +3 -3
  45. package/src/components/ResizePanel/ResizeHandle/index.tsx +29 -38
  46. package/src/components/ResizePanel/index.tsx +13 -15
  47. package/src/components/SearchField/index.tsx +9 -8
  48. package/src/containers/Analytics/actions.tsx +14 -4
  49. package/src/containers/App/actions.tsx +18 -6
  50. package/src/containers/App/reducer.tsx +1 -0
  51. package/src/containers/Domains/actions.tsx +8 -1
  52. package/src/containers/Navigation/Defaults/actions.tsx +16 -2
  53. package/src/containers/PageEditor/actions.tsx +82 -6
  54. package/src/containers/PageEditor/utils.tsx +28 -10
  55. package/src/containers/Redirects/actions.tsx +16 -2
  56. package/src/containers/Sites/actions.tsx +80 -3
  57. package/src/containers/StructuredData/actions.tsx +24 -3
  58. package/src/containers/Users/actions.tsx +8 -1
  59. package/src/forms/errors.tsx +1 -0
  60. package/src/forms/fields.tsx +6 -3
  61. package/src/forms/validators.tsx +14 -4
  62. package/src/guards/error/index.tsx +17 -21
  63. package/src/helpers/dates.tsx +2 -0
  64. package/src/helpers/index.tsx +2 -0
  65. package/src/hooks/modals.tsx +4 -4
  66. package/src/modules/Content/OptionTable/index.tsx +20 -7
  67. package/src/modules/Content/index.tsx +4 -7
  68. package/src/modules/Content/utils.tsx +18 -13
  69. package/src/modules/FramePreview/index.tsx +39 -12
  70. package/src/modules/GlobalEditor/index.tsx +17 -20
  71. package/src/modules/Navigation/Menus/List/Table/index.tsx +2 -2
  72. package/src/modules/PageEditor/Editor/index.tsx +13 -0
  73. package/src/modules/PageEditor/index.tsx +17 -20
  74. package/src/modules/Redirects/RedirectItem/index.tsx +17 -3
  75. package/src/modules/Settings/ContentTypes/DataPacks/Item/index.tsx +1 -1
  76. package/src/modules/StructuredData/Form/index.tsx +10 -13
@@ -10,7 +10,7 @@ const noteText = "This is Global content and you cannot edit it here. To do so,
10
10
  const noteTitle = "Global content";
11
11
 
12
12
  const GlobalPageForm = (props: IProps): JSX.Element => {
13
- const { selectedTab, setSelectedTab, schema, pageTitle, setHistoryPush, actions } = props;
13
+ const { selectedTab, setSelectedTab, schema, pageTitle, setHistoryPush, actions, header, footer } = props;
14
14
  const tabs = ["content", "config"];
15
15
 
16
16
  const handleGetGlobalPage = async () => {
@@ -32,12 +32,28 @@ const GlobalPageForm = (props: IProps): JSX.Element => {
32
32
  options: { excludeDetailPages: true },
33
33
  };
34
34
 
35
+ const handleRestoreHeader = () =>
36
+ actions.restorePageNavigationAction && actions.restorePageNavigationAction("header");
37
+
38
+ const handleRestoreFooter = () =>
39
+ actions.restorePageNavigationAction && actions.restorePageNavigationAction("footer");
40
+
35
41
  return (
36
42
  <section>
37
43
  <Tabs tabs={tabs} active={selectedTab} setSelectedTab={setSelectedTab} />
38
- <S.FieldWrapper>
44
+ <S.NoteWrapper>
39
45
  <NoteField value={{ text: noteText, title: noteTitle }} />
40
- </S.FieldWrapper>
46
+ </S.NoteWrapper>
47
+ {selectedTab === "content" && header === 0 && (
48
+ <S.FieldWrapper>
49
+ This page doesn&apos;t have a header. Click <S.Link onClick={handleRestoreHeader}>here</S.Link> to restore it.
50
+ </S.FieldWrapper>
51
+ )}
52
+ {selectedTab === "content" && footer === 0 && (
53
+ <S.FieldWrapper>
54
+ This page doesn&apos;t have a footer. Click <S.Link onClick={handleRestoreFooter}>here</S.Link> to restore it.
55
+ </S.FieldWrapper>
56
+ )}
41
57
  {selectedTab === "content" && (
42
58
  <S.DataWrapper>
43
59
  <S.IconWrapper>
@@ -70,9 +86,12 @@ interface IProps {
70
86
  schema: ISchema;
71
87
  pageTitle?: string;
72
88
  setHistoryPush?: (path: string, isEditor: boolean) => void;
89
+ header?: number | null;
90
+ footer?: number | null;
73
91
  actions: {
74
92
  getGlobalFromLocalPageAction(): Promise<void>;
75
93
  saveCurrentSiteInfoAction(): Promise<void>;
94
+ restorePageNavigationAction(type: string): Promise<void>;
76
95
  };
77
96
  }
78
97
 
@@ -1,7 +1,7 @@
1
1
  import { Button } from "@ax/components";
2
2
  import styled from "styled-components";
3
3
 
4
- const FieldWrapper = styled.div`
4
+ const NoteWrapper = styled.div`
5
5
  margin-top: ${(p) => p.theme.spacing.s};
6
6
  margin-bottom: ${(p) => p.theme.spacing.m};
7
7
  `;
@@ -33,4 +33,20 @@ const StyledButton = styled(Button)`
33
33
  margin-left: auto;
34
34
  `;
35
35
 
36
- export { FieldWrapper, DataWrapper, IconWrapper, DataTitle, StyledButton };
36
+ const FieldWrapper = styled.div`
37
+ margin-top: ${(p) => p.theme.spacing.s};
38
+ margin-bottom: ${(p) => p.theme.spacing.s};
39
+ background-color: ${(p) => p.theme.color?.uiBackground03};
40
+ padding: ${(p) => p.theme.spacing?.s};
41
+ border-radius: ${(p) => p.theme.radii?.s};
42
+ ${(p) => p.theme.textStyle?.uiXS};
43
+ color: ${(p) => p.theme.color?.textMediumEmphasis};
44
+ `;
45
+
46
+ const Link = styled.span`
47
+ cursor: pointer;
48
+ font-weight: bold;
49
+ color: ${(p) => p.theme.color?.interactive01};
50
+ `;
51
+
52
+ export { NoteWrapper, FieldWrapper, DataWrapper, IconWrapper, DataTitle, StyledButton, Link };
@@ -7,10 +7,8 @@ import { IRootState } from "@ax/types";
7
7
 
8
8
  import * as S from "./style";
9
9
 
10
- const pageEditorID = 0;
11
-
12
10
  const Field = (props: IProps) => {
13
- const { type, defaults, updateEditorContent, selectedContent, theme } = props;
11
+ const { type, defaults, updateEditorContent, selectedContent, theme, removeNavigationFromPage } = props;
14
12
  const { isOpen, toggleModal } = useModal();
15
13
  const isDefault = selectedContent.setAsDefault;
16
14
 
@@ -27,21 +25,17 @@ const Field = (props: IProps) => {
27
25
  }
28
26
 
29
27
  const optionsType = `${type}s`;
30
-
31
- const actionMenuOptions = [
32
- {
33
- label: "replace",
34
- icon: "change",
35
- action: toggleModal,
36
- },
37
- ];
38
-
39
28
  const actionMenuIcon = "more";
29
+ const pageEditorID = 0;
40
30
 
41
31
  const handleReplace = (option: any) => {
42
32
  updateEditorContent(pageEditorID, type, option.id);
43
33
  };
44
34
 
35
+ const handleRemove = () => {
36
+ removeNavigationFromPage(type);
37
+ };
38
+
45
39
  const setDefault = {
46
40
  title: `Set the default ${type}`,
47
41
  action: () => {
@@ -52,12 +46,28 @@ const Field = (props: IProps) => {
52
46
  checked: selectedContent.setAsDefault,
53
47
  };
54
48
 
49
+ const actionMenuOptions = [
50
+ {
51
+ label: "remove",
52
+ icon: "delete",
53
+ action: handleRemove,
54
+ },
55
+ ];
56
+
57
+ if (hasMultipleOptions) {
58
+ actionMenuOptions.unshift({
59
+ label: "replace",
60
+ icon: "change",
61
+ action: toggleModal,
62
+ });
63
+ }
64
+
55
65
  return (
56
66
  <>
57
67
  <S.Component isArray disabled>
58
68
  <S.Label containerInfo>{selectedContent.title}</S.Label>
59
69
  <S.HiddenButtonsWrapper>
60
- {hasMultipleOptions && <ActionMenu options={actionMenuOptions} icon={actionMenuIcon} />}
70
+ <ActionMenu options={actionMenuOptions} icon={actionMenuIcon} />
61
71
  </S.HiddenButtonsWrapper>
62
72
  </S.Component>
63
73
  {hasMultipleOptions && (
@@ -87,6 +97,7 @@ interface IStateProps {
87
97
 
88
98
  interface IDispatchProps {
89
99
  updateEditorContent: (selectedEditorID: number, key: string, value: any) => void;
100
+ removeNavigationFromPage: (key: string) => void;
90
101
  }
91
102
 
92
103
  type IProps = IField & IStateProps & IDispatchProps;
@@ -98,6 +109,7 @@ const mapStateToProps = (state: IRootState) => ({
98
109
 
99
110
  const mapDispatchToProps = {
100
111
  updateEditorContent: pageEditorActions.updateEditorContent,
112
+ removeNavigationFromPage: pageEditorActions.removeNavigationFromPage,
101
113
  };
102
114
 
103
115
  export default connect(mapStateToProps, mapDispatchToProps)(Field);
@@ -34,6 +34,8 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
34
34
  userEditing,
35
35
  theme,
36
36
  lastElementAddedId,
37
+ header,
38
+ footer,
37
39
  } = props;
38
40
 
39
41
  const wrapperRef = useRef<HTMLDivElement>(null);
@@ -66,6 +68,8 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
66
68
  pageTitle={pageTitle}
67
69
  setHistoryPush={setHistoryPush}
68
70
  actions={actions}
71
+ header={header}
72
+ footer={footer}
69
73
  />
70
74
  );
71
75
  } else {
@@ -80,6 +84,8 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
80
84
  isGlobal={isGlobal}
81
85
  theme={theme}
82
86
  setHistoryPush={setHistoryPush}
87
+ header={header}
88
+ footer={footer}
83
89
  />
84
90
  );
85
91
  }
@@ -119,6 +125,8 @@ interface IStateProps {
119
125
  userEditing?: IUserEditing | null;
120
126
  theme: string;
121
127
  lastElementAddedId?: null | number;
128
+ header?: number | null;
129
+ footer?: number | null;
122
130
  }
123
131
 
124
132
  export default ConfigPanel;
@@ -4,7 +4,7 @@ import { Icon } from "@ax/components";
4
4
 
5
5
  import * as S from "./style";
6
6
 
7
- const ErrorCenter = (props: IProps): JSX.Element => {
7
+ const ErrorCenter = (props: IErrorCenterProps): JSX.Element => {
8
8
  const { errors, actions } = props;
9
9
 
10
10
  const goToElement = (key: string) => {
@@ -26,9 +26,13 @@ const ErrorCenter = (props: IProps): JSX.Element => {
26
26
 
27
27
  const icon = item.type === "warning" ? "warning" : "alert";
28
28
  const isClicable = !!item.editorID || !!item.hasDeactivatedPackage || !!item.key;
29
-
30
29
  return (
31
- <S.Wrapper key={`${item.editorID}${item.key}`} clickable={isClicable} onClick={handleClick}>
30
+ <S.Wrapper
31
+ data-testid="error-center-wrapper"
32
+ key={`${item.editorID}${item.key}`}
33
+ clickable={isClicable}
34
+ onClick={handleClick}
35
+ >
32
36
  <S.Header type={item.type}>
33
37
  <Icon name={icon} size="16" />
34
38
  <S.Type>{item.type}</S.Type>
@@ -51,7 +55,7 @@ const ErrorCenter = (props: IProps): JSX.Element => {
51
55
  );
52
56
  };
53
57
 
54
- interface IProps {
58
+ export interface IErrorCenterProps {
55
59
  errors: IErrorItem[];
56
60
  actions?: {
57
61
  goToError(editorID: number | null, tab: string, template: boolean): void;
@@ -1,13 +1,13 @@
1
1
  import React, { useEffect, useState, forwardRef } from "react";
2
2
 
3
- import { isValidDate, stringToDate, getStringifyDateRange } from "@ax/helpers";
3
+ import { isValidDate, stringToDate, getStringifyDateRange, getRange, isValidDateRange } from "@ax/helpers";
4
4
 
5
5
  import { Icon, IconAction } from "@ax/components";
6
6
 
7
7
  import * as S from "./style";
8
8
 
9
9
  const DatePickerInput = (props: IDatePickerProps, ref: any): JSX.Element => {
10
- const { dates, onClick, handleChange, isOpen, error, disabled } = props;
10
+ const { dates, onClick, handleChange, isOpen, error, disabled, handleValidation, validators } = props;
11
11
  const { start, end } = dates;
12
12
  const placeholder = "dd/mm/yyyy";
13
13
 
@@ -19,15 +19,29 @@ const DatePickerInput = (props: IDatePickerProps, ref: any): JSX.Element => {
19
19
  const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
20
20
  const inputValue = e.target.value;
21
21
  setValue(inputValue);
22
+ const range = getRange(inputValue);
23
+ const isRange = range.length === 2;
22
24
 
23
- if (isValidDate(inputValue)) {
24
- const date = stringToDate(inputValue);
25
- handleChange([date, null]);
26
- } else if (!inputValue) {
27
- handleChange([null, null]);
25
+ if (isRange && isValidDateRange(inputValue)) {
26
+ const start = stringToDate(range[0]);
27
+ const end = stringToDate(range[1]);
28
+ handleChange([start, end]);
29
+ } else {
30
+ if (isValidDate(inputValue)) {
31
+ const date = stringToDate(inputValue);
32
+ handleChange([date, null]);
33
+ } else if (!inputValue) {
34
+ handleChange([null, null]);
35
+ }
28
36
  }
29
37
  };
30
38
 
39
+ const handleOnBlur = (e: React.FocusEvent<HTMLInputElement>) => {
40
+ const inputValue = e.target.value;
41
+
42
+ handleValidation && handleValidation(inputValue, validators);
43
+ };
44
+
31
45
  const CalendarIcon = <IconAction icon="calendar" onClick={onClick} />;
32
46
  const CloseIcon = (
33
47
  <S.IconWrapper>
@@ -37,7 +51,13 @@ const DatePickerInput = (props: IDatePickerProps, ref: any): JSX.Element => {
37
51
 
38
52
  return (
39
53
  <S.InputWrapper error={error}>
40
- <S.Input value={value || ""} placeholder={placeholder} onChange={handleOnChange} disabled={disabled} />
54
+ <S.Input
55
+ value={value || ""}
56
+ placeholder={placeholder}
57
+ onChange={handleOnChange}
58
+ onBlur={handleOnBlur}
59
+ disabled={disabled}
60
+ />
41
61
  <S.ButtonWrapper>{isOpen ? CloseIcon : CalendarIcon}</S.ButtonWrapper>
42
62
  </S.InputWrapper>
43
63
  );
@@ -50,6 +70,8 @@ interface IDatePickerProps {
50
70
  isOpen: boolean;
51
71
  error?: boolean;
52
72
  disabled?: boolean;
73
+ handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
74
+ validators?: Record<string, unknown>;
53
75
  }
54
76
 
55
77
  export default forwardRef(DatePickerInput);
@@ -28,11 +28,14 @@ const DateField = (props: IDateFieldProps): JSX.Element => {
28
28
  selectsRange = false,
29
29
  disabled,
30
30
  handleValidation,
31
+ validators,
31
32
  } = props;
32
33
 
33
34
  const currentDate = new Date();
34
35
  const defaultValue = mandatory ? dateToString(currentDate, "yyyy/MM/dd") : null;
35
36
  const stringDate = value && typeof value === "number" ? dateToString(new Date(value * 1000)) : value;
37
+ const dateValidators = { format: "date", ...validators };
38
+
36
39
  const dateValue = stringDate && (isValidDate(stringDate) || isValidDateRange(stringDate)) ? stringDate : defaultValue;
37
40
  const isRange = dateValue && dateValue.split(" - ").length > 1;
38
41
  const { isOpen, toggleModal } = useModal();
@@ -77,7 +80,7 @@ const DateField = (props: IDateFieldProps): JSX.Element => {
77
80
  setDates(rangeDates);
78
81
  onChange(selectedDate);
79
82
 
80
- handleValidation && handleValidation(start);
83
+ handleValidation && handleValidation(selectedDate, dateValidators);
81
84
  };
82
85
 
83
86
  const { start, end } = dates;
@@ -91,6 +94,8 @@ const DateField = (props: IDateFieldProps): JSX.Element => {
91
94
  isOpen={isOpen}
92
95
  error={error}
93
96
  disabled={disabled}
97
+ handleValidation={handleValidation}
98
+ validators={dateValidators}
94
99
  />
95
100
  );
96
101
 
@@ -126,7 +131,8 @@ export interface IDateFieldProps {
126
131
  futureDate?: boolean;
127
132
  pastDate?: boolean;
128
133
  disabled?: boolean;
129
- handleValidation?: (value: string) => void;
134
+ handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
135
+ validators?: Record<string, unknown>;
130
136
  }
131
137
 
132
138
  export default DateField;
@@ -64,6 +64,7 @@ const Select = (props: ISelectProps): JSX.Element => {
64
64
  hasEmptyOption={hasEmptyOption}
65
65
  onInputChange={handleInputChange}
66
66
  alignRight={alignRight}
67
+ aria-label={name}
67
68
  />
68
69
  </div>
69
70
  );
@@ -10,30 +10,32 @@ const getImage = (name: string) => {
10
10
  pt: "Pt_PT",
11
11
  };
12
12
  const filename = simpleIcons[name?.toLowerCase()] || name;
13
+
13
14
  try {
14
15
  return require(`./components/${filename}`).default;
15
16
  } catch (err) {
16
17
  return null;
17
18
  }
18
19
  };
19
- export default class Flag extends React.Component<IProps> {
20
- public render() {
21
- const name = this.props.name.charAt(0).toUpperCase() + this.props.name.slice(1);
20
+ const Flag = (props: IProps) => {
21
+ const name = props.name.charAt(0).toUpperCase() + props.name.slice(1);
22
22
 
23
- const size = this.props.size ? this.props.size : "24";
23
+ const size = props.size ? props.size : "24";
24
24
 
25
- const Svg = getImage(name);
25
+ const Svg = getImage(name);
26
26
 
27
- if (Svg) {
28
- return <Svg height={size} width={size} viewBox="0 0 512 512" />;
29
- }
30
- return null;
27
+ if (Svg) {
28
+ return <Svg height={size} width={size} viewBox="0 0 512 512" />;
31
29
  }
32
- }
33
30
 
34
- interface IStateProps {
31
+ return null;
32
+ };
33
+
34
+ export interface IStateProps {
35
35
  name: string;
36
36
  size?: string;
37
37
  }
38
38
 
39
39
  type IProps = IStateProps;
40
+
41
+ export default Flag;
@@ -1,10 +1,10 @@
1
- import React, { useRef, useState } from "react";
1
+ import React, { useEffect, useRef, useState } from "react";
2
2
 
3
3
  import { useHandleClickOutside } from "@ax/hooks";
4
4
 
5
5
  import * as S from "./style";
6
6
 
7
- const FloatingMenu = (props: IProps) => {
7
+ const FloatingMenu = (props: IFloatingProps) => {
8
8
  const {
9
9
  children,
10
10
  isInAppBar,
@@ -13,13 +13,23 @@ const FloatingMenu = (props: IProps) => {
13
13
  closeOnSelect = true,
14
14
  isCheckGroup,
15
15
  reactiveToHover,
16
- offset
16
+ offset,
17
17
  } = props;
18
18
  const wrapper = useRef<any>(null);
19
19
  const button = useRef<any>(null);
20
20
  const menuOptions = useRef<any>(null);
21
21
  const [isOpen, setOpen] = useState(false);
22
22
 
23
+ useEffect(() => {
24
+ if (isOpen && menuOptions.current) {
25
+ const bounding = menuOptions.current.getBoundingClientRect();
26
+ if (bounding.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
27
+ menuOptions.current.scrollIntoView({ block: "end", behavior: "smooth" });
28
+ }
29
+ }
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [isOpen]);
32
+
23
33
  const handleClickOutside = (e: any) => {
24
34
  if (wrapper.current.contains(e.target)) {
25
35
  return;
@@ -51,8 +61,7 @@ const FloatingMenu = (props: IProps) => {
51
61
  const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) =>
52
62
  reactiveToHover && !isOpen ? handleClick(e) : null;
53
63
 
54
- const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) =>
55
- reactiveToHover && isOpen && setOpen(false);
64
+ const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => reactiveToHover && isOpen && setOpen(false);
56
65
 
57
66
  useHandleClickOutside(isOpen, handleClickOutside);
58
67
 
@@ -67,8 +76,15 @@ const FloatingMenu = (props: IProps) => {
67
76
  <S.ButtonWrapper ref={button} data-testid="floating-menu-button">
68
77
  <Button />
69
78
  </S.ButtonWrapper>
79
+
70
80
  {isOpen && (
71
- <S.MenuWrapper isInAppBar={isInAppBar} ref={menuOptions} position={position} offset={offset} data-testid="floating-menu-wrapper">
81
+ <S.MenuWrapper
82
+ isInAppBar={isInAppBar}
83
+ ref={menuOptions}
84
+ position={position}
85
+ offset={offset}
86
+ data-testid="floating-menu-wrapper"
87
+ >
72
88
  <S.Menu>{children}</S.Menu>
73
89
  </S.MenuWrapper>
74
90
  )}
@@ -76,7 +92,7 @@ const FloatingMenu = (props: IProps) => {
76
92
  );
77
93
  };
78
94
 
79
- interface IProps {
95
+ export interface IFloatingProps {
80
96
  children: any;
81
97
  Button: any;
82
98
  isInAppBar?: boolean;
@@ -5,6 +5,7 @@ const Wrapper = styled.div``;
5
5
  const ButtonWrapper = styled.div`
6
6
  display: flex;
7
7
  align-items: center;
8
+ cursor: pointer;
8
9
  `;
9
10
 
10
11
  const MenuWrapper = styled.div<{ isInAppBar?: boolean; position: string; offset?: number }>`
@@ -6,7 +6,7 @@ import { IconAction } from "@ax/components";
6
6
 
7
7
  import * as S from "./style";
8
8
 
9
- const FloatingPanel = (props: IProps): JSX.Element | null => {
9
+ const FloatingPanel = (props: IFloatingPanelProps): JSX.Element | null => {
10
10
  const {
11
11
  children,
12
12
  title,
@@ -40,7 +40,13 @@ const FloatingPanel = (props: IProps): JSX.Element | null => {
40
40
  };
41
41
 
42
42
  return createPortal(
43
- <S.Wrapper ref={node} isOpen={isOpen} isOpenedSecond={isOpenedSecond} secondary={secondary}>
43
+ <S.Wrapper
44
+ data-testid="floating-panel"
45
+ ref={node}
46
+ isOpen={isOpen}
47
+ isOpenedSecond={isOpenedSecond}
48
+ secondary={secondary}
49
+ >
44
50
  <S.Header>
45
51
  <S.Title>{title}</S.Title>
46
52
  {!isOpenedSecond && (
@@ -55,7 +61,7 @@ const FloatingPanel = (props: IProps): JSX.Element | null => {
55
61
  );
56
62
  };
57
63
 
58
- interface IProps {
64
+ export interface IFloatingPanelProps {
59
65
  isOpen: boolean;
60
66
  isOpenedSecond?: boolean;
61
67
  children: JSX.Element[] | JSX.Element | boolean;
@@ -4,7 +4,7 @@ import { Modal } from "@ax/components";
4
4
 
5
5
  import * as S from "./style";
6
6
 
7
- const GuardModal = (props: IProps): JSX.Element => {
7
+ const GuardModal = (props: IGuardModalProps): JSX.Element => {
8
8
  const { isOpen, discardChanges, toggleModal } = props;
9
9
 
10
10
  const mainAction = {
@@ -33,12 +33,12 @@ const GuardModal = (props: IProps): JSX.Element => {
33
33
  mainAction={mainAction}
34
34
  secondaryAction={secondaryAction}
35
35
  >
36
- {<S.ModalContent>{modalText}</S.ModalContent>}
36
+ {<S.ModalContent data-testid='guard-modal-content'>{modalText}</S.ModalContent>}
37
37
  </Modal>
38
38
  );
39
39
  };
40
40
 
41
- interface IProps {
41
+ export interface IGuardModalProps {
42
42
  isOpen: boolean;
43
43
  discardChanges: () => void;
44
44
  toggleModal: () => void;
@@ -7,6 +7,7 @@ const getImage = (name: string) => {
7
7
  return null;
8
8
  }
9
9
  };
10
+
10
11
  const Icon = (props: IProps) => {
11
12
  const name =
12
13
  props.name &&
@@ -28,7 +29,7 @@ const Icon = (props: IProps) => {
28
29
  return null;
29
30
  };
30
31
 
31
- interface IStateProps {
32
+ export interface IStateProps {
32
33
  name: string;
33
34
  size?: string;
34
35
  fill?: string;
@@ -27,11 +27,11 @@ const IconAction = (props: IIconActionProps): JSX.Element => {
27
27
  );
28
28
  };
29
29
 
30
- interface IIconActionProps {
30
+ export interface IIconActionProps {
31
31
  icon: string;
32
32
  disabled?: boolean;
33
- size?: "s" | "m" | undefined;
34
- onClick?: any;
33
+ size?: "s" | "m";
34
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
35
35
  inversed?: boolean;
36
36
  }
37
37
 
@@ -124,7 +124,9 @@ const AppBar = (props: IProps): JSX.Element => {
124
124
 
125
125
  const ErrorCenterBtn = () => (
126
126
  <S.ErrorWrapper>
127
- <Icon name="alert" size="24" />
127
+ <Tooltip content="Error center" bottom>
128
+ <Icon name="alert" size="24" />
129
+ </Tooltip>
128
130
  </S.ErrorWrapper>
129
131
  );
130
132
 
@@ -92,6 +92,7 @@ const LanguageWrapper = styled.div`
92
92
  position: relative;
93
93
  display: flex;
94
94
  align-items: center;
95
+ cursor: pointer;
95
96
  `;
96
97
 
97
98
  const FlagWrapper = styled.div``;
@@ -113,6 +114,7 @@ const Separator = styled.div`
113
114
  const IconStatusWrapper = styled.div`
114
115
  position: relative;
115
116
  height: ${(p) => p.theme.spacing.m};
117
+ cursor: pointer;
116
118
  `;
117
119
 
118
120
  const Header = styled.section<{ inversed?: boolean }>`
@@ -169,6 +171,7 @@ const ErrorWrapper = styled.div`
169
171
  path {
170
172
  fill: ${(p) => p.theme.color.error};
171
173
  }
174
+ cursor: pointer;
172
175
  }
173
176
  `;
174
177
 
@@ -13,7 +13,7 @@ const MenuItem = (props: IMenuItemProps): JSX.Element => {
13
13
  }
14
14
  };
15
15
 
16
- const handleExtendedAction = (e: React.MouseEvent<HTMLLIElement>) => {
16
+ const handleExtendedAction = (e: React.MouseEvent<HTMLButtonElement>) => {
17
17
  e.stopPropagation();
18
18
  extendedAction && extendedAction.action();
19
19
  };
@@ -40,7 +40,7 @@ const Modal = (props: IModalProps): JSX.Element | null => {
40
40
  ? createPortal(
41
41
  <>
42
42
  <S.ModalOverlay />
43
- <S.ModalWrapper>
43
+ <S.ModalWrapper data-testid="modal-wrapper">
44
44
  <S.Modal size={size}>
45
45
  <S.ModalHeader>
46
46
  {titleContent}
@@ -0,0 +1,33 @@
1
+ import React, { useState } from "react";
2
+ import { Icon } from "@ax/components";
3
+
4
+ import * as S from "./style";
5
+
6
+ const SubNotification = (props: ISubNotificationProps): JSX.Element => {
7
+ const { text } = props;
8
+
9
+ const [isVisible, setIsVisible] = useState(true);
10
+
11
+ const handleClose = () => {
12
+ setIsVisible(false);
13
+ };
14
+
15
+ return (
16
+ <S.Wrapper isVisible={isVisible} data-testid="subnotification-wrapper">
17
+ <S.Row>
18
+ <S.Text>{text}</S.Text>
19
+ <S.ActionsWrapper>
20
+ <S.CloseWrapper onClick={handleClose} data-testid="subnotification-close-button">
21
+ <Icon name="close" size="16" />
22
+ </S.CloseWrapper>
23
+ </S.ActionsWrapper>
24
+ </S.Row>
25
+ </S.Wrapper>
26
+ );
27
+ };
28
+
29
+ export interface ISubNotificationProps {
30
+ text: string;
31
+ }
32
+
33
+ export default SubNotification;