@griddo/ax 11.11.7 → 11.11.8-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/jest/componentsMock.js +7 -5
- package/package.json +2 -2
- package/src/__tests__/components/Browser/Browser.test.tsx +438 -87
- package/src/__tests__/components/Browser/Browser.utils.test.ts +55 -0
- package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +1 -3
- package/src/__tests__/components/Fields/Button/Button.test.tsx +29 -27
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +158 -0
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorsBanner.test.tsx +90 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.test.tsx +178 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +150 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordItem/KeywordItem.test.tsx +91 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +122 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.utils.test.ts +15 -0
- package/src/__tests__/components/KeywordsPreviewModal/atoms.test.tsx +101 -0
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +1 -1
- package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +318 -0
- package/src/__tests__/modules/FramePreview/FramePreview.utils.test.ts +242 -0
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +185 -0
- package/src/components/Browser/index.tsx +294 -144
- package/src/components/Browser/style.tsx +75 -6
- package/src/components/Browser/utils.tsx +13 -0
- package/src/components/BrowserContent/index.tsx +2 -2
- package/src/components/Button/index.tsx +2 -1
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +2 -4
- package/src/components/Fields/AsyncSelect/style.tsx +13 -0
- package/src/components/Fields/FieldGroup/index.tsx +5 -2
- package/src/components/Fields/FieldGroup/style.tsx +32 -7
- package/src/components/Fields/HeadingField/index.tsx +2 -2
- package/src/components/Fields/HiddenField/style.tsx +1 -1
- package/src/components/Fields/NumberField/index.tsx +15 -16
- package/src/components/Fields/NumberField/style.tsx +2 -0
- package/src/components/Fields/ReferenceField/index.tsx +1 -1
- package/src/components/Fields/SEOPreview/index.tsx +36 -0
- package/src/components/Fields/SEOPreview/style.tsx +24 -0
- package/src/components/Fields/Select/index.tsx +5 -1
- package/src/components/Fields/Select/style.tsx +56 -0
- package/src/components/Fields/SummaryButton/index.tsx +18 -9
- package/src/components/Fields/SummaryButton/style.tsx +1 -2
- package/src/components/Fields/TagsField/index.tsx +8 -9
- package/src/components/Fields/UrlField/index.tsx +26 -27
- package/src/components/Fields/index.tsx +2 -0
- package/src/components/FloatingNote/index.tsx +35 -0
- package/src/components/FloatingNote/style.tsx +26 -0
- package/src/components/FloatingPanel/index.tsx +5 -2
- package/src/components/FloatingPanel/style.tsx +2 -1
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +85 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/style.tsx +80 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +57 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +82 -0
- package/src/components/HeadingsPreviewModal/HeadingItem/index.tsx +71 -0
- package/src/components/HeadingsPreviewModal/HeadingItem/style.tsx +77 -0
- package/src/components/HeadingsPreviewModal/index.tsx +146 -0
- package/src/components/HeadingsPreviewModal/style.tsx +82 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +257 -0
- package/src/components/IconAction/index.tsx +1 -1
- package/src/components/KeywordsPreviewModal/KeywordItem/index.tsx +46 -0
- package/src/components/KeywordsPreviewModal/KeywordItem/style.tsx +64 -0
- package/src/components/KeywordsPreviewModal/atoms.tsx +96 -0
- package/src/components/KeywordsPreviewModal/index.tsx +99 -0
- package/src/components/KeywordsPreviewModal/style.tsx +87 -0
- package/src/components/KeywordsPreviewModal/utils.tsx +22 -0
- package/src/components/MainWrapper/AppBar/index.tsx +8 -1
- package/src/components/MainWrapper/index.tsx +7 -1
- package/src/components/Notification/index.tsx +2 -2
- package/src/components/OcassionalToast/index.tsx +8 -1
- package/src/components/OcassionalToast/style.tsx +15 -1
- package/src/components/PageFinder/index.tsx +1 -1
- package/src/components/ResizePanel/index.tsx +4 -3
- package/src/components/ResizePanel/style.tsx +1 -1
- package/src/components/SearchField/style.tsx +2 -2
- package/src/components/SideModal/index.tsx +2 -1
- package/src/components/Tabs/index.tsx +13 -4
- package/src/components/Tabs/style.tsx +7 -8
- package/src/components/Toast/index.tsx +4 -2
- package/src/components/Tooltip/index.tsx +4 -3
- package/src/components/index.tsx +8 -2
- package/src/forms/fields.tsx +70 -68
- package/src/hooks/forms.tsx +22 -1
- package/src/hooks/index.tsx +13 -3
- package/src/hooks/modals.tsx +103 -15
- package/src/hooks/users.tsx +25 -8
- package/src/modules/Forms/atoms.tsx +2 -2
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +113 -0
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +24 -0
- package/src/modules/FramePreview/index.tsx +55 -16
- package/src/modules/FramePreview/style.tsx +34 -2
- package/src/modules/FramePreview/utils.tsx +140 -0
- package/src/modules/GlobalEditor/Editor/index.tsx +37 -3
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +19 -2
- package/src/modules/GlobalEditor/Preview/index.tsx +0 -2
- package/src/modules/GlobalEditor/Preview/style.tsx +1 -1
- package/src/modules/GlobalEditor/index.tsx +119 -57
- package/src/modules/PageEditor/Editor/index.tsx +33 -2
- package/src/modules/PageEditor/PageBrowser/index.tsx +20 -2
- package/src/modules/PageEditor/Preview/index.tsx +0 -2
- package/src/modules/PageEditor/Preview/style.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +1 -1
- package/src/modules/PageEditor/index.tsx +130 -66
- package/src/modules/PublicPreview/index.tsx +8 -5
- package/src/schemas/pages/GlobalPage.ts +87 -70
- package/src/schemas/pages/Page.ts +87 -70
- package/src/types/index.tsx +12 -0
- package/src/components/PageInfoBanner/index.tsx +0 -38
- package/src/components/PageInfoBanner/styles.tsx +0 -40
package/src/components/index.tsx
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
RadioGroup,
|
|
46
46
|
ReferenceField,
|
|
47
47
|
RichText,
|
|
48
|
+
SEOPreview,
|
|
48
49
|
Select,
|
|
49
50
|
SummaryButton,
|
|
50
51
|
TextArea,
|
|
@@ -64,13 +65,16 @@ import FilterTagsBar from "./FilterTagsBar";
|
|
|
64
65
|
import Flag from "./Flag";
|
|
65
66
|
import FloatingButton from "./FloatingButton";
|
|
66
67
|
import FloatingMenu from "./FloatingMenu";
|
|
68
|
+
import FloatingNote from "./FloatingNote";
|
|
67
69
|
import FloatingPanel from "./FloatingPanel";
|
|
68
70
|
import Gallery from "./Gallery";
|
|
69
71
|
import GuardModal from "./GuardModal";
|
|
72
|
+
import HeadingsPreviewModal from "./HeadingsPreviewModal";
|
|
70
73
|
import Icon from "./Icon";
|
|
71
74
|
import IconAction from "./IconAction";
|
|
72
75
|
import Image from "./Image";
|
|
73
76
|
import InformativeMenu from "./InformativeMenu";
|
|
77
|
+
import KeywordsPreviewModal from "./KeywordsPreviewModal";
|
|
74
78
|
import LanguageMenu from "./LanguageMenu";
|
|
75
79
|
import { ListItem, ListTitle } from "./Lists";
|
|
76
80
|
import Loader from "./Loader";
|
|
@@ -84,7 +88,6 @@ import Nav from "./Nav";
|
|
|
84
88
|
import Notification from "./Notification";
|
|
85
89
|
import OcassionalToast from "./OcassionalToast";
|
|
86
90
|
import PageFinder from "./PageFinder";
|
|
87
|
-
import PageInfoBanner from "./PageInfoBanner";
|
|
88
91
|
import Pagination from "./Pagination";
|
|
89
92
|
import ProgressBar from "./ProgressBar";
|
|
90
93
|
import ReorderArrows from "./ReorderArrows";
|
|
@@ -165,12 +168,14 @@ export {
|
|
|
165
168
|
Flag,
|
|
166
169
|
FloatingButton,
|
|
167
170
|
FloatingMenu,
|
|
171
|
+
FloatingNote,
|
|
168
172
|
FloatingPanel,
|
|
169
173
|
FormCategorySelect,
|
|
170
174
|
FormContainer,
|
|
171
175
|
FormFieldArray,
|
|
172
176
|
Gallery,
|
|
173
177
|
GuardModal,
|
|
178
|
+
HeadingsPreviewModal,
|
|
174
179
|
HeadingField,
|
|
175
180
|
HiddenField,
|
|
176
181
|
Icon,
|
|
@@ -178,6 +183,7 @@ export {
|
|
|
178
183
|
Image,
|
|
179
184
|
ImageField,
|
|
180
185
|
InformativeMenu,
|
|
186
|
+
KeywordsPreviewModal,
|
|
181
187
|
IntegrationsField,
|
|
182
188
|
LanguageMenu,
|
|
183
189
|
LastAccessFilter,
|
|
@@ -199,7 +205,6 @@ export {
|
|
|
199
205
|
NumberField,
|
|
200
206
|
OcassionalToast,
|
|
201
207
|
PageFinder,
|
|
202
|
-
PageInfoBanner,
|
|
203
208
|
Pagination,
|
|
204
209
|
PermissionsFilter,
|
|
205
210
|
ProgressBar,
|
|
@@ -215,6 +220,7 @@ export {
|
|
|
215
220
|
SearchField,
|
|
216
221
|
SearchTagsBar,
|
|
217
222
|
Select,
|
|
223
|
+
SEOPreview,
|
|
218
224
|
SharePageModal,
|
|
219
225
|
SideModal,
|
|
220
226
|
SiteFilter,
|
package/src/forms/fields.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { IErrorItem, IPage, ISite } from "@ax/types";
|
|
1
|
+
import { FieldContainer, FieldGroup, FieldsBehavior } from "@ax/components";
|
|
2
|
+
import type { IErrorItem, IPage, ISite } from "@ax/types";
|
|
4
3
|
|
|
5
4
|
const getInnerFields = (
|
|
6
5
|
innerFields: any[],
|
|
@@ -16,48 +15,54 @@ const getInnerFields = (
|
|
|
16
15
|
) => {
|
|
17
16
|
let fieldArr: any[] = [];
|
|
18
17
|
|
|
19
|
-
return (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { key } = singleFieldProps;
|
|
23
|
-
const error = errors && errors.find((err: any) => err.editorID === selectedContent.editorID && err.key === key);
|
|
18
|
+
return innerFields?.map((singleFieldProps: any) => {
|
|
19
|
+
const { key } = singleFieldProps;
|
|
20
|
+
const error = errors?.find((err: any) => err.editorID === selectedContent.editorID && err.key === key);
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
selectedContent,
|
|
30
|
-
isTemplateActivated,
|
|
31
|
-
theme,
|
|
32
|
-
moduleCopy,
|
|
33
|
-
parentDisabled,
|
|
34
|
-
site,
|
|
35
|
-
errors,
|
|
36
|
-
deleteError,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
22
|
+
const isGroup = singleFieldProps.type === "FieldGroup";
|
|
23
|
+
const isCollapsed = isGroup && singleFieldProps.collapsed;
|
|
24
|
+
const isConditional = singleFieldProps.type === "ConditionalField";
|
|
25
|
+
const isArrayGroup = singleFieldProps.type === "ArrayFieldGroup";
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
disabled={!isTemplateActivated || singleFieldProps.disabled || parentDisabled}
|
|
53
|
-
error={error}
|
|
54
|
-
deleteError={deleteError}
|
|
55
|
-
theme={theme}
|
|
56
|
-
moduleCopy={moduleCopy}
|
|
57
|
-
/>
|
|
27
|
+
if (isGroup || isConditional || isArrayGroup) {
|
|
28
|
+
fieldArr = getInnerFields(
|
|
29
|
+
singleFieldProps.fields,
|
|
30
|
+
innerActions,
|
|
31
|
+
selectedContent,
|
|
32
|
+
isTemplateActivated,
|
|
33
|
+
theme,
|
|
34
|
+
moduleCopy,
|
|
35
|
+
parentDisabled,
|
|
36
|
+
site,
|
|
37
|
+
errors,
|
|
38
|
+
deleteError,
|
|
58
39
|
);
|
|
59
|
-
}
|
|
60
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return isGroup ? (
|
|
43
|
+
<FieldGroup key={key} title={singleFieldProps.title} collapsed={isCollapsed} solid={singleFieldProps.solid}>
|
|
44
|
+
{fieldArr}
|
|
45
|
+
</FieldGroup>
|
|
46
|
+
) : (
|
|
47
|
+
<FieldContainer
|
|
48
|
+
key={key}
|
|
49
|
+
objKey={key}
|
|
50
|
+
innerFields={fieldArr}
|
|
51
|
+
field={singleFieldProps}
|
|
52
|
+
actions={innerActions}
|
|
53
|
+
selectedContent={selectedContent}
|
|
54
|
+
updateValue={innerActions.updateValue}
|
|
55
|
+
goTo={innerActions.goTo}
|
|
56
|
+
site={site}
|
|
57
|
+
{...singleFieldProps}
|
|
58
|
+
disabled={!isTemplateActivated || singleFieldProps.disabled || parentDisabled}
|
|
59
|
+
error={error}
|
|
60
|
+
deleteError={deleteError}
|
|
61
|
+
theme={theme}
|
|
62
|
+
moduleCopy={moduleCopy}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
});
|
|
61
66
|
};
|
|
62
67
|
|
|
63
68
|
const getStructuredDataInnerFields = (
|
|
@@ -69,38 +74,35 @@ const getStructuredDataInnerFields = (
|
|
|
69
74
|
) => {
|
|
70
75
|
let fieldArr: any[] = [];
|
|
71
76
|
|
|
72
|
-
return (
|
|
73
|
-
|
|
74
|
-
innerFields.map((singleFieldProps: any) => {
|
|
75
|
-
const { key, type, fields } = singleFieldProps;
|
|
77
|
+
return innerFields?.map((singleFieldProps: any) => {
|
|
78
|
+
const { key, type, fields } = singleFieldProps;
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
const handleChange = (newValue: any) => {
|
|
81
|
+
updateValue({ [key]: newValue });
|
|
82
|
+
};
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
const value = content?.[key];
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
if (type === "ConditionalField" || type === "ArrayFieldGroup") {
|
|
87
|
+
fieldArr = getStructuredDataInnerFields(fields, content, updateValue, theme, errors);
|
|
88
|
+
}
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
const error = errors.find((err: any) => err.key === key);
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
const fieldProps = {
|
|
93
|
+
value,
|
|
94
|
+
objKey: key,
|
|
95
|
+
fieldType: type,
|
|
96
|
+
innerFields: fieldArr,
|
|
97
|
+
field: singleFieldProps,
|
|
98
|
+
onChange: handleChange,
|
|
99
|
+
...singleFieldProps,
|
|
100
|
+
theme,
|
|
101
|
+
error,
|
|
102
|
+
};
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
+
return <FieldsBehavior key={key} {...fieldProps} />;
|
|
105
|
+
});
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
export { getInnerFields, getStructuredDataInnerFields };
|
package/src/hooks/forms.tsx
CHANGED
|
@@ -26,6 +26,27 @@ const useDebounce = (value: any) => {
|
|
|
26
26
|
return debouncedValue;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const useDebouncedCallback = <T extends unknown[]>(callback: (...args: T) => void, delay: number) => {
|
|
30
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
31
|
+
const callbackRef = useRef(callback);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
callbackRef.current = callback;
|
|
35
|
+
}, [callback]);
|
|
36
|
+
|
|
37
|
+
return useCallback(
|
|
38
|
+
(...args: T) => {
|
|
39
|
+
if (delay === 0) {
|
|
40
|
+
callbackRef.current(...args);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
clearTimeout(timeoutRef.current);
|
|
44
|
+
timeoutRef.current = setTimeout(() => callbackRef.current(...args), delay);
|
|
45
|
+
},
|
|
46
|
+
[delay],
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
const useEqualStructured = (component: any) => {
|
|
30
51
|
return memo(component, (prevProps: any, newProps: any) => {
|
|
31
52
|
const { fieldKey } = prevProps;
|
|
@@ -234,4 +255,4 @@ const useShouldBeSaved = (form: Record<string, unknown> | IUser | FormContent, d
|
|
|
234
255
|
return { isDirty, setIsDirty };
|
|
235
256
|
};
|
|
236
257
|
|
|
237
|
-
export { useDebounce,
|
|
258
|
+
export { useDebounce, useDebouncedCallback, useEqualStructured, usePrevious, useIsDirty, useShouldBeSaved };
|
package/src/hooks/index.tsx
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type IBulkSelectedItems, useBulkSelection } from "./bulk";
|
|
2
2
|
import { useAdaptiveText, useCategoryColors, useEmptyState } from "./content";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useDebounce,
|
|
5
|
+
useDebouncedCallback,
|
|
6
|
+
useEqualStructured,
|
|
7
|
+
useIsDirty,
|
|
8
|
+
usePrevious,
|
|
9
|
+
useShouldBeSaved,
|
|
10
|
+
} from "./forms";
|
|
4
11
|
import { useOnMessageReceivedFromIframe, useOnMessageReceivedFromOutside } from "./iframe";
|
|
5
12
|
import { useURLSearchParam } from "./location";
|
|
6
|
-
import { useHandleClickOutside, useModal, useToast } from "./modals";
|
|
13
|
+
import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
|
|
7
14
|
import { useNetworkStatus } from "./network";
|
|
8
15
|
import { useResizable } from "./resize";
|
|
9
|
-
import { useGlobalPermission, usePermission } from "./users";
|
|
16
|
+
import { useGlobalPermission, usePermission, usePermissions } from "./users";
|
|
10
17
|
import { useWindowSize } from "./window";
|
|
11
18
|
|
|
12
19
|
export {
|
|
@@ -14,16 +21,19 @@ export {
|
|
|
14
21
|
useBulkSelection,
|
|
15
22
|
useCategoryColors,
|
|
16
23
|
useDebounce,
|
|
24
|
+
useDebouncedCallback,
|
|
17
25
|
useEmptyState,
|
|
18
26
|
useEqualStructured,
|
|
19
27
|
useGlobalPermission,
|
|
20
28
|
useHandleClickOutside,
|
|
21
29
|
useIsDirty,
|
|
22
30
|
useModal,
|
|
31
|
+
useModals,
|
|
23
32
|
useNetworkStatus,
|
|
24
33
|
useOnMessageReceivedFromIframe,
|
|
25
34
|
useOnMessageReceivedFromOutside,
|
|
26
35
|
usePermission,
|
|
36
|
+
usePermissions,
|
|
27
37
|
usePrevious,
|
|
28
38
|
useResizable,
|
|
29
39
|
useShouldBeSaved,
|
package/src/hooks/modals.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
2
|
|
|
3
3
|
const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
4
4
|
const [isOpen, setIsOpen] = useState(initialState || false);
|
|
@@ -6,11 +6,26 @@ const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
|
6
6
|
setIsOpen(!isOpen);
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (isOpen && bodyBlock) {
|
|
11
|
+
if (!document.body.classList.contains("modal-open")) {
|
|
12
|
+
document.body.classList.add("modal-open");
|
|
13
|
+
}
|
|
14
|
+
return () => {
|
|
15
|
+
// Solo eliminar si no hay otros modales abiertos
|
|
16
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
17
|
+
if (modals.length <= 1) {
|
|
18
|
+
document.body.classList.remove("modal-open");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
} else if (!bodyBlock || !isOpen) {
|
|
22
|
+
// Solo eliminar si no hay modales abiertos
|
|
23
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
24
|
+
if (modals.length === 0) {
|
|
25
|
+
document.body.classList.remove("modal-open");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [isOpen, bodyBlock]);
|
|
14
29
|
|
|
15
30
|
return {
|
|
16
31
|
isOpen,
|
|
@@ -18,6 +33,66 @@ const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
|
18
33
|
};
|
|
19
34
|
};
|
|
20
35
|
|
|
36
|
+
const useModals = <T extends string>(modalKeys: readonly T[], bodyBlock = true) => {
|
|
37
|
+
const [openModals, setOpenModals] = useState<Record<T, boolean>>(() =>
|
|
38
|
+
modalKeys.reduce(
|
|
39
|
+
(acc, key) => {
|
|
40
|
+
acc[key] = false;
|
|
41
|
+
return acc;
|
|
42
|
+
},
|
|
43
|
+
{} as Record<T, boolean>,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const toggleModal = useCallback((modalKey: T) => {
|
|
48
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: !prev[modalKey] }));
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const openModal = useCallback((modalKey: T) => {
|
|
52
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: true }));
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const closeModal = useCallback((modalKey: T) => {
|
|
56
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: false }));
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const isOpen = useCallback(
|
|
60
|
+
(modalKey: T) => {
|
|
61
|
+
return openModals[modalKey] || false;
|
|
62
|
+
},
|
|
63
|
+
[openModals],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const hasOpenModals = Object.values(openModals).some(Boolean);
|
|
68
|
+
|
|
69
|
+
if (hasOpenModals && bodyBlock) {
|
|
70
|
+
if (!document.body.classList.contains("modal-open")) {
|
|
71
|
+
document.body.classList.add("modal-open");
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
// Solo eliminar si no hay otros modales abiertos
|
|
75
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
76
|
+
if (modals.length <= 1) {
|
|
77
|
+
document.body.classList.remove("modal-open");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Solo eliminar si no hay modales abiertos
|
|
82
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
83
|
+
if (modals.length === 0) {
|
|
84
|
+
document.body.classList.remove("modal-open");
|
|
85
|
+
}
|
|
86
|
+
}, [openModals, bodyBlock]);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
toggleModal,
|
|
90
|
+
openModal,
|
|
91
|
+
closeModal,
|
|
92
|
+
isOpen,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
21
96
|
const useHandleClickOutside = (isOpen: boolean, handleClickOutside: (e: MouseEvent) => void) => {
|
|
22
97
|
useEffect(() => {
|
|
23
98
|
if (isOpen) {
|
|
@@ -35,19 +110,32 @@ const useHandleClickOutside = (isOpen: boolean, handleClickOutside: (e: MouseEve
|
|
|
35
110
|
const useToast = () => {
|
|
36
111
|
const [isVisible, setIsVisible] = useState(false);
|
|
37
112
|
const [state, setState] = useState<any>(null);
|
|
113
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
115
|
+
const toggleToast = useCallback((newState?: any) => {
|
|
116
|
+
if (timeoutRef.current) {
|
|
117
|
+
clearTimeout(timeoutRef.current);
|
|
118
|
+
}
|
|
42
119
|
|
|
43
|
-
|
|
120
|
+
if (newState) {
|
|
121
|
+
setState(newState);
|
|
122
|
+
}
|
|
44
123
|
setIsVisible(true);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
124
|
+
|
|
125
|
+
timeoutRef.current = setTimeout(() => {
|
|
126
|
+
setIsVisible(false);
|
|
127
|
+
}, 6000);
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
return () => {
|
|
132
|
+
if (timeoutRef.current) {
|
|
133
|
+
clearTimeout(timeoutRef.current);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, []);
|
|
49
137
|
|
|
50
138
|
return { isVisible, setIsVisible, toggleToast, state };
|
|
51
139
|
};
|
|
52
140
|
|
|
53
|
-
export { useModal, useHandleClickOutside, useToast };
|
|
141
|
+
export { useModal, useModals, useHandleClickOutside, useToast };
|
package/src/hooks/users.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { useSelector } from "react-redux";
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
import type { IRootState } from "@ax/types";
|
|
3
5
|
|
|
4
6
|
const usePermission = (permission: string | string[] | undefined): boolean => {
|
|
5
7
|
const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
|
|
6
|
-
const isSuperAdmin = useSelector(
|
|
7
|
-
(state: IRootState) => state.users.currentUser && state.users.currentUser.isSuperAdmin,
|
|
8
|
-
);
|
|
8
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
9
9
|
|
|
10
10
|
const isAllowedTo = (permissions: string[]) =>
|
|
11
11
|
userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
|
|
@@ -19,11 +19,28 @@ const usePermission = (permission: string | string[] | undefined): boolean => {
|
|
|
19
19
|
return isAllowedTo(arrayPermission);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const usePermissions = <T extends Record<string, string | string[]>>(permissions: T): Record<keyof T, boolean> => {
|
|
23
|
+
const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
|
|
24
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
25
|
+
|
|
26
|
+
return useMemo(() => {
|
|
27
|
+
const isAllowedTo = (permission: string | string[]) => {
|
|
28
|
+
if (isSuperAdmin) return true;
|
|
29
|
+
const arrayPermission = Array.isArray(permission) ? permission : [permission];
|
|
30
|
+
return userPermissions && arrayPermission.some((perm: string) => userPermissions.includes(perm));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result: Record<string, boolean> = {};
|
|
34
|
+
for (const key of Object.keys(permissions)) {
|
|
35
|
+
result[key] = isAllowedTo(permissions[key]);
|
|
36
|
+
}
|
|
37
|
+
return result as Record<keyof T, boolean>;
|
|
38
|
+
}, [permissions, userPermissions, isSuperAdmin]);
|
|
39
|
+
};
|
|
40
|
+
|
|
22
41
|
const useGlobalPermission = (permission: string | string[] | undefined): boolean => {
|
|
23
42
|
const userPermissions = useSelector((state: IRootState) => state.users.globalPermissions);
|
|
24
|
-
const isSuperAdmin = useSelector(
|
|
25
|
-
(state: IRootState) => state.users.currentUser && state.users.currentUser.isSuperAdmin,
|
|
26
|
-
);
|
|
43
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
27
44
|
|
|
28
45
|
const isAllowedTo = (permissions: string[]) =>
|
|
29
46
|
userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
|
|
@@ -37,4 +54,4 @@ const useGlobalPermission = (permission: string | string[] | undefined): boolean
|
|
|
37
54
|
return isAllowedTo(arrayPermission);
|
|
38
55
|
};
|
|
39
56
|
|
|
40
|
-
export { usePermission, useGlobalPermission };
|
|
57
|
+
export { usePermission, usePermissions, useGlobalPermission };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Dispatch, SetStateAction } from "react";
|
|
2
2
|
|
|
3
|
-
import { IModal } from "@ax/types";
|
|
3
|
+
import type { IModal } from "@ax/types";
|
|
4
4
|
import { Modal, FieldsBehavior, AsyncSelect } from "@ax/components";
|
|
5
5
|
|
|
6
6
|
import * as S from "./style";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
import * as S from "./style";
|
|
5
|
+
|
|
6
|
+
const headColors: Record<string, string> = {
|
|
7
|
+
h1: "#FFF06D",
|
|
8
|
+
h2: "#FFB8F8",
|
|
9
|
+
h3: "#73F8C8",
|
|
10
|
+
h4: "#9BEDFF",
|
|
11
|
+
h5: "#C6C1FF",
|
|
12
|
+
h6: "#FFCE95",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isEffectivelyVisible = (el: HTMLElement): boolean => {
|
|
16
|
+
let node: HTMLElement | null = el;
|
|
17
|
+
while (node && node !== document.body) {
|
|
18
|
+
const style = window.getComputedStyle(node);
|
|
19
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
20
|
+
if (parseFloat(style.opacity) === 0) return false;
|
|
21
|
+
node = node.parentElement;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
|
|
27
|
+
const [boxes, setBoxes] = useState<HeadingBox[]>([]);
|
|
28
|
+
const rafRef = useRef<number>(0);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const update = () => {
|
|
32
|
+
cancelAnimationFrame(rafRef.current);
|
|
33
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
34
|
+
const selector = headingFilter || "h1, h2, h3, h4, h5, h6";
|
|
35
|
+
const headings = Array.from(document.querySelectorAll<HTMLElement>(selector));
|
|
36
|
+
const scrollX = window.scrollX;
|
|
37
|
+
const scrollY = window.scrollY;
|
|
38
|
+
const boxes: HeadingBox[] = [];
|
|
39
|
+
for (let i = 0; i < headings.length; i++) {
|
|
40
|
+
const el = headings[i];
|
|
41
|
+
const rect = el.getBoundingClientRect();
|
|
42
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
43
|
+
if (!isEffectivelyVisible(el)) continue;
|
|
44
|
+
boxes.push({
|
|
45
|
+
id: el.dataset.griddoid || `heading-${i}`,
|
|
46
|
+
tag: el.tagName.toLowerCase(),
|
|
47
|
+
rect,
|
|
48
|
+
scrollX,
|
|
49
|
+
scrollY,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
setBoxes(boxes);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
update();
|
|
57
|
+
|
|
58
|
+
window.addEventListener("resize", update);
|
|
59
|
+
window.addEventListener("scroll", update, true);
|
|
60
|
+
document.addEventListener("animationend", update, true);
|
|
61
|
+
document.addEventListener("transitionend", update, true);
|
|
62
|
+
|
|
63
|
+
const observer = new MutationObserver(update);
|
|
64
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
cancelAnimationFrame(rafRef.current);
|
|
68
|
+
window.removeEventListener("resize", update);
|
|
69
|
+
window.removeEventListener("scroll", update, true);
|
|
70
|
+
document.removeEventListener("animationend", update, true);
|
|
71
|
+
document.removeEventListener("transitionend", update, true);
|
|
72
|
+
observer.disconnect();
|
|
73
|
+
};
|
|
74
|
+
}, [headingFilter]);
|
|
75
|
+
|
|
76
|
+
return createPortal(
|
|
77
|
+
<div data-testid="headings-overlay">
|
|
78
|
+
{boxes.map(({ id, tag, rect, scrollX, scrollY }) => {
|
|
79
|
+
const color = headColors[tag];
|
|
80
|
+
if (!color) return null;
|
|
81
|
+
return (
|
|
82
|
+
<S.Box
|
|
83
|
+
key={id}
|
|
84
|
+
$color={color}
|
|
85
|
+
style={{
|
|
86
|
+
top: rect.top + scrollY,
|
|
87
|
+
left: rect.left + scrollX,
|
|
88
|
+
width: rect.width,
|
|
89
|
+
height: rect.height,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<S.Label $color={color}>{tag.toUpperCase()}</S.Label>
|
|
93
|
+
</S.Box>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
96
|
+
</div>,
|
|
97
|
+
document.body,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
interface HeadingBox {
|
|
102
|
+
id: string;
|
|
103
|
+
tag: string;
|
|
104
|
+
rect: DOMRect;
|
|
105
|
+
scrollX: number;
|
|
106
|
+
scrollY: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface IHeadingsOverlayProps {
|
|
110
|
+
headingFilter: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default HeadingsOverlay;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
const Box = styled.div<{ $color: string }>`
|
|
4
|
+
position: absolute;
|
|
5
|
+
outline: 2px solid ${(p) => p.$color};
|
|
6
|
+
z-index: 1001;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const Label = styled.span<{ $color: string }>`
|
|
11
|
+
position: absolute;
|
|
12
|
+
background-color: ${(p) => p.$color};
|
|
13
|
+
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
14
|
+
${(p) => p.theme.textStyle.uiS};
|
|
15
|
+
padding: ${(p) => `0 ${p.theme.spacing.xxs}`};
|
|
16
|
+
transform: rotate(-90deg);
|
|
17
|
+
top: 1px;
|
|
18
|
+
left: -23px;
|
|
19
|
+
font-family: Source Sans Pro;
|
|
20
|
+
font-style: normal;
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export { Box, Label };
|