@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.
Files changed (99) hide show
  1. package/config/jest/componentsMock.js +5 -7
  2. package/package.json +2 -2
  3. package/src/__tests__/components/Browser/Browser.test.tsx +87 -438
  4. package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +3 -1
  5. package/src/__tests__/components/Fields/Button/Button.test.tsx +27 -29
  6. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +1 -1
  7. package/src/components/Browser/index.tsx +149 -294
  8. package/src/components/Browser/style.tsx +6 -75
  9. package/src/components/Button/index.tsx +1 -2
  10. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +4 -2
  11. package/src/components/Fields/AsyncSelect/style.tsx +0 -13
  12. package/src/components/Fields/FieldGroup/index.tsx +2 -5
  13. package/src/components/Fields/FieldGroup/style.tsx +7 -32
  14. package/src/components/Fields/HeadingField/index.tsx +2 -2
  15. package/src/components/Fields/HiddenField/style.tsx +1 -1
  16. package/src/components/Fields/NumberField/index.tsx +16 -15
  17. package/src/components/Fields/NumberField/style.tsx +0 -2
  18. package/src/components/Fields/ReferenceField/index.tsx +1 -1
  19. package/src/components/Fields/Select/index.tsx +1 -5
  20. package/src/components/Fields/Select/style.tsx +0 -56
  21. package/src/components/Fields/SummaryButton/index.tsx +9 -18
  22. package/src/components/Fields/SummaryButton/style.tsx +2 -1
  23. package/src/components/Fields/TagsField/index.tsx +9 -8
  24. package/src/components/Fields/UrlField/index.tsx +27 -26
  25. package/src/components/Fields/index.tsx +0 -2
  26. package/src/components/FloatingPanel/index.tsx +2 -5
  27. package/src/components/FloatingPanel/style.tsx +1 -2
  28. package/src/components/IconAction/index.tsx +1 -1
  29. package/src/components/MainWrapper/AppBar/index.tsx +1 -8
  30. package/src/components/MainWrapper/index.tsx +1 -7
  31. package/src/components/Notification/index.tsx +2 -2
  32. package/src/components/PageFinder/index.tsx +1 -1
  33. package/src/components/ResizePanel/index.tsx +3 -4
  34. package/src/components/ResizePanel/style.tsx +1 -1
  35. package/src/components/SearchField/style.tsx +2 -2
  36. package/src/components/SideModal/index.tsx +1 -2
  37. package/src/components/Tabs/index.tsx +4 -13
  38. package/src/components/Tabs/style.tsx +8 -7
  39. package/src/components/Toast/index.tsx +2 -4
  40. package/src/components/Tooltip/index.tsx +3 -4
  41. package/src/components/index.tsx +0 -8
  42. package/src/forms/fields.tsx +68 -70
  43. package/src/hooks/forms.tsx +1 -22
  44. package/src/hooks/index.tsx +3 -13
  45. package/src/hooks/modals.tsx +15 -103
  46. package/src/hooks/users.tsx +8 -25
  47. package/src/modules/Forms/atoms.tsx +2 -2
  48. package/src/modules/FramePreview/index.tsx +16 -55
  49. package/src/modules/FramePreview/style.tsx +2 -34
  50. package/src/modules/GlobalEditor/Editor/index.tsx +3 -37
  51. package/src/modules/GlobalEditor/PageBrowser/index.tsx +2 -19
  52. package/src/modules/GlobalEditor/Preview/index.tsx +2 -0
  53. package/src/modules/GlobalEditor/Preview/style.tsx +1 -1
  54. package/src/modules/GlobalEditor/index.tsx +57 -119
  55. package/src/modules/PageEditor/Editor/index.tsx +2 -33
  56. package/src/modules/PageEditor/PageBrowser/index.tsx +2 -20
  57. package/src/modules/PageEditor/Preview/index.tsx +2 -0
  58. package/src/modules/PageEditor/Preview/style.tsx +1 -1
  59. package/src/modules/PageEditor/atoms.tsx +1 -1
  60. package/src/modules/PageEditor/index.tsx +66 -130
  61. package/src/modules/PublicPreview/index.tsx +2 -5
  62. package/src/schemas/pages/GlobalPage.ts +70 -87
  63. package/src/schemas/pages/Page.ts +70 -87
  64. package/src/types/index.tsx +0 -12
  65. package/src/__tests__/components/Browser/Browser.utils.test.ts +0 -55
  66. package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +0 -158
  67. package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorsBanner.test.tsx +0 -90
  68. package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.test.tsx +0 -178
  69. package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +0 -150
  70. package/src/__tests__/components/KeywordsPreviewModal/KeywordItem/KeywordItem.test.tsx +0 -91
  71. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +0 -122
  72. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.utils.test.ts +0 -15
  73. package/src/__tests__/components/KeywordsPreviewModal/atoms.test.tsx +0 -101
  74. package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +0 -318
  75. package/src/__tests__/modules/FramePreview/FramePreview.utils.test.ts +0 -242
  76. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +0 -185
  77. package/src/components/Browser/utils.tsx +0 -13
  78. package/src/components/Fields/SEOPreview/index.tsx +0 -36
  79. package/src/components/Fields/SEOPreview/style.tsx +0 -24
  80. package/src/components/FloatingNote/index.tsx +0 -35
  81. package/src/components/FloatingNote/style.tsx +0 -26
  82. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +0 -85
  83. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/style.tsx +0 -80
  84. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +0 -57
  85. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +0 -82
  86. package/src/components/HeadingsPreviewModal/HeadingItem/index.tsx +0 -71
  87. package/src/components/HeadingsPreviewModal/HeadingItem/style.tsx +0 -77
  88. package/src/components/HeadingsPreviewModal/index.tsx +0 -146
  89. package/src/components/HeadingsPreviewModal/style.tsx +0 -82
  90. package/src/components/HeadingsPreviewModal/utils.tsx +0 -257
  91. package/src/components/KeywordsPreviewModal/KeywordItem/index.tsx +0 -46
  92. package/src/components/KeywordsPreviewModal/KeywordItem/style.tsx +0 -64
  93. package/src/components/KeywordsPreviewModal/atoms.tsx +0 -96
  94. package/src/components/KeywordsPreviewModal/index.tsx +0 -99
  95. package/src/components/KeywordsPreviewModal/style.tsx +0 -87
  96. package/src/components/KeywordsPreviewModal/utils.tsx +0 -22
  97. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +0 -113
  98. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +0 -24
  99. 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;