@griddo/ax 11.11.8-rc.1 → 11.12.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 +5 -7
- package/package.json +2 -2
- package/src/__tests__/components/Browser/Browser.test.tsx +87 -438
- package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +3 -1
- package/src/__tests__/components/Fields/Button/Button.test.tsx +27 -29
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +1 -1
- package/src/components/Browser/index.tsx +149 -294
- package/src/components/Browser/style.tsx +6 -75
- package/src/components/Button/index.tsx +1 -2
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +4 -2
- package/src/components/Fields/AsyncSelect/style.tsx +0 -13
- package/src/components/Fields/FieldGroup/index.tsx +2 -5
- package/src/components/Fields/FieldGroup/style.tsx +7 -32
- 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 +16 -15
- package/src/components/Fields/NumberField/style.tsx +0 -2
- package/src/components/Fields/ReferenceField/index.tsx +1 -1
- package/src/components/Fields/Select/index.tsx +1 -5
- package/src/components/Fields/Select/style.tsx +0 -56
- package/src/components/Fields/SummaryButton/index.tsx +9 -18
- package/src/components/Fields/SummaryButton/style.tsx +2 -1
- package/src/components/Fields/TagsField/index.tsx +9 -8
- package/src/components/Fields/UrlField/index.tsx +27 -26
- package/src/components/Fields/index.tsx +0 -2
- package/src/components/FloatingPanel/index.tsx +2 -5
- package/src/components/FloatingPanel/style.tsx +1 -2
- package/src/components/IconAction/index.tsx +1 -1
- package/src/components/MainWrapper/AppBar/index.tsx +1 -8
- package/src/components/MainWrapper/index.tsx +1 -7
- package/src/components/Notification/index.tsx +2 -2
- package/src/components/PageFinder/index.tsx +1 -1
- package/src/components/ResizePanel/index.tsx +3 -4
- package/src/components/ResizePanel/style.tsx +1 -1
- package/src/components/SearchField/style.tsx +2 -2
- package/src/components/SideModal/index.tsx +1 -2
- package/src/components/Tabs/index.tsx +4 -13
- package/src/components/Tabs/style.tsx +8 -7
- package/src/components/Toast/index.tsx +2 -4
- package/src/components/Tooltip/index.tsx +3 -4
- package/src/components/index.tsx +0 -8
- package/src/forms/fields.tsx +68 -70
- package/src/hooks/forms.tsx +1 -22
- package/src/hooks/index.tsx +3 -13
- package/src/hooks/modals.tsx +15 -103
- package/src/hooks/users.tsx +8 -25
- package/src/modules/Forms/atoms.tsx +2 -2
- package/src/modules/FramePreview/index.tsx +16 -55
- package/src/modules/FramePreview/style.tsx +2 -34
- package/src/modules/GlobalEditor/Editor/index.tsx +3 -37
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +2 -19
- package/src/modules/GlobalEditor/Preview/index.tsx +2 -0
- package/src/modules/GlobalEditor/Preview/style.tsx +1 -1
- package/src/modules/GlobalEditor/index.tsx +57 -119
- package/src/modules/PageEditor/Editor/index.tsx +2 -33
- package/src/modules/PageEditor/PageBrowser/index.tsx +2 -20
- package/src/modules/PageEditor/Preview/index.tsx +2 -0
- package/src/modules/PageEditor/Preview/style.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +1 -1
- package/src/modules/PageEditor/index.tsx +66 -130
- package/src/modules/PublicPreview/index.tsx +2 -5
- package/src/schemas/pages/GlobalPage.ts +70 -87
- package/src/schemas/pages/Page.ts +70 -87
- package/src/types/index.tsx +0 -12
- package/src/__tests__/components/Browser/Browser.utils.test.ts +0 -55
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +0 -158
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorsBanner.test.tsx +0 -90
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.test.tsx +0 -178
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +0 -150
- package/src/__tests__/components/KeywordsPreviewModal/KeywordItem/KeywordItem.test.tsx +0 -91
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +0 -122
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.utils.test.ts +0 -15
- package/src/__tests__/components/KeywordsPreviewModal/atoms.test.tsx +0 -101
- package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +0 -318
- package/src/__tests__/modules/FramePreview/FramePreview.utils.test.ts +0 -242
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +0 -185
- package/src/components/Browser/utils.tsx +0 -13
- package/src/components/Fields/SEOPreview/index.tsx +0 -36
- package/src/components/Fields/SEOPreview/style.tsx +0 -24
- package/src/components/FloatingNote/index.tsx +0 -35
- package/src/components/FloatingNote/style.tsx +0 -26
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +0 -85
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/style.tsx +0 -80
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +0 -57
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +0 -82
- package/src/components/HeadingsPreviewModal/HeadingItem/index.tsx +0 -71
- package/src/components/HeadingsPreviewModal/HeadingItem/style.tsx +0 -77
- package/src/components/HeadingsPreviewModal/index.tsx +0 -146
- package/src/components/HeadingsPreviewModal/style.tsx +0 -82
- package/src/components/HeadingsPreviewModal/utils.tsx +0 -257
- package/src/components/KeywordsPreviewModal/KeywordItem/index.tsx +0 -46
- package/src/components/KeywordsPreviewModal/KeywordItem/style.tsx +0 -64
- package/src/components/KeywordsPreviewModal/atoms.tsx +0 -96
- package/src/components/KeywordsPreviewModal/index.tsx +0 -99
- package/src/components/KeywordsPreviewModal/style.tsx +0 -87
- package/src/components/KeywordsPreviewModal/utils.tsx +0 -22
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +0 -113
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +0 -24
- package/src/modules/FramePreview/utils.tsx +0 -140
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { ThemeProvider } from "styled-components";
|
|
2
|
-
import { render, cleanup, screen, act } from "@testing-library/react";
|
|
3
|
-
import { parseTheme } from "@ax/helpers";
|
|
4
|
-
import "@testing-library/jest-dom";
|
|
5
|
-
|
|
6
|
-
import HeadingsOverlay from "@ax/modules/FramePreview/HeadingsOverlay";
|
|
7
|
-
import globalTheme from "@ax/themes/theme.json";
|
|
8
|
-
|
|
9
|
-
const renderWithTheme = (ui: React.ReactElement) =>
|
|
10
|
-
render(<ThemeProvider theme={parseTheme(globalTheme)}>{ui}</ThemeProvider>);
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
|
|
14
|
-
cb(0);
|
|
15
|
-
return 0;
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
cleanup();
|
|
21
|
-
for (const el of document.querySelectorAll("h1, h2, h3, h4, h5, h6")) el.remove();
|
|
22
|
-
jest.restoreAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe("HeadingsOverlay rendering", () => {
|
|
26
|
-
it("should render without boxes when no headings exist in the document", () => {
|
|
27
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
28
|
-
|
|
29
|
-
expect(screen.queryByText("H1")).toBeNull();
|
|
30
|
-
expect(screen.queryByText("H2")).toBeNull();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("should render a labeled box for an h1 element with a non-zero bounding rect", async () => {
|
|
34
|
-
const h1 = document.createElement("h1");
|
|
35
|
-
h1.dataset.griddoid = "heading-1";
|
|
36
|
-
document.body.appendChild(h1);
|
|
37
|
-
jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
|
|
38
|
-
top: 10,
|
|
39
|
-
left: 10,
|
|
40
|
-
width: 200,
|
|
41
|
-
height: 40,
|
|
42
|
-
bottom: 50,
|
|
43
|
-
right: 210,
|
|
44
|
-
x: 10,
|
|
45
|
-
y: 10,
|
|
46
|
-
toJSON: () => {},
|
|
47
|
-
} as DOMRect);
|
|
48
|
-
|
|
49
|
-
await act(async () => {
|
|
50
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(screen.getByText("H1")).toBeTruthy();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("should render an uppercase label for the heading tag", async () => {
|
|
57
|
-
const h2 = document.createElement("h2");
|
|
58
|
-
h2.dataset.griddoid = "heading-1";
|
|
59
|
-
document.body.appendChild(h2);
|
|
60
|
-
jest.spyOn(h2, "getBoundingClientRect").mockReturnValue({
|
|
61
|
-
top: 10,
|
|
62
|
-
left: 10,
|
|
63
|
-
width: 200,
|
|
64
|
-
height: 40,
|
|
65
|
-
bottom: 50,
|
|
66
|
-
right: 210,
|
|
67
|
-
x: 10,
|
|
68
|
-
y: 10,
|
|
69
|
-
toJSON: () => {},
|
|
70
|
-
} as DOMRect);
|
|
71
|
-
|
|
72
|
-
await act(async () => {
|
|
73
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(screen.getByText("H2")).toBeTruthy();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("should not render boxes for headings with zero bounding rect", () => {
|
|
80
|
-
const h1 = document.createElement("h1");
|
|
81
|
-
document.body.appendChild(h1);
|
|
82
|
-
// JSDOM returns zero rect by default — no mock needed
|
|
83
|
-
|
|
84
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
85
|
-
|
|
86
|
-
expect(screen.queryByText("H1")).toBeNull();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should not render a box for a heading with display:none", async () => {
|
|
90
|
-
const h1 = document.createElement("h1");
|
|
91
|
-
h1.dataset.griddoid = "heading-1";
|
|
92
|
-
document.body.appendChild(h1);
|
|
93
|
-
jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
|
|
94
|
-
top: 10,
|
|
95
|
-
left: 10,
|
|
96
|
-
width: 200,
|
|
97
|
-
height: 40,
|
|
98
|
-
bottom: 50,
|
|
99
|
-
right: 210,
|
|
100
|
-
x: 10,
|
|
101
|
-
y: 10,
|
|
102
|
-
toJSON: () => {},
|
|
103
|
-
} as DOMRect);
|
|
104
|
-
|
|
105
|
-
const originalGetComputedStyle = window.getComputedStyle.bind(window);
|
|
106
|
-
jest.spyOn(window, "getComputedStyle").mockImplementation((el) => {
|
|
107
|
-
if (el === h1) return { display: "none", visibility: "", opacity: "1" } as CSSStyleDeclaration;
|
|
108
|
-
return originalGetComputedStyle(el);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
await act(async () => {
|
|
112
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(screen.queryByText("H1")).toBeNull();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("should not render a box for a heading with opacity 0", async () => {
|
|
119
|
-
const h1 = document.createElement("h1");
|
|
120
|
-
h1.dataset.griddoid = "heading-1";
|
|
121
|
-
document.body.appendChild(h1);
|
|
122
|
-
jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
|
|
123
|
-
top: 10,
|
|
124
|
-
left: 10,
|
|
125
|
-
width: 200,
|
|
126
|
-
height: 40,
|
|
127
|
-
bottom: 50,
|
|
128
|
-
right: 210,
|
|
129
|
-
x: 10,
|
|
130
|
-
y: 10,
|
|
131
|
-
toJSON: () => {},
|
|
132
|
-
} as DOMRect);
|
|
133
|
-
|
|
134
|
-
const originalGetComputedStyle = window.getComputedStyle.bind(window);
|
|
135
|
-
jest.spyOn(window, "getComputedStyle").mockImplementation((el) => {
|
|
136
|
-
if (el === h1) return { display: "block", visibility: "visible", opacity: "0" } as CSSStyleDeclaration;
|
|
137
|
-
return originalGetComputedStyle(el);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
await act(async () => {
|
|
141
|
-
renderWithTheme(<HeadingsOverlay headingFilter={null} />);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(screen.queryByText("H1")).toBeNull();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should only render boxes for headings matching the headingFilter", async () => {
|
|
148
|
-
const h1 = document.createElement("h1");
|
|
149
|
-
h1.dataset.griddoid = "heading-1";
|
|
150
|
-
document.body.appendChild(h1);
|
|
151
|
-
jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
|
|
152
|
-
top: 10,
|
|
153
|
-
left: 10,
|
|
154
|
-
width: 200,
|
|
155
|
-
height: 40,
|
|
156
|
-
bottom: 50,
|
|
157
|
-
right: 210,
|
|
158
|
-
x: 10,
|
|
159
|
-
y: 10,
|
|
160
|
-
toJSON: () => {},
|
|
161
|
-
} as DOMRect);
|
|
162
|
-
|
|
163
|
-
const h2 = document.createElement("h2");
|
|
164
|
-
h2.dataset.griddoid = "heading-2";
|
|
165
|
-
document.body.appendChild(h2);
|
|
166
|
-
jest.spyOn(h2, "getBoundingClientRect").mockReturnValue({
|
|
167
|
-
top: 60,
|
|
168
|
-
left: 10,
|
|
169
|
-
width: 200,
|
|
170
|
-
height: 40,
|
|
171
|
-
bottom: 100,
|
|
172
|
-
right: 210,
|
|
173
|
-
x: 10,
|
|
174
|
-
y: 60,
|
|
175
|
-
toJSON: () => {},
|
|
176
|
-
} as DOMRect);
|
|
177
|
-
|
|
178
|
-
await act(async () => {
|
|
179
|
-
renderWithTheme(<HeadingsOverlay headingFilter="h1" />);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
expect(screen.getByText("H1")).toBeTruthy();
|
|
183
|
-
expect(screen.queryByText("H2")).toBeNull();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const calcAutoZoom = (containerWidth: number, resolution: string): string => {
|
|
2
|
-
const resolutionWidth = parseInt(resolution);
|
|
3
|
-
if (!containerWidth || !resolutionWidth) return "100";
|
|
4
|
-
return String(Math.min(100, Math.floor((containerWidth / resolutionWidth) * 100)));
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
const calcDefaultResolution = (containerWidth: number, options: Array<{ value: string }>): string => {
|
|
8
|
-
const sorted = [...options].sort((a, b) => parseInt(a.value) - parseInt(b.value));
|
|
9
|
-
const best = sorted.find((opt) => parseInt(opt.value) >= containerWidth);
|
|
10
|
-
return best ? best.value : sorted[sorted.length - 1].value;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export { calcAutoZoom, calcDefaultResolution };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as S from "./style";
|
|
2
|
-
|
|
3
|
-
const SEOPreview = (props: IHeadingsPreviewProps) => {
|
|
4
|
-
const { actions } = props;
|
|
5
|
-
const { toggleHeadingsPreviewAction, toggleKeywordsPreviewAction } = actions || {};
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<S.Wrapper>
|
|
9
|
-
<S.TextWrapper>
|
|
10
|
-
Quickly review page <strong>headings</strong> for correctness it before publishing. You can also edit the text.
|
|
11
|
-
</S.TextWrapper>
|
|
12
|
-
<S.ButtonWrapper>
|
|
13
|
-
<S.StyledButton type="button" onClick={toggleHeadingsPreviewAction}>
|
|
14
|
-
Open Headings preview
|
|
15
|
-
</S.StyledButton>
|
|
16
|
-
</S.ButtonWrapper>
|
|
17
|
-
<S.TextWrapper margin>
|
|
18
|
-
Review the <strong>Keywords</strong> of the page in a quick way before publishing.
|
|
19
|
-
</S.TextWrapper>
|
|
20
|
-
<S.ButtonWrapper>
|
|
21
|
-
<S.StyledButton type="button" onClick={toggleKeywordsPreviewAction}>
|
|
22
|
-
Open Keywords preview
|
|
23
|
-
</S.StyledButton>
|
|
24
|
-
</S.ButtonWrapper>
|
|
25
|
-
</S.Wrapper>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
interface IHeadingsPreviewProps {
|
|
30
|
-
actions?: {
|
|
31
|
-
toggleHeadingsPreviewAction?: () => void;
|
|
32
|
-
toggleKeywordsPreviewAction?: () => void;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default SEOPreview;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Button } from "@ax/components";
|
|
2
|
-
|
|
3
|
-
import styled from "styled-components";
|
|
4
|
-
|
|
5
|
-
const Wrapper = styled.div``;
|
|
6
|
-
|
|
7
|
-
const TextWrapper = styled.div<{ margin?: boolean }>`
|
|
8
|
-
${(p) => p.theme.textStyle.uiM};
|
|
9
|
-
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
10
|
-
margin-top: ${(p) => (p.margin ? p.theme.spacing.m : 0)};
|
|
11
|
-
`;
|
|
12
|
-
|
|
13
|
-
const ButtonWrapper = styled.div`
|
|
14
|
-
display: flex;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
margin-top: ${(p) => p.theme.spacing.xs};
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
|
-
const StyledButton = styled((props) => <Button {...props} />)`
|
|
20
|
-
width: 100%;
|
|
21
|
-
max-width: 286px;
|
|
22
|
-
`;
|
|
23
|
-
|
|
24
|
-
export { Wrapper, TextWrapper, ButtonWrapper, StyledButton };
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Button, Icon } from "@ax/components";
|
|
2
|
-
|
|
3
|
-
import * as S from "./style";
|
|
4
|
-
|
|
5
|
-
const FloatingNote = (props: IFloatingNoteProps): JSX.Element => {
|
|
6
|
-
const { message, icon, className, btnText, onClick } = props;
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<S.Wrapper data-testid="floating-note-wrapper" className={className}>
|
|
10
|
-
{icon && (
|
|
11
|
-
<S.IconWrapper>
|
|
12
|
-
<Icon name={icon} size="16" />
|
|
13
|
-
</S.IconWrapper>
|
|
14
|
-
)}
|
|
15
|
-
<S.Text data-testid="floating-note-message">{message}</S.Text>
|
|
16
|
-
{btnText && (
|
|
17
|
-
<S.ActionWrapper>
|
|
18
|
-
<Button type="button" buttonStyle="minimal" onClick={onClick}>
|
|
19
|
-
{btnText}
|
|
20
|
-
</Button>
|
|
21
|
-
</S.ActionWrapper>
|
|
22
|
-
)}
|
|
23
|
-
</S.Wrapper>
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export interface IFloatingNoteProps {
|
|
28
|
-
message: string;
|
|
29
|
-
icon?: string;
|
|
30
|
-
className?: string;
|
|
31
|
-
btnText?: string;
|
|
32
|
-
onClick?: () => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default FloatingNote;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
|
-
|
|
3
|
-
const Wrapper = styled.div`
|
|
4
|
-
display: flex;
|
|
5
|
-
background-color: ${(p) => p.theme.color.uiBackground03};
|
|
6
|
-
padding: ${(p) => p.theme.spacing.s};
|
|
7
|
-
border-radius: ${(p) => p.theme.radii.s};
|
|
8
|
-
z-index: 98;
|
|
9
|
-
`;
|
|
10
|
-
|
|
11
|
-
const Text = styled.div`
|
|
12
|
-
${(p) => p.theme.textStyle.uiS};
|
|
13
|
-
color: ${(p) => p.theme.color.textMediumEmphasis};
|
|
14
|
-
`;
|
|
15
|
-
|
|
16
|
-
const IconWrapper = styled.div`
|
|
17
|
-
width: ${(p) => p.theme.spacing.s};
|
|
18
|
-
height: ${(p) => p.theme.spacing.s};
|
|
19
|
-
margin-right: ${(p) => p.theme.spacing.xs};
|
|
20
|
-
`;
|
|
21
|
-
|
|
22
|
-
const ActionWrapper = styled.div`
|
|
23
|
-
margin-left: auto;
|
|
24
|
-
`;
|
|
25
|
-
|
|
26
|
-
export { Wrapper, Text, IconWrapper, ActionWrapper };
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { Icon, Tooltip } from "@ax/components";
|
|
4
|
-
|
|
5
|
-
import type { IHeadingError } from "../../utils";
|
|
6
|
-
|
|
7
|
-
import * as S from "./style";
|
|
8
|
-
|
|
9
|
-
const ErrorsItem = (props: IErrorsItemProps) => {
|
|
10
|
-
const { error, onSelectHeading } = props;
|
|
11
|
-
const { message, description, headingIds } = error;
|
|
12
|
-
|
|
13
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
14
|
-
const [isDeleted, setIsDeleted] = useState(false);
|
|
15
|
-
const [currentIndex, setCurrentIndex] = useState<number | null>(null);
|
|
16
|
-
|
|
17
|
-
const handlePrevious = () => {
|
|
18
|
-
if (!currentIndex) return;
|
|
19
|
-
const newIndex = currentIndex - 1;
|
|
20
|
-
setCurrentIndex(newIndex);
|
|
21
|
-
onSelectHeading(headingIds[newIndex])();
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const handleNext = () => {
|
|
25
|
-
const newIndex = currentIndex === null ? 0 : currentIndex + 1;
|
|
26
|
-
if (newIndex > headingIds.length - 1) return;
|
|
27
|
-
setCurrentIndex(newIndex);
|
|
28
|
-
onSelectHeading(headingIds[newIndex])();
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const handleOpen = () => {
|
|
32
|
-
setIsOpen(!isOpen);
|
|
33
|
-
setCurrentIndex(0);
|
|
34
|
-
onSelectHeading(headingIds[0])();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const errorText =
|
|
38
|
-
headingIds.length > 1 && currentIndex !== null
|
|
39
|
-
? `${currentIndex + 1} of ${headingIds.length} headings`
|
|
40
|
-
: `${headingIds.length} headings`;
|
|
41
|
-
|
|
42
|
-
if (isDeleted) {
|
|
43
|
-
return <></>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<S.ErrorItem>
|
|
48
|
-
<Tooltip content={description}>
|
|
49
|
-
<S.ErrorHeader>
|
|
50
|
-
<S.ErrorMessage>{message}</S.ErrorMessage>
|
|
51
|
-
<S.ErrorActions>
|
|
52
|
-
{headingIds.length > 0 && (
|
|
53
|
-
<S.IconWrapper onClick={handleOpen}>
|
|
54
|
-
<Icon name={isOpen ? "UpArrow" : "DownArrow"} size="16" />
|
|
55
|
-
</S.IconWrapper>
|
|
56
|
-
)}
|
|
57
|
-
<S.IconWrapper onClick={() => setIsDeleted(true)}>
|
|
58
|
-
<Icon name="close" size="16" />
|
|
59
|
-
</S.IconWrapper>
|
|
60
|
-
</S.ErrorActions>
|
|
61
|
-
</S.ErrorHeader>
|
|
62
|
-
</Tooltip>
|
|
63
|
-
<S.ErrorContentWrapper isOpen={isOpen}>
|
|
64
|
-
<S.ErrorContent>
|
|
65
|
-
<S.ErrorContentActions>
|
|
66
|
-
<S.IconWrapper onClick={handlePrevious} isDisabled={!currentIndex}>
|
|
67
|
-
<Icon name="UpArrow" size="16" />
|
|
68
|
-
</S.IconWrapper>
|
|
69
|
-
<S.IconWrapper onClick={handleNext} isDisabled={currentIndex === headingIds.length - 1}>
|
|
70
|
-
<Icon name="DownArrow" size="16" />
|
|
71
|
-
</S.IconWrapper>
|
|
72
|
-
</S.ErrorContentActions>
|
|
73
|
-
<S.ErrorContentText>{errorText}</S.ErrorContentText>
|
|
74
|
-
</S.ErrorContent>
|
|
75
|
-
</S.ErrorContentWrapper>
|
|
76
|
-
</S.ErrorItem>
|
|
77
|
-
);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
interface IErrorsItemProps {
|
|
81
|
-
error: IHeadingError;
|
|
82
|
-
onSelectHeading: (id: number) => () => void;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export default ErrorsItem;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
|
-
|
|
3
|
-
const ErrorItem = styled.li`
|
|
4
|
-
display: flex;
|
|
5
|
-
background-color: ${(p) => p.theme.colors.uiBackground02};
|
|
6
|
-
border: ${(p) => `1px solid ${p.theme.colors.uiLine}`};
|
|
7
|
-
padding: ${(p) => p.theme.spacing.xs};
|
|
8
|
-
margin-bottom: ${(p) => p.theme.spacing.xxs};
|
|
9
|
-
border-radius: ${(p) => p.theme.radii.s};
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
|
|
12
|
-
&:last-child {
|
|
13
|
-
margin-bottom: 0;
|
|
14
|
-
}
|
|
15
|
-
`;
|
|
16
|
-
|
|
17
|
-
const ErrorHeader = styled.div`
|
|
18
|
-
display: flex;
|
|
19
|
-
width: 100%;
|
|
20
|
-
`;
|
|
21
|
-
|
|
22
|
-
const ErrorMessage = styled.div`
|
|
23
|
-
${(p) => p.theme.textStyle.uiS};
|
|
24
|
-
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
25
|
-
font-weight: 400;
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const ErrorActions = styled.div`
|
|
29
|
-
display: flex;
|
|
30
|
-
margin-left: auto;
|
|
31
|
-
align-items: start;
|
|
32
|
-
gap: ${(p) => p.theme.spacing.xxs};
|
|
33
|
-
padding-left: ${(p) => p.theme.spacing.xs};
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
const IconWrapper = styled.button<{ isDisabled?: boolean }>`
|
|
37
|
-
width: ${(p) => p.theme.spacing.s};
|
|
38
|
-
height: ${(p) => p.theme.spacing.s};
|
|
39
|
-
svg {
|
|
40
|
-
path {
|
|
41
|
-
fill: ${(p) => (p.isDisabled ? p.theme.color.interactiveDisabled : p.theme.color.interactive01)};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const ErrorContentWrapper = styled.div<{ isOpen: boolean }>`
|
|
47
|
-
width: 100%;
|
|
48
|
-
overflow: hidden;
|
|
49
|
-
height: ${(p) => (p.isOpen ? "auto" : "0")};
|
|
50
|
-
transition: height 1s ease-in-out;
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const ErrorContent = styled.div`
|
|
54
|
-
display: flex;
|
|
55
|
-
width: 100%;
|
|
56
|
-
margin-top: ${(p) => p.theme.spacing.xxs};
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
const ErrorContentActions = styled.div`
|
|
60
|
-
display: flex;
|
|
61
|
-
gap: ${(p) => p.theme.spacing.xxs};
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
const ErrorContentText = styled.div`
|
|
65
|
-
${(p) => p.theme.textStyle.uiXS};
|
|
66
|
-
color: ${(p) => p.theme.colors.textMediumEmphasis};
|
|
67
|
-
margin-left: ${(p) => p.theme.spacing.s};
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
|
-
export {
|
|
71
|
-
ErrorItem,
|
|
72
|
-
ErrorHeader,
|
|
73
|
-
ErrorMessage,
|
|
74
|
-
ErrorActions,
|
|
75
|
-
IconWrapper,
|
|
76
|
-
ErrorContentWrapper,
|
|
77
|
-
ErrorContent,
|
|
78
|
-
ErrorContentActions,
|
|
79
|
-
ErrorContentText,
|
|
80
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { Icon } from "@ax/components";
|
|
4
|
-
|
|
5
|
-
import type { IHeadingError } from "../utils";
|
|
6
|
-
import ErrorItem from "./ErrorItem";
|
|
7
|
-
|
|
8
|
-
import * as S from "./style";
|
|
9
|
-
|
|
10
|
-
const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
11
|
-
const { errors, onSelectHeading, isOpen, setIsOpen, resetKey } = props;
|
|
12
|
-
|
|
13
|
-
const [isDeleted, setIsDeleted] = useState(false);
|
|
14
|
-
|
|
15
|
-
if (isDeleted) {
|
|
16
|
-
return <></>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<S.ErrorsWrapper>
|
|
21
|
-
<S.ErrorsHeader>
|
|
22
|
-
<S.WarningWrapper>
|
|
23
|
-
<Icon name="warning" size="16" />
|
|
24
|
-
</S.WarningWrapper>
|
|
25
|
-
<S.HeaderText>SEO Alerts</S.HeaderText>
|
|
26
|
-
<S.HeaderActions>
|
|
27
|
-
<S.ToggleWrapper onClick={() => setIsOpen(!isOpen)}>
|
|
28
|
-
<Icon name={isOpen ? "UpArrow" : "DownArrow"} size="24" />
|
|
29
|
-
</S.ToggleWrapper>
|
|
30
|
-
<S.IconWrapper onClick={() => setIsDeleted(true)}>
|
|
31
|
-
<Icon name="close" size="16" />
|
|
32
|
-
</S.IconWrapper>
|
|
33
|
-
</S.HeaderActions>
|
|
34
|
-
</S.ErrorsHeader>
|
|
35
|
-
<S.ErrorsContent isOpen={isOpen}>
|
|
36
|
-
<S.Description>
|
|
37
|
-
Review <strong>suggestions and warnings</strong> to enhance your page's search engine optimization.
|
|
38
|
-
</S.Description>
|
|
39
|
-
<S.ErrorListWrapper>
|
|
40
|
-
{errors.map((error) => (
|
|
41
|
-
<ErrorItem key={`${error.message}-${resetKey}`} error={error} onSelectHeading={onSelectHeading} />
|
|
42
|
-
))}
|
|
43
|
-
</S.ErrorListWrapper>
|
|
44
|
-
</S.ErrorsContent>
|
|
45
|
-
</S.ErrorsWrapper>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
interface IErrorsBannerProps {
|
|
50
|
-
errors: IHeadingError[];
|
|
51
|
-
onSelectHeading: (id: number) => () => void;
|
|
52
|
-
isOpen: boolean;
|
|
53
|
-
setIsOpen: (value: boolean) => void;
|
|
54
|
-
resetKey: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default ErrorsBanner;
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
|
-
|
|
3
|
-
const ErrorsWrapper = styled.div`
|
|
4
|
-
position: sticky;
|
|
5
|
-
top: 0;
|
|
6
|
-
background-color: ${(p) => p.theme.colors.uiBackground03};
|
|
7
|
-
width: 100%;
|
|
8
|
-
padding: ${(p) => p.theme.spacing.s};
|
|
9
|
-
margin-bottom: ${(p) => p.theme.spacing.xs};
|
|
10
|
-
border-radius: ${(p) => p.theme.radii.s};
|
|
11
|
-
z-index: 2;
|
|
12
|
-
`;
|
|
13
|
-
|
|
14
|
-
const ErrorsHeader = styled.div`
|
|
15
|
-
display: flex;
|
|
16
|
-
align-items: center;
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
|
-
const WarningWrapper = styled.div`
|
|
20
|
-
width: ${(p) => p.theme.spacing.s};
|
|
21
|
-
height: ${(p) => p.theme.spacing.s};
|
|
22
|
-
svg {
|
|
23
|
-
path {
|
|
24
|
-
fill: ${(p) => p.theme.color.interactive02};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
const HeaderText = styled.div`
|
|
30
|
-
${(p) => p.theme.textStyle.uiM};
|
|
31
|
-
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
32
|
-
margin-left: ${(p) => p.theme.spacing.xxs};
|
|
33
|
-
font-weight: 600;
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
const HeaderActions = styled.div`
|
|
37
|
-
display: flex;
|
|
38
|
-
margin-left: auto;
|
|
39
|
-
gap: ${(p) => p.theme.spacing.xxs};
|
|
40
|
-
align-items: center;
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
|
-
const ToggleWrapper = styled.button`
|
|
44
|
-
width: ${(p) => p.theme.spacing.m};
|
|
45
|
-
height: ${(p) => p.theme.spacing.m};
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const IconWrapper = styled.button`
|
|
49
|
-
width: ${(p) => p.theme.spacing.s};
|
|
50
|
-
height: ${(p) => p.theme.spacing.s};
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const ErrorsContent = styled.div<{ isOpen: boolean }>`
|
|
54
|
-
display: flex;
|
|
55
|
-
flex-direction: column;
|
|
56
|
-
overflow: hidden;
|
|
57
|
-
height: ${(p) => (p.isOpen ? "auto" : "0")};
|
|
58
|
-
transition: height 1s ease-in-out;
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
|
-
const Description = styled.div`
|
|
62
|
-
${(p) => p.theme.textStyle.uiXS};
|
|
63
|
-
color: ${(p) => p.theme.colors.textMediumEmphasis};
|
|
64
|
-
margin-top: ${(p) => p.theme.spacing.xs};
|
|
65
|
-
`;
|
|
66
|
-
|
|
67
|
-
const ErrorListWrapper = styled.ul`
|
|
68
|
-
margin-top: ${(p) => p.theme.spacing.xs};
|
|
69
|
-
`;
|
|
70
|
-
|
|
71
|
-
export {
|
|
72
|
-
ErrorsWrapper,
|
|
73
|
-
ErrorsHeader,
|
|
74
|
-
IconWrapper,
|
|
75
|
-
WarningWrapper,
|
|
76
|
-
HeaderText,
|
|
77
|
-
HeaderActions,
|
|
78
|
-
ErrorsContent,
|
|
79
|
-
ToggleWrapper,
|
|
80
|
-
Description,
|
|
81
|
-
ErrorListWrapper,
|
|
82
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Icon } from "@ax/components";
|
|
2
|
-
import type { HeadingNode } from "@ax/types";
|
|
3
|
-
|
|
4
|
-
import * as S from "./style";
|
|
5
|
-
|
|
6
|
-
const HeadingItem = ({
|
|
7
|
-
head,
|
|
8
|
-
index,
|
|
9
|
-
isFiltering,
|
|
10
|
-
selected,
|
|
11
|
-
onHeadingClick,
|
|
12
|
-
counter,
|
|
13
|
-
parentPath,
|
|
14
|
-
}: IHeadingItemProps) => {
|
|
15
|
-
counter.value += 1;
|
|
16
|
-
const headingId = counter.value;
|
|
17
|
-
const uniqueKey = `${parentPath}${head.tag}-${head.level}-${index}-${head.text.slice(0, 20)}`;
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<>
|
|
21
|
-
<S.HeadItem
|
|
22
|
-
level={isFiltering ? 1 : head.level}
|
|
23
|
-
tag={head.tag}
|
|
24
|
-
tabIndex={0}
|
|
25
|
-
role="button"
|
|
26
|
-
onClick={onHeadingClick(headingId)}
|
|
27
|
-
isSelected={selected === headingId}
|
|
28
|
-
isHidden={head.isHidden}
|
|
29
|
-
data-heading-id={headingId}
|
|
30
|
-
>
|
|
31
|
-
<S.HeadTag>
|
|
32
|
-
<div>{head.tag}</div>
|
|
33
|
-
</S.HeadTag>
|
|
34
|
-
<S.StyledTooltip content={head.isHidden ? "Hidden with CSS" : null}>
|
|
35
|
-
<S.HeadText>
|
|
36
|
-
<div>{head.text}</div>
|
|
37
|
-
{head.isHidden && (
|
|
38
|
-
<S.HiddenIcon>
|
|
39
|
-
<Icon name="hide" size="16" />
|
|
40
|
-
</S.HiddenIcon>
|
|
41
|
-
)}
|
|
42
|
-
</S.HeadText>
|
|
43
|
-
</S.StyledTooltip>
|
|
44
|
-
</S.HeadItem>
|
|
45
|
-
{head.children.map((child, childIndex) => (
|
|
46
|
-
<HeadingItem
|
|
47
|
-
key={`${uniqueKey}-child-${childIndex}`}
|
|
48
|
-
head={child}
|
|
49
|
-
index={childIndex}
|
|
50
|
-
isFiltering={isFiltering}
|
|
51
|
-
selected={selected}
|
|
52
|
-
onHeadingClick={onHeadingClick}
|
|
53
|
-
counter={counter}
|
|
54
|
-
parentPath={`${uniqueKey}-`}
|
|
55
|
-
/>
|
|
56
|
-
))}
|
|
57
|
-
</>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
interface IHeadingItemProps {
|
|
62
|
-
head: HeadingNode;
|
|
63
|
-
index: number;
|
|
64
|
-
isFiltering: boolean;
|
|
65
|
-
selected: number | null;
|
|
66
|
-
onHeadingClick: (id: number) => () => void;
|
|
67
|
-
counter: { value: number };
|
|
68
|
-
parentPath: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export default HeadingItem;
|