@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.
- package/config/jest/componentsMock.js +0 -26
- package/package.json +4 -3
- package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +97 -0
- package/src/__tests__/components/EmptyState/EmptyState.test.tsx +78 -0
- package/src/__tests__/components/Fields/AnalyticsField/PageAnalytics/PageAnalytics.test.tsx +0 -14
- package/src/__tests__/components/Fields/AnalyticsField/StructuredDataAnalytics/StructuredDataAnalytics.test.tsx +0 -15
- package/src/__tests__/components/Fields/ArrayFieldGroup/ArrayFieldGroup.test.tsx +6 -15
- package/src/__tests__/components/Fields/AsyncCheckGroup/AsyncCheckGroup.test.tsx +1 -13
- package/src/__tests__/components/Fields/AsyncSelect/AsyncSelect.test.tsx +1 -19
- package/src/__tests__/components/Fields/ColorPicker/ColorPicker.test.tsx +1 -10
- package/src/__tests__/components/Fields/ComponentArray/ComponentArray.test.tsx +1 -22
- package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/MixableComponentArray.test.tsx +4 -24
- package/src/__tests__/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/PasteModuleButton.test.tsx +6 -12
- package/src/__tests__/components/Fields/ComponentArray/SameComponentArray/SameComponentArray.test.tsx +1 -20
- package/src/__tests__/components/Fields/ComponentContainer/ComponentContainer.test.tsx +559 -0
- package/src/__tests__/components/Fields/HiddenField/HiddenField.test.tsx +1 -7
- package/src/__tests__/components/Fields/ImageField/ImageField.test.tsx +471 -0
- package/src/__tests__/components/Fields/MultiCheckSelect/MultiCheckSelect.test.tsx +1 -15
- package/src/__tests__/components/Fields/NoteField/NoteField.test.tsx +1 -6
- package/src/__tests__/components/Fields/NumberField/NumberField.test.tsx +1 -14
- package/src/__tests__/components/Fields/RadioField/RadioField.test.tsx +1 -11
- package/src/__tests__/components/Fields/ReferenceField/ReferenceField.test.tsx +77 -13
- package/src/__tests__/components/Fields/RichText/RichText.test.tsx +1 -12
- package/src/__tests__/components/Fields/Select/Select.test.tsx +1 -21
- package/src/__tests__/components/Fields/SliderField/SliderField.test.tsx +1 -14
- package/src/__tests__/components/Fields/TagField/TagField.test.tsx +3 -3
- package/src/__tests__/components/Fields/TimeField/HourInput/HourInput.test.tsx +142 -0
- package/src/__tests__/components/Fields/TimeField/TimeField.test.tsx +100 -0
- package/src/__tests__/components/Fields/ToggleField/ToggleField.test.tsx +1 -9
- package/src/__tests__/components/Fields/Tooltip/Tooltip.test.tsx +151 -0
- package/src/__tests__/components/Fields/VisualUniqueSelection/ImageSelection/ImageSelection.test.tsx +1 -13
- package/src/__tests__/components/Fields/VisualUniqueSelection/ScrollableSelection/ScrollableSelection.test.tsx +3 -17
- package/src/__tests__/components/Fields/VisualUniqueSelection/VisualUniqueSelection.test.tsx +2 -28
- package/src/__tests__/components/TableList/TableList.test.tsx +119 -0
- package/src/__tests__/components/Tabs/Tabs.test.tsx +202 -0
- package/src/__tests__/components/Tag/Tag.test.tsx +138 -0
- package/src/__tests__/components/Toast/Toast.test.tsx +100 -0
- package/src/api/navigation.tsx +1 -1
- package/src/components/Browser/index.tsx +1 -1
- package/src/components/Button/index.tsx +3 -3
- package/src/components/ConfigPanel/NavigationForm/Field/index.tsx +14 -3
- package/src/components/ElementsTooltip/index.tsx +10 -9
- package/src/components/EmptyState/index.tsx +2 -2
- package/src/components/Fields/ArrayFieldGroup/index.tsx +1 -1
- package/src/components/Fields/AsyncCheckGroup/index.tsx +1 -1
- package/src/components/Fields/AsyncSelect/index.tsx +1 -1
- package/src/components/Fields/ComponentContainer/index.tsx +7 -6
- package/src/components/Fields/ComponentContainer/style.tsx +2 -2
- package/src/components/Fields/HiddenField/index.tsx +1 -1
- package/src/components/Fields/ImageField/index.tsx +10 -5
- package/src/components/Fields/MultiCheckSelect/index.tsx +3 -3
- package/src/components/Fields/NumberField/index.tsx +2 -1
- package/src/components/Fields/ReferenceField/ItemList/Item/index.tsx +5 -7
- package/src/components/Fields/ReferenceField/ItemList/Item/style.tsx +2 -2
- package/src/components/Fields/ReferenceField/ItemList/index.tsx +1 -1
- package/src/components/Fields/RichText/index.tsx +10 -6
- package/src/components/Fields/Select/index.tsx +1 -1
- package/src/components/Fields/SliderField/index.tsx +1 -1
- package/src/components/Fields/TimeField/HourInput/index.tsx +103 -0
- package/src/components/Fields/TimeField/HourInput/style.tsx +19 -0
- package/src/components/Fields/TimeField/HourInput/utils.tsx +35 -0
- package/src/components/Fields/TimeField/index.tsx +57 -0
- package/src/components/Fields/TimeField/style.tsx +37 -0
- package/src/components/Fields/index.tsx +2 -0
- package/src/components/FloatingMenu/index.tsx +1 -1
- package/src/components/Gallery/GalleryFilters/Type/index.tsx +50 -0
- package/src/components/Gallery/GalleryFilters/Type/style.tsx +39 -0
- package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +2 -1
- package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/style.tsx +3 -3
- package/src/components/Gallery/hooks.tsx +10 -4
- package/src/components/Gallery/index.tsx +2 -0
- package/src/components/Icon/index.tsx +1 -1
- package/src/components/Loading/index.tsx +1 -1
- package/src/components/Pagination/index.tsx +1 -1
- package/src/components/SideModal/SideModalOption/index.tsx +4 -2
- package/src/components/SideModal/index.tsx +1 -1
- package/src/components/TableList/index.tsx +6 -6
- package/src/components/TableList/style.tsx +1 -1
- package/src/components/Tabs/index.tsx +19 -7
- package/src/components/Tag/index.tsx +6 -6
- package/src/components/Toast/index.tsx +4 -4
- package/src/components/Tooltip/index.tsx +5 -3
- package/src/components/index.tsx +2 -0
- package/src/containers/Navigation/Defaults/actions.tsx +10 -5
- package/src/containers/Navigation/Defaults/utils.tsx +13 -4
- package/src/containers/Sites/actions.tsx +7 -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/containers/StructuredData/actions.tsx +21 -8
- package/src/containers/StructuredData/constants.tsx +2 -0
- package/src/containers/StructuredData/interfaces.tsx +7 -1
- package/src/containers/StructuredData/reducer.tsx +5 -1
- package/src/helpers/fields.tsx +2 -2
- package/src/helpers/schemas.tsx +2 -2
- package/src/hooks/forms.tsx +2 -1
- package/src/modules/App/Routing/NavMenu/index.tsx +9 -1
- package/src/modules/Content/BulkHeader/TableHeader/index.tsx +1 -1
- package/src/modules/Content/hooks.tsx +19 -12
- package/src/modules/Content/index.tsx +23 -14
- package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +3 -0
- package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/index.tsx +3 -1
- package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +16 -18
- package/src/modules/Navigation/Defaults/DefaultsEditor/utils.tsx +37 -0
- package/src/modules/StructuredData/Form/ConnectedField/index.tsx +3 -2
- package/src/modules/StructuredData/Form/index.tsx +22 -17
- package/src/modules/StructuredData/StructuredDataList/hooks.tsx +30 -20
- package/src/modules/StructuredData/StructuredDataList/index.tsx +24 -14
- 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="
|
|
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="
|
|
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="
|
|
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={
|
|
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:
|
|
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
|
|
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
|
|
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>
|
|
@@ -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;
|
|
@@ -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
|
|
60
|
+
const isEmpty: boolean = containerInfo?.isEmpty;
|
|
61
61
|
|
|
62
|
-
const currentComponent: any = containerInfo
|
|
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 &&
|
|
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
|
|
|
@@ -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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
50
|
-
<S.
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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);
|
|
@@ -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);
|