@griddo/ax 1.69.7 → 1.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/config/jest/componentsMock.js +0 -26
  2. package/package.json +4 -3
  3. package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +97 -0
  4. package/src/__tests__/components/EmptyState/EmptyState.test.tsx +78 -0
  5. package/src/__tests__/components/Fields/AnalyticsField/PageAnalytics/PageAnalytics.test.tsx +0 -14
  6. package/src/__tests__/components/Fields/AnalyticsField/StructuredDataAnalytics/StructuredDataAnalytics.test.tsx +0 -15
  7. package/src/__tests__/components/Fields/ArrayFieldGroup/ArrayFieldGroup.test.tsx +6 -15
  8. package/src/__tests__/components/Fields/AsyncCheckGroup/AsyncCheckGroup.test.tsx +1 -13
  9. package/src/__tests__/components/Fields/AsyncSelect/AsyncSelect.test.tsx +1 -19
  10. package/src/__tests__/components/Fields/ColorPicker/ColorPicker.test.tsx +1 -10
  11. package/src/__tests__/components/Fields/ComponentArray/ComponentArray.test.tsx +1 -22
  12. package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/MixableComponentArray.test.tsx +4 -24
  13. package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/PasteModuleButton.test.tsx +6 -12
  14. package/src/__tests__/components/Fields/ComponentArray/SameComponentArray/SameComponentArray.test.tsx +1 -20
  15. package/src/__tests__/components/Fields/ComponentContainer/ComponentContainer.test.tsx +559 -0
  16. package/src/__tests__/components/Fields/HiddenField/HiddenField.test.tsx +1 -7
  17. package/src/__tests__/components/Fields/ImageField/ImageField.test.tsx +471 -0
  18. package/src/__tests__/components/Fields/MultiCheckSelect/MultiCheckSelect.test.tsx +1 -15
  19. package/src/__tests__/components/Fields/NoteField/NoteField.test.tsx +1 -6
  20. package/src/__tests__/components/Fields/NumberField/NumberField.test.tsx +1 -14
  21. package/src/__tests__/components/Fields/RadioField/RadioField.test.tsx +1 -11
  22. package/src/__tests__/components/Fields/ReferenceField/ReferenceField.test.tsx +77 -13
  23. package/src/__tests__/components/Fields/RichText/RichText.test.tsx +1 -12
  24. package/src/__tests__/components/Fields/Select/Select.test.tsx +1 -21
  25. package/src/__tests__/components/Fields/SliderField/SliderField.test.tsx +1 -14
  26. package/src/__tests__/components/Fields/TagField/TagField.test.tsx +3 -3
  27. package/src/__tests__/components/Fields/TimeField/HourInput/HourInput.test.tsx +142 -0
  28. package/src/__tests__/components/Fields/TimeField/TimeField.test.tsx +100 -0
  29. package/src/__tests__/components/Fields/ToggleField/ToggleField.test.tsx +1 -9
  30. package/src/__tests__/components/Fields/Tooltip/Tooltip.test.tsx +151 -0
  31. package/src/__tests__/components/Fields/VisualUniqueSelection/ImageSelection/ImageSelection.test.tsx +1 -13
  32. package/src/__tests__/components/Fields/VisualUniqueSelection/ScrollableSelection/ScrollableSelection.test.tsx +3 -17
  33. package/src/__tests__/components/Fields/VisualUniqueSelection/VisualUniqueSelection.test.tsx +2 -28
  34. package/src/__tests__/components/TableList/TableList.test.tsx +119 -0
  35. package/src/__tests__/components/Tabs/Tabs.test.tsx +202 -0
  36. package/src/__tests__/components/Tag/Tag.test.tsx +138 -0
  37. package/src/__tests__/components/Toast/Toast.test.tsx +100 -0
  38. package/src/api/navigation.tsx +1 -1
  39. package/src/components/Browser/index.tsx +1 -1
  40. package/src/components/Button/index.tsx +3 -3
  41. package/src/components/ConfigPanel/NavigationForm/Field/index.tsx +14 -3
  42. package/src/components/ElementsTooltip/index.tsx +10 -9
  43. package/src/components/EmptyState/index.tsx +2 -2
  44. package/src/components/Fields/ArrayFieldGroup/index.tsx +1 -1
  45. package/src/components/Fields/AsyncCheckGroup/index.tsx +1 -1
  46. package/src/components/Fields/AsyncSelect/index.tsx +1 -1
  47. package/src/components/Fields/ComponentContainer/index.tsx +7 -6
  48. package/src/components/Fields/ComponentContainer/style.tsx +2 -2
  49. package/src/components/Fields/HiddenField/index.tsx +1 -1
  50. package/src/components/Fields/ImageField/index.tsx +10 -5
  51. package/src/components/Fields/MultiCheckSelect/index.tsx +3 -3
  52. package/src/components/Fields/NumberField/index.tsx +2 -1
  53. package/src/components/Fields/ReferenceField/ItemList/Item/index.tsx +5 -7
  54. package/src/components/Fields/ReferenceField/ItemList/Item/style.tsx +2 -2
  55. package/src/components/Fields/ReferenceField/ItemList/index.tsx +1 -1
  56. package/src/components/Fields/RichText/index.tsx +10 -6
  57. package/src/components/Fields/Select/index.tsx +1 -1
  58. package/src/components/Fields/SliderField/index.tsx +1 -1
  59. package/src/components/Fields/TimeField/HourInput/index.tsx +103 -0
  60. package/src/components/Fields/TimeField/HourInput/style.tsx +19 -0
  61. package/src/components/Fields/TimeField/HourInput/utils.tsx +35 -0
  62. package/src/components/Fields/TimeField/index.tsx +57 -0
  63. package/src/components/Fields/TimeField/style.tsx +37 -0
  64. package/src/components/Fields/index.tsx +2 -0
  65. package/src/components/FloatingMenu/index.tsx +1 -1
  66. package/src/components/Gallery/GalleryFilters/Type/index.tsx +50 -0
  67. package/src/components/Gallery/GalleryFilters/Type/style.tsx +39 -0
  68. package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +2 -1
  69. package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/style.tsx +3 -3
  70. package/src/components/Gallery/hooks.tsx +10 -4
  71. package/src/components/Gallery/index.tsx +2 -0
  72. package/src/components/Icon/index.tsx +1 -1
  73. package/src/components/Loading/index.tsx +1 -1
  74. package/src/components/Pagination/index.tsx +1 -1
  75. package/src/components/SideModal/SideModalOption/index.tsx +4 -2
  76. package/src/components/SideModal/index.tsx +1 -1
  77. package/src/components/TableList/index.tsx +6 -6
  78. package/src/components/TableList/style.tsx +1 -1
  79. package/src/components/Tabs/index.tsx +19 -7
  80. package/src/components/Tag/index.tsx +6 -6
  81. package/src/components/Toast/index.tsx +4 -4
  82. package/src/components/Tooltip/index.tsx +5 -3
  83. package/src/components/index.tsx +2 -0
  84. package/src/containers/Navigation/Defaults/actions.tsx +10 -5
  85. package/src/containers/Navigation/Defaults/utils.tsx +13 -4
  86. package/src/containers/Sites/actions.tsx +7 -0
  87. package/src/containers/Sites/constants.tsx +1 -0
  88. package/src/containers/Sites/interfaces.tsx +6 -0
  89. package/src/containers/Sites/reducer.tsx +4 -0
  90. package/src/containers/StructuredData/actions.tsx +21 -8
  91. package/src/containers/StructuredData/constants.tsx +2 -0
  92. package/src/containers/StructuredData/interfaces.tsx +7 -1
  93. package/src/containers/StructuredData/reducer.tsx +5 -1
  94. package/src/helpers/fields.tsx +2 -2
  95. package/src/helpers/schemas.tsx +2 -2
  96. package/src/hooks/forms.tsx +2 -1
  97. package/src/modules/App/Routing/NavMenu/index.tsx +9 -1
  98. package/src/modules/Content/BulkHeader/TableHeader/index.tsx +1 -1
  99. package/src/modules/Content/hooks.tsx +19 -12
  100. package/src/modules/Content/index.tsx +23 -14
  101. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +3 -0
  102. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/index.tsx +3 -1
  103. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +16 -18
  104. package/src/modules/Navigation/Defaults/DefaultsEditor/utils.tsx +37 -0
  105. package/src/modules/StructuredData/Form/ConnectedField/index.tsx +3 -2
  106. package/src/modules/StructuredData/Form/index.tsx +22 -17
  107. package/src/modules/StructuredData/StructuredDataList/hooks.tsx +30 -20
  108. package/src/modules/StructuredData/StructuredDataList/index.tsx +24 -14
  109. package/src/types/index.tsx +8 -7
@@ -36,7 +36,7 @@ const Button = ({ children, type, disabled, icon, buttonStyle, onClick, classNam
36
36
  case buttonStyles.TEXT:
37
37
  return (
38
38
  <S.TextButton
39
- data-testid="buttonText"
39
+ data-testid="button-text"
40
40
  className={className}
41
41
  type={type}
42
42
  disabled={disabled}
@@ -49,7 +49,7 @@ const Button = ({ children, type, disabled, icon, buttonStyle, onClick, classNam
49
49
  case buttonStyles.INVERSE:
50
50
  return (
51
51
  <S.LineButton
52
- data-testid="buttonLineInverse"
52
+ data-testid="button-line-inverse"
53
53
  className={className}
54
54
  type={type}
55
55
  disabled={disabled}
@@ -62,7 +62,7 @@ const Button = ({ children, type, disabled, icon, buttonStyle, onClick, classNam
62
62
  default:
63
63
  return (
64
64
  <S.Button
65
- data-testid="buttonDefault"
65
+ data-testid="button-default"
66
66
  className={className}
67
67
  type={type}
68
68
  disabled={disabled}
@@ -12,9 +12,20 @@ const pageEditorID = 0;
12
12
  const Field = (props: IProps) => {
13
13
  const { type, defaults, updateEditorContent, selectedContent, theme } = props;
14
14
  const { isOpen, toggleModal } = useModal();
15
+ const isDefault = selectedContent.setAsDefault;
16
+
17
+ const options = defaults.filter((component: any) => {
18
+ const isCurrentType = component.type === type;
19
+ const isNotSelected = component.id !== selectedContent.id;
20
+ return isCurrentType && isNotSelected;
21
+ });
22
+
23
+ const hasMultipleOptions: boolean | undefined = options && options.length > 0;
24
+ let filteredByDefaultOptions = options;
25
+ if (!isDefault && hasMultipleOptions) {
26
+ filteredByDefaultOptions = options.filter((component: any) => !component.setAsDefault);
27
+ }
15
28
 
16
- const options = defaults.filter((component: any) => component.type === type);
17
- const hasMultipleOptions: boolean | undefined = options && options.length > 1;
18
29
  const optionsType = `${type}s`;
19
30
 
20
31
  const actionMenuOptions = [
@@ -52,7 +63,7 @@ const Field = (props: IProps) => {
52
63
  {hasMultipleOptions && (
53
64
  <SideModal
54
65
  optionsType={optionsType}
55
- whiteList={options}
66
+ whiteList={filteredByDefaultOptions}
56
67
  handleClick={handleReplace}
57
68
  toggleModal={toggleModal}
58
69
  isOpen={isOpen}
@@ -3,12 +3,12 @@ import { trimText } from "@ax/helpers";
3
3
 
4
4
  import * as S from "./style";
5
5
 
6
- const ElementsTooltip = (props: IProps): JSX.Element => {
6
+ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
7
7
  const { elements, maxChar, defaultElements = 1, colors, rounded = false, elementsPerRow = 1 } = props;
8
8
 
9
9
  if (!elements) return <></>;
10
10
 
11
- const visibleElements = elements?.slice(0, defaultElements);
11
+ const visibleElements = elements.slice(0, defaultElements);
12
12
  const remainingElements = elements.length - defaultElements;
13
13
 
14
14
  const elementsRows: string[][] = [];
@@ -16,30 +16,31 @@ const ElementsTooltip = (props: IProps): JSX.Element => {
16
16
  const row = Math.floor(idx / elementsPerRow);
17
17
  const isNewRow = idx % elementsPerRow === 0;
18
18
  elementsRows[row] = isNewRow ? [element] : [...elementsRows[row], element];
19
- })
19
+ });
20
20
 
21
21
  return (
22
- <S.Wrapper>
22
+ <S.Wrapper data-testid="elements-wrapper">
23
23
  {visibleElements.map((fullElement, idx) => {
24
24
  const element = defaultElements === 1 && maxChar ? trimText(fullElement, maxChar) : fullElement;
25
25
  const color = colors && colors[element] ? colors[element] : undefined;
26
+
26
27
  return (
27
- <S.Element key={idx} color={color} rounded={rounded}>
28
+ <S.Element data-testid="element" key={idx} color={color} rounded={rounded}>
28
29
  {element}
29
30
  </S.Element>
30
31
  );
31
32
  })}
32
33
  {remainingElements > 0 && (
33
- <S.Element rounded={rounded}>
34
+ <S.Element data-testid="remaining-element" rounded={rounded}>
34
35
  +{remainingElements}
35
36
  <S.Tooltip>
36
37
  {elementsRows.map((row, idx) => {
37
38
  return (
38
- <S.Row key={idx}>
39
+ <S.Row data-testid="row-element" key={idx}>
39
40
  {row.map((element, idx) => {
40
41
  const color = colors && colors[element] ? colors[element] : undefined;
41
42
  return (
42
- <S.Element key={idx} color={color} rounded={rounded}>
43
+ <S.Element data-testid="div-element" key={idx} color={color} rounded={rounded}>
43
44
  {element}
44
45
  </S.Element>
45
46
  );
@@ -54,7 +55,7 @@ const ElementsTooltip = (props: IProps): JSX.Element => {
54
55
  );
55
56
  };
56
57
 
57
- interface IProps {
58
+ export interface IElementsTooltipProps {
58
59
  elements?: string[];
59
60
  defaultElements?: number;
60
61
  maxChar?: number;
@@ -8,8 +8,8 @@ const EmptyState = (props: IEmptyStateProps) => {
8
8
  const { icon, title, message, button, action } = props;
9
9
 
10
10
  return (
11
- <S.Wrapper>
12
- <S.IconWrapper>
11
+ <S.Wrapper data-testid="wrapper">
12
+ <S.IconWrapper data-testid="icon-wrapper">
13
13
  <Icon name={icon || "empty"} size="26" />
14
14
  </S.IconWrapper>
15
15
  <S.Title>{title || "Oh! This Looks so empty"}</S.Title>
@@ -84,7 +84,7 @@ const ArrayFieldGroup = (props: IProps): JSX.Element => {
84
84
  );
85
85
  };
86
86
 
87
- interface IProps {
87
+ export interface IProps {
88
88
  value: Record<string, unknown>[];
89
89
  name: string;
90
90
  onChange: (value: Record<string, unknown>[]) => void;
@@ -78,7 +78,7 @@ const AsyncCheckGroup = (props: IAsyncCheckGroup): JSX.Element => {
78
78
  return <S.FieldGroup full={fullHeight}>{checks}</S.FieldGroup>;
79
79
  };
80
80
 
81
- interface IAsyncCheckGroup {
81
+ export interface IAsyncCheckGroup {
82
82
  value: any[] | null;
83
83
  site?: ISite | null;
84
84
  source: string;
@@ -152,7 +152,7 @@ interface ICheck {
152
152
  name: string;
153
153
  title: string;
154
154
  }
155
- interface IAsyncSelectProps {
155
+ export interface IAsyncSelectProps {
156
156
  name: string;
157
157
  value: any;
158
158
  entity?: string;
@@ -57,9 +57,9 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
57
57
  let componentID: number = editorID ? editorID : containerOptions.editorID;
58
58
 
59
59
  const containerInfo: any = !isArray && value && isEmptyContainer(value, hasMultipleOptions);
60
- const isEmpty: boolean = containerInfo.isEmpty;
60
+ const isEmpty: boolean = containerInfo?.isEmpty;
61
61
 
62
- const currentComponent: any = containerInfo.containedComponent;
62
+ const currentComponent: any = containerInfo?.containedComponent;
63
63
 
64
64
  if (currentComponent) {
65
65
  containerText = currentComponent.containerText;
@@ -152,17 +152,18 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
152
152
  className={`editorId-${editorID}`}
153
153
  onClick={handleClick}
154
154
  ref={innerRef}
155
+ data-testid="component-container"
155
156
  {...provided?.draggableProps}
156
157
  >
157
- {isArray && provided && arrayLength > 1 && (
158
- <S.HandleWrapper {...provided?.dragHandleProps}>
158
+ {isArray && provided && (
159
+ <S.HandleWrapper {...provided?.dragHandleProps} hidden={arrayLength < 2} data-testid="handle-wrapper">
159
160
  <S.IconHandleWrapper>
160
161
  <Icon name="drag" size="16" />
161
162
  </S.IconHandleWrapper>
162
163
  </S.HandleWrapper>
163
164
  )}
164
165
  {containerInfo && !disabled && (
165
- <S.IconWrapper>
166
+ <S.IconWrapper data-testid="icon-wrapper">
166
167
  <Icon name={containerText} />
167
168
  </S.IconWrapper>
168
169
  )}
@@ -195,7 +196,7 @@ const ComponentContainer = (props: IComponentContainerProps): JSX.Element => {
195
196
  );
196
197
  };
197
198
 
198
- interface IComponentContainerProps {
199
+ export interface IComponentContainerProps {
199
200
  text: string;
200
201
  editorID: number;
201
202
  whiteList: any[] | undefined;
@@ -76,8 +76,8 @@ const ButtonsWrapper = styled.span`
76
76
  margin-left: auto;
77
77
  `;
78
78
 
79
- const HandleWrapper = styled.div`
80
- display: flex;
79
+ const HandleWrapper = styled.div<{ hidden?: boolean }>`
80
+ display: ${(p) => (p.hidden ? "none" : "flex")};
81
81
  align-items: center;
82
82
  `;
83
83
 
@@ -19,7 +19,7 @@ const HiddenField = ({ title, showField, hasMultipleOptions }: IHiddenFieldProps
19
19
  );
20
20
  };
21
21
 
22
- interface IHiddenFieldProps {
22
+ export interface IHiddenFieldProps {
23
23
  title: string;
24
24
  showField: any;
25
25
  hasMultipleOptions?: boolean;
@@ -65,17 +65,22 @@ const ImageField = (props: IImageFieldProps) => {
65
65
  };
66
66
 
67
67
  const previewHeight = cropPreview ? { height: 190 } : {};
68
-
69
68
  return (
70
69
  <>
71
- <S.FieldWrapper error={error} preview={!!previewSrc} disabled={disabled} onClick={handleClick}>
70
+ <S.FieldWrapper
71
+ data-testid="fieldWrapper"
72
+ error={error}
73
+ preview={!!previewSrc}
74
+ disabled={disabled}
75
+ onClick={handleClick}
76
+ >
72
77
  <S.IconWrapper>
73
78
  <Icon name="image" size="18" />
74
79
  </S.IconWrapper>
75
80
  <S.Text>Add image</S.Text>
76
81
  </S.FieldWrapper>
77
82
  {value && Object.keys(value).length > 1 && typeof value !== "string" && (
78
- <S.ImageDataWrapper>
83
+ <S.ImageDataWrapper data-testid="imageDataWrapper">
79
84
  <S.FileName>{value.name}</S.FileName>
80
85
  <S.ImageData>
81
86
  Uploaded: {!!value.published && getFormattedDateWithTimezone(value.published, "d MMM Y")}
@@ -84,7 +89,7 @@ const ImageField = (props: IImageFieldProps) => {
84
89
  </S.ImageData>
85
90
  </S.ImageDataWrapper>
86
91
  )}
87
- <S.Preview preview={!!previewSrc}>
92
+ <S.Preview preview={!!previewSrc} data-testid="previewDiv">
88
93
  {previewSrc && <Image url={previewSrc} width={320} {...previewHeight} />}
89
94
  <S.PreviewActions>
90
95
  <IconAction icon="change" onClick={handleChange} />
@@ -98,7 +103,7 @@ const ImageField = (props: IImageFieldProps) => {
98
103
  );
99
104
  };
100
105
 
101
- interface IImageFieldProps {
106
+ export interface IImageFieldProps {
102
107
  value: IImage | null | string;
103
108
  error?: boolean;
104
109
  onChange: (value: any) => void;
@@ -6,7 +6,7 @@ import CheckGroup from "@ax/components/Fields/CheckGroup";
6
6
 
7
7
  import * as S from "./style";
8
8
 
9
- const MultiCheckSelect = (props: IProps) => {
9
+ const MultiCheckSelect = (props: IMultiCheckSelectProps) => {
10
10
  const { placeholder, source, value, onChange, site, className, mandatory, options, selectAllOption, floating } =
11
11
  props;
12
12
 
@@ -35,7 +35,7 @@ const MultiCheckSelect = (props: IProps) => {
35
35
  );
36
36
  };
37
37
 
38
- interface IProps {
38
+ export interface IMultiCheckSelectProps {
39
39
  value: (string | number)[];
40
40
  onChange: (value: string | (string | number)[]) => void;
41
41
  site?: ISite | null;
@@ -43,7 +43,7 @@ interface IProps {
43
43
  source?: string;
44
44
  className?: string;
45
45
  mandatory?: boolean;
46
- options?: { name: string, value: string, title: string }[];
46
+ options?: { name: string; value: string; title: string }[];
47
47
  selectAllOption?: string;
48
48
  floating?: boolean;
49
49
  }
@@ -37,6 +37,7 @@ const NumberField = (props: INumberFieldProps): JSX.Element => {
37
37
 
38
38
  setInputValue(updatedValue.toString());
39
39
  onChange(updatedValue);
40
+ handleValidation && handleValidation(updatedValue.toString(), validators);
40
41
  };
41
42
 
42
43
  let validators: Record<string, unknown> = maxValue ? { maxValue } : {};
@@ -81,7 +82,7 @@ const NumberField = (props: INumberFieldProps): JSX.Element => {
81
82
  );
82
83
  };
83
84
 
84
- interface INumberFieldProps {
85
+ export interface INumberFieldProps {
85
86
  value?: number;
86
87
  onChange: (value: number) => void;
87
88
  name?: string;
@@ -46,13 +46,11 @@ const Item = (props: IProps) => {
46
46
 
47
47
  return (
48
48
  <S.Item ref={innerRef} data-testid="reference-field-item" {...provided?.draggableProps}>
49
- {listLength > 1 && (
50
- <S.HandleWrapper {...provided?.dragHandleProps}>
51
- <S.IconHandleWrapper>
52
- <Icon name="drag" size="16" />
53
- </S.IconHandleWrapper>
54
- </S.HandleWrapper>
55
- )}
49
+ <S.HandleWrapper {...provided?.dragHandleProps} hidden={listLength < 2} data-testid="handle-wrapper">
50
+ <S.IconHandleWrapper>
51
+ <Icon name="drag" size="16" />
52
+ </S.IconHandleWrapper>
53
+ </S.HandleWrapper>
56
54
  <S.TextWrapper>
57
55
  <S.Header>
58
56
  <S.Type>{source?.title.toUpperCase()}</S.Type>
@@ -108,8 +108,8 @@ const ButtonsWrapper = styled.span`
108
108
  right: ${(p) => p.theme.spacing.xs};
109
109
  `;
110
110
 
111
- const HandleWrapper = styled.div`
112
- display: flex;
111
+ const HandleWrapper = styled.div<{ hidden?: boolean }>`
112
+ display: ${(p) => (p.hidden ? "none" : "flex")};
113
113
  align-items: center;
114
114
  padding-right: ${(p) => p.theme.spacing.xxs};
115
115
  `;
@@ -98,7 +98,7 @@ const ItemList = (props: IProps) => {
98
98
  <DragDropContext onDragEnd={onDragEnd}>
99
99
  <Droppable droppableId="referenceList">
100
100
  {(provided) => (
101
- <div ref={provided.innerRef} {...provided.droppableProps}>
101
+ <div ref={provided.innerRef} {...provided.droppableProps} data-testid="droppable">
102
102
  <ComponentList components={selectedItems} />
103
103
  {provided.placeholder}
104
104
  </div>
@@ -10,7 +10,7 @@ import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
10
10
  import * as S from "./style";
11
11
 
12
12
  const RichText = (props: IRichTextProps): JSX.Element => {
13
- const { value, error, placeholder, onChange, disabled, editorID, handleValidation, html = false } = props;
13
+ const { value, error, placeholder, onChange, disabled, editorID, handleValidation, html = false, delayed } = props;
14
14
 
15
15
  const rawData = html ? htmlToDraft(value || "") : markdownToDraft(value);
16
16
  const { contentBlocks, entityMap } = rawData;
@@ -28,15 +28,18 @@ const RichText = (props: IRichTextProps): JSX.Element => {
28
28
  }, [editorID]);
29
29
 
30
30
  useEffect(() => {
31
- timeOutRef.current && clearTimeout(timeOutRef.current);
32
- timeOutRef.current = setTimeout(() => {
33
- delayedChange();
34
- }, 300);
31
+ if (delayed !== false) {
32
+ timeOutRef.current && clearTimeout(timeOutRef.current);
33
+ timeOutRef.current = setTimeout(() => {
34
+ delayedChange();
35
+ }, 300);
36
+ }
35
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
36
38
  }, [editorState]);
37
39
 
38
40
  const handleOnChange = (editorState: EditorState) => {
39
41
  setEditorState(editorState);
42
+ delayed === false && delayedChange();
40
43
  };
41
44
 
42
45
  const delayedChange = () => {
@@ -106,7 +109,7 @@ const RichText = (props: IRichTextProps): JSX.Element => {
106
109
  );
107
110
  };
108
111
 
109
- interface IRichTextProps {
112
+ export interface IRichTextProps {
110
113
  editorID: number;
111
114
  value: string;
112
115
  error?: boolean;
@@ -115,6 +118,7 @@ interface IRichTextProps {
115
118
  onChange: (value: string) => void;
116
119
  handleValidation?: (value: string) => void;
117
120
  html: boolean;
121
+ delayed?: boolean;
118
122
  }
119
123
 
120
124
  export default memo(RichText);
@@ -69,7 +69,7 @@ const Select = (props: ISelectProps): JSX.Element => {
69
69
  );
70
70
  };
71
71
 
72
- interface ISelectProps {
72
+ export interface ISelectProps {
73
73
  name: string;
74
74
  value: string;
75
75
  options: IOptionProps[];
@@ -41,7 +41,7 @@ const SliderField = (props: ITextFieldProps): JSX.Element => {
41
41
  );
42
42
  };
43
43
 
44
- interface ITextFieldProps {
44
+ export interface ITextFieldProps {
45
45
  title: string;
46
46
  value: number;
47
47
  defaultValue?: number;
@@ -0,0 +1,103 @@
1
+ import React, { memo, useEffect, useRef, useState } from "react";
2
+ import { isNumber } from "@ax/helpers";
3
+ import { validateTimeAndCursor } from "./utils";
4
+
5
+ import * as S from "./style";
6
+
7
+ const HourInput = (props: IProps) => {
8
+ const { value, onChange, disabled } = props;
9
+
10
+ const [cursorPosition, setCursorPosition] = useState(0);
11
+ const [count, setCount] = useState(0);
12
+ const inputRef = useRef<HTMLInputElement>(null);
13
+
14
+ useEffect(() => {
15
+ inputRef.current?.setSelectionRange(cursorPosition, cursorPosition);
16
+ }, [count, cursorPosition]);
17
+
18
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
19
+ const oldValue = value;
20
+ const inputEl = e.target;
21
+ const inputValue = inputEl.value;
22
+ const position = inputEl.selectionEnd || 0;
23
+ const isTyped = inputValue.length > oldValue.length;
24
+ const cursorCharacter = inputValue[position - 1];
25
+ const addedCharacter = isTyped ? cursorCharacter : null;
26
+ const removedCharacter = isTyped ? null : oldValue[position];
27
+ const replacedSingleCharacter = inputValue.length === oldValue.length ? oldValue[position - 1] : null;
28
+ const colon = ":";
29
+
30
+ let newValue = oldValue;
31
+ let newPosition = position;
32
+
33
+ if (addedCharacter !== null) {
34
+ if (position > 5) {
35
+ newPosition = 5;
36
+ } else if (position === 3 && addedCharacter === colon) {
37
+ newValue = `${inputValue.slice(0, position - 1)}${colon}${inputValue.slice(position + 1)}`;
38
+ } else if (position === 3 && isNumber(addedCharacter)) {
39
+ newValue = `${inputValue.slice(0, position - 1)}${colon}${addedCharacter}${inputValue.slice(position + 2)}`;
40
+ newPosition = position + 1;
41
+ } else if (isNumber(addedCharacter)) {
42
+ // user typed a number
43
+ newValue = inputValue.slice(0, position - 1) + addedCharacter + inputValue.slice(position + 1);
44
+ if (position === 2) {
45
+ newPosition = position + 1;
46
+ }
47
+ } else {
48
+ // if user typed NOT a number, then keep old value & position
49
+ newPosition = position - 1;
50
+ }
51
+ } else if (replacedSingleCharacter !== null) {
52
+ // user replaced only a single character
53
+ if (isNumber(cursorCharacter)) {
54
+ if (position - 1 === 2) {
55
+ newValue = `${inputValue.slice(0, position - 1)}${colon}${inputValue.slice(position)}`;
56
+ } else {
57
+ newValue = inputValue;
58
+ }
59
+ } else {
60
+ // user replaced a number on some non-number character
61
+ newValue = oldValue;
62
+ newPosition = position - 1;
63
+ }
64
+ } else if (typeof cursorCharacter !== "undefined" && cursorCharacter !== colon && !isNumber(cursorCharacter)) {
65
+ // set of characters replaced by non-number
66
+ newValue = oldValue;
67
+ newPosition = position - 1;
68
+ } else if (removedCharacter !== null) {
69
+ if (position === 2 && removedCharacter === colon) {
70
+ newValue = `${inputValue.slice(0, position - 1)}0${colon}${inputValue.slice(position)}`;
71
+ newPosition = position - 1;
72
+ } else {
73
+ // user removed a number
74
+ newValue = `${inputValue.slice(0, position)}0${inputValue.slice(position)}`;
75
+ }
76
+ }
77
+
78
+ const [validatedTime, validatedCursorPosition] = validateTimeAndCursor(newValue, oldValue, newPosition);
79
+
80
+ setCursorPosition(validatedCursorPosition);
81
+ setCount(count + 1);
82
+ onChange(validatedTime);
83
+ };
84
+
85
+ return (
86
+ <S.InputField
87
+ type="text"
88
+ value={value}
89
+ onChange={handleChange}
90
+ ref={inputRef}
91
+ disabled={disabled}
92
+ data-testid="time-field-input"
93
+ />
94
+ );
95
+ };
96
+
97
+ interface IProps {
98
+ value: string;
99
+ onChange: (value: string) => void;
100
+ disabled?: boolean;
101
+ }
102
+
103
+ export default memo(HourInput);
@@ -0,0 +1,19 @@
1
+ import styled from "styled-components";
2
+
3
+ const InputField = styled.input`
4
+ ${(p) => p.theme.textStyle.fieldContent};
5
+ color: ${(p) => p.theme.color.textMediumEmphasis};
6
+ width: 50px;
7
+ border: none;
8
+
9
+ &:active,
10
+ &:focus {
11
+ outline: none;
12
+ }
13
+
14
+ &:disabled {
15
+ color: ${(p) => p.theme.color.interactiveDisabled};
16
+ }
17
+ `;
18
+
19
+ export { InputField };
@@ -0,0 +1,35 @@
1
+ const formatTimeItem = (value?: string | number): string => {
2
+ return `${value || ""}00`.substr(0, 2);
3
+ };
4
+
5
+ const validateTimeAndCursor = (value = "", defaultValue = "", cursorPosition = 0): [string, number] => {
6
+ const [oldH, oldM] = defaultValue.split(":");
7
+
8
+ let newCursorPosition = Number(cursorPosition);
9
+ let [newH, newM] = String(value).split(":");
10
+
11
+ newH = formatTimeItem(newH);
12
+ if (Number(newH[0]) > 1) {
13
+ newH = oldH;
14
+ newCursorPosition -= 1;
15
+ } else if (Number(newH[0]) === 1) {
16
+ if (Number(oldH[0]) === 1 && Number(newH[1]) > 2) {
17
+ newH = `1${oldH[1]}`;
18
+ newCursorPosition -= 2;
19
+ } else if (Number(newH[1]) > 2) {
20
+ newH = "12";
21
+ }
22
+ }
23
+
24
+ newM = formatTimeItem(newM);
25
+ if (Number(newM[0]) > 5) {
26
+ newM = oldM;
27
+ newCursorPosition -= 1;
28
+ }
29
+
30
+ const validatedValue = `${newH}:${newM}`;
31
+
32
+ return [validatedValue, newCursorPosition];
33
+ };
34
+
35
+ export { validateTimeAndCursor };
@@ -0,0 +1,57 @@
1
+ import React, { memo, useState } from "react";
2
+ import { Select, Icon } from "@ax/components";
3
+ import HourInput from "./HourInput";
4
+
5
+ import * as S from "./style";
6
+
7
+ const TimeField = (props: IProps) => {
8
+ const { value, error, onChange, disabled } = props;
9
+
10
+ const safeValue = !value || !value.length ? "00:00 am" : value;
11
+ const splitValue = safeValue.split(" ");
12
+ const initValue = { time: splitValue[0], meridian: splitValue[1] };
13
+ const [state, setState] = useState(initValue);
14
+
15
+ const handleChange = (newValue: string) => {
16
+ setState({ ...state, time: newValue });
17
+ onChange(`${newValue} ${state.meridian}`);
18
+ };
19
+
20
+ const handleSelectChange = (newValue: string) => {
21
+ setState({ ...state, meridian: newValue });
22
+ onChange(`${state.time} ${newValue}`);
23
+ };
24
+
25
+ const selectOptions = [
26
+ { value: "am", label: "AM" },
27
+ { value: "pm", label: "PM" },
28
+ ];
29
+
30
+ return (
31
+ <S.FieldWrapper error={error} data-testid="time-field-wrapper" disabled={disabled}>
32
+ <HourInput value={state.time} onChange={handleChange} disabled={disabled} />
33
+ <S.SelectWrapper>
34
+ <Select
35
+ name="timeSelect"
36
+ value={state.meridian}
37
+ options={selectOptions}
38
+ onChange={handleSelectChange}
39
+ type="inline"
40
+ disabled={disabled}
41
+ />
42
+ </S.SelectWrapper>
43
+ <S.IconWrapper>
44
+ <Icon name="scheduled" size="24" />
45
+ </S.IconWrapper>
46
+ </S.FieldWrapper>
47
+ );
48
+ };
49
+
50
+ interface IProps {
51
+ value?: string;
52
+ error?: boolean;
53
+ onChange: (value: string) => void;
54
+ disabled?: boolean;
55
+ }
56
+
57
+ export default memo(TimeField);