diff --git a/packages/analysis-report/src/AnalysisList.js b/packages/analysis-report/src/AnalysisList.js
index 5270756dabf..c407f5f25a9 100644
--- a/packages/analysis-report/src/AnalysisList.js
+++ b/packages/analysis-report/src/AnalysisList.js
@@ -93,6 +93,7 @@ export default function AnalysisList( props ) {
bulletColor={ color }
hasMarksButton={ result.hasMarks }
hasEditButton={ result.hasJumps }
+ hasAIFixes={ result.hasAIFixes }
ariaLabelMarks={ ariaLabelMarks }
ariaLabelEdit={ ariaLabelEdit }
pressed={ isMarkButtonPressed }
@@ -109,6 +110,7 @@ export default function AnalysisList( props ) {
onResultChange={ props.onResultChange }
markButtonFactory={ props.markButtonFactory }
shouldUpsellHighlighting={ props.shouldUpsellHighlighting }
+ renderAIFixesButton={ props.renderAIFixesButton }
renderHighlightingUpsell={ props.renderHighlightingUpsell }
/>;
} ) }
@@ -128,6 +130,7 @@ AnalysisList.propTypes = {
onResultChange: PropTypes.func,
shouldUpsellHighlighting: PropTypes.bool,
renderHighlightingUpsell: PropTypes.func,
+ renderAIFixesButton: PropTypes.func,
};
AnalysisList.defaultProps = {
@@ -141,4 +144,5 @@ AnalysisList.defaultProps = {
onResultChange: noop,
shouldUpsellHighlighting: false,
renderHighlightingUpsell: noop,
+ renderAIFixesButton: noop,
};
diff --git a/packages/analysis-report/src/AnalysisResult.js b/packages/analysis-report/src/AnalysisResult.js
index 060f62b1940..2fae915ccdc 100644
--- a/packages/analysis-report/src/AnalysisResult.js
+++ b/packages/analysis-report/src/AnalysisResult.js
@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useState } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { noop } from "lodash";
-
import { SvgIcon, IconButtonToggle, IconCTAEditButton, BetaBadge } from "@yoast/components";
import { strings } from "@yoast/helpers";
@@ -11,9 +10,18 @@ const { stripTagsFromHtmlString } = strings;
const ALLOWED_TAGS = [ "a", "b", "strong", "em", "i" ];
+const ResultButtonsContainer = styled.div`
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ // This gap value is half the gap value between assessment result list items, which is 12px.
+ gap: 6px;
+`;
+
const AnalysisResultBase = styled.li`
// This is the height of the IconButtonToggle.
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: flex;
align-items: flex-start;
@@ -125,18 +133,21 @@ const AnalysisResult = ( { markButtonFactory, ...props } ) => {
{ props.hasBetaBadgeLabel && }
- { marksButton }
- { props.renderHighlightingUpsell( isOpen, closeModal ) }
- {
- props.hasEditButton && props.isPremium &&
-
- }
+
+ { marksButton }
+ { props.renderHighlightingUpsell( isOpen, closeModal ) }
+ {
+ props.hasEditButton && props.isPremium &&
+
+ }
+ { props.renderAIFixesButton( props.hasAIFixes, props.id ) }
+
);
};
@@ -147,6 +158,8 @@ AnalysisResult.propTypes = {
bulletColor: PropTypes.string.isRequired,
hasMarksButton: PropTypes.bool.isRequired,
hasEditButton: PropTypes.bool,
+ hasAIButton: PropTypes.bool,
+ hasAIFixes: PropTypes.bool,
buttonIdMarks: PropTypes.string.isRequired,
buttonIdEdit: PropTypes.string,
pressed: PropTypes.bool.isRequired,
@@ -168,6 +181,7 @@ AnalysisResult.propTypes = {
] ),
shouldUpsellHighlighting: PropTypes.bool,
renderHighlightingUpsell: PropTypes.func,
+ renderAIFixesButton: PropTypes.func,
};
AnalysisResult.defaultProps = {
@@ -177,6 +191,7 @@ AnalysisResult.defaultProps = {
editButtonClassName: "",
hasBetaBadgeLabel: false,
hasEditButton: false,
+ hasAIFixes: false,
buttonIdEdit: "",
ariaLabelEdit: "",
onButtonClickEdit: noop,
@@ -186,6 +201,7 @@ AnalysisResult.defaultProps = {
marker: noop,
shouldUpsellHighlighting: false,
renderHighlightingUpsell: noop,
+ renderAIFixesButton: noop,
};
export default AnalysisResult;
diff --git a/packages/analysis-report/src/ContentAnalysis.js b/packages/analysis-report/src/ContentAnalysis.js
index f21ee54e80e..29206cfda62 100644
--- a/packages/analysis-report/src/ContentAnalysis.js
+++ b/packages/analysis-report/src/ContentAnalysis.js
@@ -20,13 +20,10 @@ const ContentAnalysisContainer = styled.div`
const StyledCollapsible = styled( Collapsible )`
margin-bottom: 8px;
- button:first-child svg {
- margin: -2px 8px 0 -2px; // Compensate icon size set to 18px.
- }
-
${ StyledIconsButton } {
padding: 8px 0;
- color: ${ colors.$color_blue }
+ color: ${ colors.$color_blue };
+ margin: -2px 8px 0 -2px; // Compensate icon size set to 18px.
}
`;
@@ -37,7 +34,7 @@ const StyledCollapsible = styled( Collapsible )`
*/
class ContentAnalysis extends React.Component {
/**
- * Renders a Collapsible component with a liset of Analysis results.
+ * Renders a Collapsible component with a list of Analysis results.
*
* @param {string} title The title of the collapsible section.
* @param {number} headingLevel Heading level: 1 for h1, 2 for h2, etc.
@@ -65,6 +62,7 @@ class ContentAnalysis extends React.Component {
markButtonFactory={ this.props.markButtonFactory }
onMarksButtonClick={ this.props.onMarkButtonClick }
onEditButtonClick={ this.props.onEditButtonClick }
+ renderAIFixesButton={ this.props.renderAIFixesButton }
isPremium={ this.props.isPremium }
onResultChange={ this.props.onResultChange }
shouldUpsellHighlighting={ this.props.shouldUpsellHighlighting }
@@ -156,6 +154,7 @@ ContentAnalysis.propTypes = {
onResultChange: PropTypes.func,
shouldUpsellHighlighting: PropTypes.bool,
renderHighlightingUpsell: PropTypes.func,
+ renderAIFixesButton: PropTypes.func,
};
ContentAnalysis.defaultProps = {
@@ -178,6 +177,7 @@ ContentAnalysis.defaultProps = {
onResultChange: () => {},
shouldUpsellHighlighting: false,
renderHighlightingUpsell: () => {},
+ renderAIFixesButton: () => {},
};
export default ContentAnalysis;
diff --git a/packages/analysis-report/tests/__snapshots__/AnalysisResultTest.js.snap b/packages/analysis-report/tests/__snapshots__/AnalysisResultTest.js.snap
index f9fa3b24728..6d615cd7f9f 100644
--- a/packages/analysis-report/tests/__snapshots__/AnalysisResultTest.js.snap
+++ b/packages/analysis-report/tests/__snapshots__/AnalysisResultTest.js.snap
@@ -9,7 +9,7 @@ exports[`the AnalysisResult component matches the snapshot 1`] = `
flex: none;
}
-.c5 {
+.c6 {
width: 18px;
height: 18px;
-webkit-flex: none;
@@ -17,7 +17,7 @@ exports[`the AnalysisResult component matches the snapshot 1`] = `
flex: none;
}
-.c4 {
+.c5 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -38,22 +38,30 @@ exports[`the AnalysisResult component matches the snapshot 1`] = `
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: 23px;
+ height: 24px;
}
-.c4:hover {
+.c5:hover {
border-color: #fff;
}
-.c4:disabled {
+.c5:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -106,30 +114,34 @@ exports[`the AnalysisResult component matches the snapshot 1`] = `
}
/>
-
+
+
+
`;
@@ -142,7 +154,7 @@ exports[`the AnalysisResult component with a activated premium matches the snaps
flex: none;
}
-.c5 {
+.c6 {
width: 18px;
height: 18px;
-webkit-flex: none;
@@ -150,25 +162,44 @@ exports[`the AnalysisResult component with a activated premium matches the snaps
flex: none;
}
-.c4 {
+.c5 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
box-sizing: border-box;
min-width: 32px;
- display: inline-block;
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
border: 1px solid #ccc;
background-color: #f7f7f7;
box-shadow: #ccc;
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: "24px";
+ height: 24px;
}
-.c4:hover {
+.c5:hover {
border-color: #fff;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -226,28 +257,32 @@ exports[`the AnalysisResult component with a activated premium matches the snaps
}
/>
-
+
+
+
`;
@@ -260,8 +295,16 @@ exports[`the AnalysisResult component with a beta badge label matches the snapsh
flex: none;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -319,6 +362,9 @@ exports[`the AnalysisResult component with a beta badge label matches the snapsh
}
/>
+
`;
@@ -331,7 +377,7 @@ exports[`the AnalysisResult component with disabled buttons matches the snapshot
flex: none;
}
-.c5 {
+.c6 {
width: 18px;
height: 18px;
-webkit-flex: none;
@@ -339,7 +385,7 @@ exports[`the AnalysisResult component with disabled buttons matches the snapshot
flex: none;
}
-.c4 {
+.c5 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -360,22 +406,30 @@ exports[`the AnalysisResult component with disabled buttons matches the snapshot
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: 23px;
+ height: 24px;
}
-.c4:hover {
+.c5:hover {
border-color: #fff;
}
-.c4:disabled {
+.c5:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -428,30 +482,34 @@ exports[`the AnalysisResult component with disabled buttons matches the snapshot
}
/>
-
+
+
+
`;
@@ -464,8 +522,16 @@ exports[`the AnalysisResult component with hidden buttons matches the snapshot 1
flex: none;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -518,6 +584,9 @@ exports[`the AnalysisResult component with hidden buttons matches the snapshot 1
}
/>
+
`;
@@ -530,7 +599,7 @@ exports[`the AnalysisResult component with html in the text matches the snapshot
flex: none;
}
-.c5 {
+.c6 {
width: 18px;
height: 18px;
-webkit-flex: none;
@@ -538,7 +607,7 @@ exports[`the AnalysisResult component with html in the text matches the snapshot
flex: none;
}
-.c4 {
+.c5 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -559,22 +628,30 @@ exports[`the AnalysisResult component with html in the text matches the snapshot
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: 23px;
+ height: 24px;
}
-.c4:hover {
+.c5:hover {
border-color: #fff;
}
-.c4:disabled {
+.c5:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -627,30 +704,34 @@ exports[`the AnalysisResult component with html in the text matches the snapshot
}
/>
-
+
+
+
`;
@@ -663,8 +744,16 @@ exports[`the AnalysisResult component with suppressed text matches the snapshot
flex: none;
}
+.c4 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c0 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -717,5 +806,8 @@ exports[`the AnalysisResult component with suppressed text matches the snapshot
}
/>
+
`;
diff --git a/packages/analysis-report/tests/__snapshots__/ContentAnalysisTest.js.snap b/packages/analysis-report/tests/__snapshots__/ContentAnalysisTest.js.snap
index d60aae3478f..30a93f17d8b 100644
--- a/packages/analysis-report/tests/__snapshots__/ContentAnalysisTest.js.snap
+++ b/packages/analysis-report/tests/__snapshots__/ContentAnalysisTest.js.snap
@@ -139,7 +139,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
margin-left: 8px;
}
-.c21 {
+.c22 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -163,19 +163,27 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
height: 24px;
}
-.c21:hover {
+.c22:hover {
border-color: #fff;
}
-.c21:disabled {
+.c22:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -216,13 +224,10 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -238,7 +243,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -246,12 +251,12 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -259,12 +264,12 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -272,12 +277,12 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c24 {
+.c25 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -285,7 +290,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
color: #1e1e1e !important;
}
-.c24 .c13 {
+.c25 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -371,6 +376,9 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
}
/>
+
@@ -378,7 +386,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
className="c1 c2"
>
-
+
+
+
@@ -475,7 +487,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
className="c1 c2"
>
+
@@ -548,7 +563,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
className="c1 c2"
>
+
@@ -621,7 +639,7 @@ exports[`ContentAnalysis the ContentAnalysis component with custom result catego
className="c1 c2"
>
+
-
+
+
+
@@ -885,7 +910,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
margin-left: 8px;
}
-.c21 {
+.c22 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -909,19 +934,27 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
height: 24px;
}
-.c21:hover {
+.c22:hover {
border-color: #fff;
}
-.c21:disabled {
+.c22:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -962,13 +995,10 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -984,7 +1014,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -992,12 +1022,12 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1005,12 +1035,12 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1018,12 +1048,12 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c24 {
+.c25 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1031,7 +1061,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
color: #1e1e1e !important;
}
-.c24 .c13 {
+.c25 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -1117,6 +1147,9 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
}
/>
+
@@ -1124,7 +1157,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
className="c1 c2"
>
-
+
+
+
@@ -1221,7 +1258,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
className="c1 c2"
>
+
@@ -1294,7 +1334,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
className="c1 c2"
>
+
@@ -1367,7 +1410,7 @@ exports[`ContentAnalysis the ContentAnalysis component with disabled buttons mat
className="c1 c2"
>
+
-
+
+
+
@@ -1631,8 +1681,16 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
margin-left: 8px;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -1673,13 +1731,10 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -1695,7 +1750,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1703,12 +1758,12 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c21 {
+.c22 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1716,12 +1771,12 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
color: #1e1e1e !important;
}
-.c21 .c13 {
+.c22 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1729,12 +1784,12 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -1742,7 +1797,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -1828,6 +1883,9 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
}
/>
+
@@ -1835,7 +1893,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
className="c1 c2"
>
+
@@ -1908,7 +1969,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
className="c1 c2"
>
+
@@ -1981,7 +2045,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
className="c1 c2"
>
+
@@ -2054,7 +2121,7 @@ exports[`ContentAnalysis the ContentAnalysis component with hidden buttons match
className="c1 c2"
>
+
+
@@ -2294,7 +2367,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
margin-left: 8px;
}
-.c21 {
+.c22 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -2318,19 +2391,27 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
height: 24px;
}
-.c21:hover {
+.c22:hover {
border-color: #fff;
}
-.c21:disabled {
+.c22:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -2371,13 +2452,10 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -2393,7 +2471,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -2401,12 +2479,12 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -2414,12 +2492,12 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -2427,12 +2505,12 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c24 {
+.c25 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -2440,7 +2518,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
color: #1e1e1e !important;
}
-.c24 .c13 {
+.c25 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -2526,6 +2604,9 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
}
/>
+
@@ -2533,7 +2614,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
className="c1 c2"
>
-
+
+
+
@@ -2630,7 +2715,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
className="c1 c2"
>
+
@@ -2703,7 +2791,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
className="c1 c2"
>
+
@@ -2776,7 +2867,7 @@ exports[`ContentAnalysis the ContentAnalysis component with specified header lev
className="c1 c2"
>
+
-
+
+
+
@@ -3040,7 +3138,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
margin-left: 8px;
}
-.c22 {
+.c23 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -3064,19 +3162,27 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
height: 24px;
}
-.c22:hover {
+.c23:hover {
border-color: #fff;
}
-.c22:disabled {
+.c23:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -3101,7 +3207,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: inherit;
}
-.c21 {
+.c22 {
margin: 0 16px 0 0;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
@@ -3125,13 +3231,10 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -3147,7 +3250,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3155,12 +3258,12 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3168,12 +3271,12 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c24 {
+.c25 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3181,12 +3284,12 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: #1e1e1e !important;
}
-.c24 .c13 {
+.c25 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c25 {
+.c26 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3194,7 +3297,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
color: #1e1e1e !important;
}
-.c25 .c13 {
+.c26 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -3280,6 +3383,9 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
}
/>
+
@@ -3287,7 +3393,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
className="c1 c2"
>
+
-
+
+
+
@@ -3413,7 +3526,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
className="c1 c2"
>
+
@@ -3486,7 +3602,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
className="c1 c2"
>
+
@@ -3559,7 +3678,7 @@ exports[`ContentAnalysis the ContentAnalysis component with upsell results match
className="c1 c2"
>
+
-
+
+
+
@@ -3823,7 +3949,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
margin-left: 8px;
}
-.c21 {
+.c22 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -3847,19 +3973,27 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
height: 24px;
}
-.c21:hover {
+.c22:hover {
border-color: #fff;
}
-.c21:disabled {
+.c22:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -3900,13 +4034,10 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -3922,7 +4053,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3930,12 +4061,12 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3943,12 +4074,12 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c23 {
+.c24 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3956,12 +4087,12 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
color: #1e1e1e !important;
}
-.c23 .c13 {
+.c24 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c24 {
+.c25 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -3969,7 +4100,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
color: #1e1e1e !important;
}
-.c24 .c13 {
+.c25 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -4055,6 +4186,9 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
}
/>
+
@@ -4062,7 +4196,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
className="c1 c2"
>
-
+
+
+
@@ -4159,7 +4297,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
className="c1 c2"
>
+
@@ -4232,7 +4373,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
className="c1 c2"
>
+
@@ -4305,7 +4449,7 @@ exports[`ContentAnalysis the ContentAnalysis component without language notice m
className="c1 c2"
>
+
-
+
+
+
@@ -4569,7 +4720,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
margin-left: 8px;
}
-.c22 {
+.c23 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -4593,19 +4744,27 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
height: 24px;
}
-.c22:hover {
+.c23:hover {
border-color: #fff;
}
-.c22:disabled {
+.c23:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -4646,13 +4805,10 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -4668,7 +4824,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -4676,12 +4832,12 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c21 {
+.c22 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -4689,7 +4845,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
color: #1e1e1e !important;
}
-.c21 .c13 {
+.c22 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -4775,6 +4931,9 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
}
/>
+
@@ -4782,7 +4941,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
className="c1 c2"
>
+
@@ -4855,7 +5017,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and cons
className="c1 c2"
>
+
-
+
+
+
@@ -5119,7 +5288,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
margin-left: 8px;
}
-.c22 {
+.c23 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -5143,19 +5312,27 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
height: 24px;
}
-.c22:hover {
+.c23:hover {
border-color: #fff;
}
-.c22:disabled {
+.c23:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -5196,13 +5373,10 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -5218,7 +5392,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -5226,12 +5400,12 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c21 {
+.c22 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -5239,7 +5413,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
color: #1e1e1e !important;
}
-.c21 .c13 {
+.c22 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -5325,6 +5499,9 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
}
/>
+
@@ -5332,7 +5509,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
className="c1 c2"
>
+
@@ -5405,7 +5585,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems and impr
className="c1 c2"
>
+
-
+
+
+
@@ -5669,7 +5856,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
margin-left: 8px;
}
-.c23 {
+.c24 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -5693,19 +5880,27 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
height: 24px;
}
-.c23:hover {
+.c24:hover {
border-color: #fff;
}
-.c23:disabled {
+.c24:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -5746,13 +5941,10 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -5768,7 +5960,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -5776,12 +5968,12 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c21 {
+.c22 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -5789,12 +5981,12 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
color: #1e1e1e !important;
}
-.c21 .c13 {
+.c22 .c13 {
font-weight: 500;
color: #1e1e1e;
}
-.c22 {
+.c23 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -5802,7 +5994,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
color: #1e1e1e !important;
}
-.c22 .c13 {
+.c23 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -5888,6 +6080,9 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
}
/>
+
@@ -5895,7 +6090,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
className="c1 c2"
>
+
@@ -5968,7 +6166,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
className="c1 c2"
>
+
@@ -6041,7 +6242,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems matches
className="c1 c2"
>
+
-
+
+
+
@@ -6305,7 +6513,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
margin-left: 8px;
}
-.c21 {
+.c22 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -6329,19 +6537,27 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
height: 24px;
}
-.c21:hover {
+.c22:hover {
border-color: #fff;
}
-.c21:disabled {
+.c22:disabled {
background-color: #f7f7f7;
box-shadow: none;
border: none;
cursor: default;
}
+.c20 {
+ display: grid;
+ grid-template-rows: 1fr;
+ max-width: 32px;
+ gap: 6px;
+}
+
.c16 {
min-height: 24px;
+ margin-bottom: 12px;
padding: 0;
display: -webkit-box;
display: -webkit-flex;
@@ -6382,13 +6598,10 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
margin-bottom: 8px;
}
-.c2 button:first-child svg {
- margin: -2px 8px 0 -2px;
-}
-
.c2 .c9 {
padding: 8px 0;
color: #0066cd;
+ margin: -2px 8px 0 -2px;
}
.c3 {
@@ -6404,7 +6617,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
color: #1e1e1e;
}
-.c20 {
+.c21 {
margin: 0 !important;
padding: 0 !important;
font-size: 13px !important;
@@ -6412,7 +6625,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
color: #1e1e1e !important;
}
-.c20 .c13 {
+.c21 .c13 {
font-weight: 500;
color: #1e1e1e;
}
@@ -6498,6 +6711,9 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
}
/>
+
@@ -6505,7 +6721,7 @@ exports[`ContentAnalysis the ContentAnalysis component without problems, improve
className="c1 c2"
>
+
-
+
+
+
diff --git a/packages/components/src/IconAIFixesButton.js b/packages/components/src/IconAIFixesButton.js
new file mode 100644
index 00000000000..eaaeb65fe44
--- /dev/null
+++ b/packages/components/src/IconAIFixesButton.js
@@ -0,0 +1,67 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+/* Yoast dependencies */
+import { colors } from "@yoast/style-guide";
+
+/* Internal dependencies */
+import IconButtonBase from "./IconButtonBase";
+
+/**
+ * Returns the IconAIFixesButton component.
+ *
+ * @param {Object} props Component props.
+ *
+ * @returns {JSX.Element} IconAIFixesButton component.
+ */
+const IconAIFixesButton = function( props ) {
+ return (
+
+ { props.children }
+
+ );
+};
+
+IconAIFixesButton.propTypes = {
+ children: PropTypes.node,
+ id: PropTypes.string.isRequired,
+ ariaLabel: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ unpressedBoxShadowColor: PropTypes.string,
+ pressedBoxShadowColor: PropTypes.string,
+ pressedBackground: PropTypes.string,
+ unpressedBackground: PropTypes.string,
+ pressedIconColor: PropTypes.string,
+ unpressedIconColor: PropTypes.string,
+ pressed: PropTypes.bool.isRequired,
+ hoverBorderColor: PropTypes.string,
+ className: PropTypes.string,
+};
+
+IconAIFixesButton.defaultProps = {
+ unpressedBoxShadowColor: colors.$color_button_border,
+ pressedBoxShadowColor: colors.$color_purple,
+ pressedBackground: colors.$color_pink_dark,
+ unpressedBackground: colors.$color_button,
+ pressedIconColor: colors.$color_white,
+ unpressedIconColor: colors.$color_button_text,
+ hoverBorderColor: colors.$color_white,
+};
+
+export default IconAIFixesButton;
diff --git a/packages/components/src/IconButtonBase.js b/packages/components/src/IconButtonBase.js
new file mode 100644
index 00000000000..340e495aacd
--- /dev/null
+++ b/packages/components/src/IconButtonBase.js
@@ -0,0 +1,31 @@
+import styled from "styled-components";
+import { colors, rgba } from "@yoast/style-guide";
+
+const IconButtonBase = styled.button`
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ min-width: 32px;
+ display: inline-flex;
+ border: 1px solid ${ colors.$color_button_border };
+ background-color: ${ props => props.pressed ? props.pressedBackground : props.unpressedBackground };
+ box-shadow: ${ props => props.pressed
+ ? `inset 0 2px 0 ${ rgba( props.pressedBoxShadowColor, 0.7 ) }`
+ : `0 1px 0 ${ rgba( props.unpressedBoxShadowColor, 0.7 ) }` };
+ border-radius: 3px;
+ cursor: pointer;
+ padding: 0;
+ height: 24px;
+
+ &:hover {
+ border-color: ${ props => props.hoverBorderColor };
+ }
+ &:disabled {
+ background-color: ${ props => props.unpressedBackground };
+ box-shadow: none;
+ border: none;
+ cursor: default;
+ }
+`;
+
+export default IconButtonBase;
diff --git a/packages/components/src/IconButtonToggle.js b/packages/components/src/IconButtonToggle.js
index bc6bf13fbe8..ce42c3b5d39 100644
--- a/packages/components/src/IconButtonToggle.js
+++ b/packages/components/src/IconButtonToggle.js
@@ -1,39 +1,11 @@
import React from "react";
-import styled from "styled-components";
import PropTypes from "prop-types";
-
/* Yoast dependencies */
-import { colors, rgba } from "@yoast/style-guide";
-
+import { colors } from "@yoast/style-guide";
/* Internal dependencies */
import SvgIcon from "./SvgIcon";
+import IconButtonBase from "./IconButtonBase";
-const IconButtonBase = styled.button`
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- min-width: 32px;
- display: inline-flex;
- border: 1px solid ${ colors.$color_button_border };
- background-color: ${ props => props.pressed ? props.pressedBackground : props.unpressedBackground };
- box-shadow: ${ props => props.pressed
- ? `inset 0 2px 0 ${ rgba( props.pressedBoxShadowColor, 0.7 ) }`
- : `0 1px 0 ${ rgba( props.unpressedBoxShadowColor, 0.7 ) }` };
- border-radius: 3px;
- cursor: pointer;
- padding: 0;
- height: ${ props => props.pressed ? "23px" : "24px" };
-
- &:hover {
- border-color: ${ props => props.hoverBorderColor };
- }
- &:disabled {
- background-color: ${ props => props.unpressedBackground };
- box-shadow: none;
- border: none;
- cursor: default;
- }
-`;
/**
* Returns the ChangingIconButton component.
diff --git a/packages/components/src/IconCTAEditButton.js b/packages/components/src/IconCTAEditButton.js
index c591ce70ab1..cdb04292651 100644
--- a/packages/components/src/IconCTAEditButton.js
+++ b/packages/components/src/IconCTAEditButton.js
@@ -9,16 +9,18 @@ import { colors } from "@yoast/style-guide";
import SvgIcon from "./SvgIcon";
const IconButtonBase = styled.button`
+ align-items: center;
+ justify-content: center;
box-sizing: border-box;
min-width: 32px;
- display: inline-block;
+ display: inline-flex;
border: 1px solid ${ colors.$color_button_border };
background-color: ${ props => props.background };
box-shadow: ${ props => props.boxShadowColor };
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: "24px";
+ height: 24px;
&:hover {
border-color: ${ props => props.hoverBorderColor };
}
diff --git a/packages/components/src/index.js b/packages/components/src/index.js
index c9538b44998..b15c4e05387 100644
--- a/packages/components/src/index.js
+++ b/packages/components/src/index.js
@@ -49,6 +49,7 @@ export {
wrapInHeading,
} from "./Collapsible";
+export { default as IconAIFixesButton } from "./IconAIFixesButton";
export { default as Alert } from "./Alert";
export { default as ArticleList } from "./ArticleList";
export { default as Card, FullHeightCard } from "./Card";
@@ -61,6 +62,7 @@ export { default as ErrorBoundary } from "./ErrorBoundary";
export { default as Heading } from "./Heading";
export { default as HelpText } from "./HelpText";
export { default as Icon } from "./Icon";
+export { default as IconButtonBase } from "./IconButtonBase";
export { default as IconButtonToggle } from "./IconButtonToggle.js";
export { default as IconCTAEditButton } from "./IconCTAEditButton.js";
export { default as IFrame } from "./IFrame";
diff --git a/packages/components/tests/IconAIFixesButtonTest.js b/packages/components/tests/IconAIFixesButtonTest.js
new file mode 100644
index 00000000000..83d9b0bee13
--- /dev/null
+++ b/packages/components/tests/IconAIFixesButtonTest.js
@@ -0,0 +1,37 @@
+import React from "react";
+import renderer from "react-test-renderer";
+
+import IconAIFixesButton from "../src/IconAIFixesButton";
+
+test( "the unpressed IconAIFixesButton matches the snapshot", () => {
+ const component = renderer.create(
+ {} }
+ className={ "yoast-tooltip yoast-tooltip-w" }
+ />
+ );
+
+ const tree = component.toJSON();
+ expect( tree ).toMatchSnapshot();
+} );
+
+test( "the pressed IconAIFixesButton matches the snapshot", () => {
+ const component = renderer.create(
+ {} }
+ className={ "yoast-tooltip yoast-tooltip-w" }
+ />
+ );
+
+ const tree = component.toJSON();
+ expect( tree ).toMatchSnapshot();
+} );
+
diff --git a/packages/components/tests/__snapshots__/IconAIFixesButtonTest.js.snap b/packages/components/tests/__snapshots__/IconAIFixesButtonTest.js.snap
new file mode 100644
index 00000000000..a109f990813
--- /dev/null
+++ b/packages/components/tests/__snapshots__/IconAIFixesButtonTest.js.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`the pressed IconAIFixesButton matches the snapshot 1`] = `
+.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ box-sizing: border-box;
+ min-width: 32px;
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ border: 1px solid #ccc;
+ background-color: #a4286a;
+ box-shadow: inset 0 2px 0 rgba( 93,35,122,0.7 );
+ border-radius: 3px;
+ cursor: pointer;
+ padding: 0;
+ height: 24px;
+}
+
+.c0:hover {
+ border-color: #fff;
+}
+
+.c0:disabled {
+ background-color: #f7f7f7;
+ box-shadow: none;
+ border: none;
+ cursor: default;
+}
+
+
+`;
+
+exports[`the unpressed IconAIFixesButton matches the snapshot 1`] = `
+.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ box-sizing: border-box;
+ min-width: 32px;
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ border: 1px solid #ccc;
+ background-color: #f7f7f7;
+ box-shadow: 0 1px 0 rgba( 204,204,204,0.7 );
+ border-radius: 3px;
+ cursor: pointer;
+ padding: 0;
+ height: 24px;
+}
+
+.c0:hover {
+ border-color: #fff;
+}
+
+.c0:disabled {
+ background-color: #f7f7f7;
+ box-shadow: none;
+ border: none;
+ cursor: default;
+}
+
+
+`;
diff --git a/packages/components/tests/__snapshots__/IconButtonToggleTest.js.snap b/packages/components/tests/__snapshots__/IconButtonToggleTest.js.snap
index 0adcef6b6e0..7ffe9db8b5c 100644
--- a/packages/components/tests/__snapshots__/IconButtonToggleTest.js.snap
+++ b/packages/components/tests/__snapshots__/IconButtonToggleTest.js.snap
@@ -100,7 +100,7 @@ exports[`the pressed IconButtonToggle matches the snapshot 1`] = `
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: 23px;
+ height: 24px;
}
.c0:hover {
diff --git a/packages/components/tests/__snapshots__/IconCTAEditButtonTest.js.snap b/packages/components/tests/__snapshots__/IconCTAEditButtonTest.js.snap
index 15f396cef0b..110cdf4c53e 100644
--- a/packages/components/tests/__snapshots__/IconCTAEditButtonTest.js.snap
+++ b/packages/components/tests/__snapshots__/IconCTAEditButtonTest.js.snap
@@ -10,16 +10,27 @@ exports[`the IconCTAEditButton matches the snapshot 1`] = `
}
.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
box-sizing: border-box;
min-width: 32px;
- display: inline-block;
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
border: 1px solid #ccc;
background-color: #f7f7f7;
box-shadow: #ccc;
border-radius: 3px;
cursor: pointer;
padding: 0;
- height: "24px";
+ height: 24px;
}
.c0:hover {
diff --git a/packages/js/src/ai-assessment-fixes/components/AIAssessmentFixesButton.js b/packages/js/src/ai-assessment-fixes/components/AIAssessmentFixesButton.js
new file mode 100644
index 00000000000..3b732d08ead
--- /dev/null
+++ b/packages/js/src/ai-assessment-fixes/components/AIAssessmentFixesButton.js
@@ -0,0 +1,110 @@
+import { SparklesIcon } from "@heroicons/react/outline";
+import PropTypes from "prop-types";
+import { __ } from "@wordpress/i18n";
+import { useCallback, useRef } from "@wordpress/element";
+import { doAction } from "@wordpress/hooks";
+import { useSelect, useDispatch } from "@wordpress/data";
+/* Yoast dependencies */
+import { colors } from "@yoast/style-guide";
+import { IconAIFixesButton } from "@yoast/components";
+import { Modal, useToggleState } from "@yoast/ui-library";
+import { Paper } from "yoastseo";
+
+/* Internal dependencies */
+import { ModalContent } from "../../ai-generator/components/modal-content";
+
+/**
+ * The AI Assessment Fixes button component.
+ *
+ * @param {string} id The assessment ID for which the AI fixes should be applied to.
+ * @param {boolean} isPremium Whether the premium add-on is active.
+ *
+ * @returns {JSX.Element} The AI Assessment Fixes button.
+ */
+const AIAssessmentFixesButton = ( { id, isPremium } ) => {
+ const aiFixesId = id + "AIFixes";
+ const ariaLabel = __( "Fix with AI", "wordpress-seo" );
+ const [ isModalOpen, , , setIsModalOpenTrue, setIsModalOpenFalse ] = useToggleState( false );
+ const activeAIButtonId = useSelect( select => select( "yoast-seo/editor" ).getActiveAIFixesButton(), [] );
+ const activeMarker = useSelect( select => select( "yoast-seo/editor" ).getActiveMarker(), [] );
+ const { setActiveAIFixesButton, setActiveMarker, setMarkerPauseStatus } = useDispatch( "yoast-seo/editor" );
+ const focusElementRef = useRef( null );
+
+ /**
+ * Handles the button press state.
+ * @returns {void}
+ */
+ const handlePressedButton = () => {
+ // If there is an active marker when the AI fixes button is clicked, remove it.
+ if ( activeMarker ) {
+ setActiveMarker( null );
+ setMarkerPauseStatus( false );
+ // Remove highlighting from the editor.
+ window.YoastSEO.analysis.applyMarks( new Paper( "", {} ), [] );
+ }
+
+ /* If the current pressed button ID is the same as the active AI button id,
+ we want to set the active AI button to null. Otherwise, update the active AI button ID. */
+ if ( aiFixesId === activeAIButtonId ) {
+ setActiveAIFixesButton( null );
+ } else {
+ setActiveAIFixesButton( aiFixesId );
+ }
+ };
+
+ const handleClick = useCallback( () => {
+ if ( isPremium ) {
+ doAction( "yoast.ai.fixAssessments", aiFixesId );
+ /* Only handle the pressed button state in Premium.
+ We don't want to change the background color of the button and other styling when it's pressed in Free.
+ This is because clicking on the button in Free will open the modal, and the button will not be in a pressed state. */
+ handlePressedButton();
+ } else {
+ setIsModalOpenTrue();
+ }
+ }, [ handlePressedButton, setIsModalOpenTrue ] );
+
+ // The button is pressed when the active AI button id is the same as the current button id.
+ const isButtonPressed = activeAIButtonId === aiFixesId;
+ /* This color selection when the button is pressed/unpressed is in line with the design of the highlighting button.
+ In Premium: when the button is pressed, the icon color is white. When the button is unpressed, the color is grey.
+ In Free: the icon color is always grey. */
+ const iconColor = isButtonPressed ? colors.$color_white : colors.$color_button_text;
+ // Don't show the tooltip when the button is pressed.
+ const className = isButtonPressed ? "" : "yoast-tooltip yoast-tooltip-w";
+
+ return (
+ <>
+
+
+ {
+ // We put the logic for the Upsell component in place.
+ // The Modal below is only a placeholder/mock. When we have the design for the real upsell, the modal should be replaced.
+ isModalOpen &&
+
+
+
+
+ }
+
+ >
+ );
+};
+
+AIAssessmentFixesButton.propTypes = {
+ id: PropTypes.string.isRequired,
+ isPremium: PropTypes.bool,
+};
+
+AIAssessmentFixesButton.defaultProps = {
+ isPremium: false,
+};
+
+export default AIAssessmentFixesButton;
+
diff --git a/packages/js/src/components/contentAnalysis/Results.js b/packages/js/src/components/contentAnalysis/Results.js
index bc323b1b5b0..be5a2413cdb 100644
--- a/packages/js/src/components/contentAnalysis/Results.js
+++ b/packages/js/src/components/contentAnalysis/Results.js
@@ -140,6 +140,11 @@ class Results extends Component {
// To see a difference between keyphrases: Prepend the keyword key when applicable.
const markerId = this.props.keywordKey.length > 0 ? `${this.props.keywordKey}:${id}` : id;
+ // If AI Fixes button is active while the Mark button is clicked, set the active AI Fixes button ID to null.
+ if ( this.props.activeAIFixesButton ) {
+ this.props.setActiveAIFixesButton( null );
+ }
+
// If marker button is clicked while active, disable markers.
if ( markerId === this.props.activeMarker ) {
this.deactivateMarker();
@@ -367,6 +372,7 @@ class Results extends Component {
resultCategoryLabels={ labels }
onResultChange={ this.handleResultsChange }
shouldUpsellHighlighting={ this.props.shouldUpsellHighlighting }
+ renderAIFixesButton={ this.props.renderAIFixesButton }
renderHighlightingUpsell={ this.renderHighlightingUpsell }
markButtonFactory={ this.createMarkButton }
/>
@@ -383,7 +389,9 @@ Results.propTypes = {
marksButtonStatus: PropTypes.oneOf( [ "enabled", "disabled", "hidden" ] ),
setActiveMarker: PropTypes.func.isRequired,
setMarkerPauseStatus: PropTypes.func.isRequired,
+ setActiveAIFixesButton: PropTypes.func.isRequired,
activeMarker: PropTypes.string,
+ activeAIFixesButton: PropTypes.string,
keywordKey: PropTypes.string,
location: PropTypes.string,
isPremium: PropTypes.bool,
@@ -397,6 +405,7 @@ Results.propTypes = {
shortcodesForParsing: PropTypes.array,
shouldUpsellHighlighting: PropTypes.bool,
highlightingUpsellLink: PropTypes.string,
+ renderAIFixesButton: PropTypes.func,
};
Results.defaultProps = {
@@ -406,6 +415,7 @@ Results.defaultProps = {
marksButtonClassName: "",
editButtonClassName: "",
activeMarker: null,
+ activeAIFixesButton: null,
keywordKey: "",
location: "",
isPremium: false,
@@ -413,6 +423,7 @@ Results.defaultProps = {
shortcodesForParsing: [],
shouldUpsellHighlighting: false,
highlightingUpsellLink: "",
+ renderAIFixesButton: () => {},
};
export default Results;
diff --git a/packages/js/src/components/contentAnalysis/SeoAnalysis.js b/packages/js/src/components/contentAnalysis/SeoAnalysis.js
index 1853924c592..19e8bd00c40 100644
--- a/packages/js/src/components/contentAnalysis/SeoAnalysis.js
+++ b/packages/js/src/components/contentAnalysis/SeoAnalysis.js
@@ -20,6 +20,8 @@ import ScoreIconPortal from "../portals/ScoreIconPortal";
import SidebarCollapsible from "../SidebarCollapsible";
import SynonymSlot from "../slots/SynonymSlot";
import { getIconForScore } from "./mapResults";
+import isBlockEditor from "../../helpers/isBlockEditor";
+import AIAssessmentFixesButton from "../../ai-assessment-fixes/components/AIAssessmentFixesButton";
const AnalysisHeader = styled.span`
font-size: 1em;
@@ -189,6 +191,23 @@ class SeoAnalysis extends Component {
];
}
+ /**
+ * Renders the AI Assessment Fixes button.
+ *
+ * @param {boolean} hasAIFixes Whether the assessment has AI fixes or not.
+ * @param {string} id The assessment ID for which the AI fixes should be applied to.
+ *
+ * @returns {JSX.Element} The AI Assessment Fixes button.
+ */
+ renderAIFixesButton = ( hasAIFixes, id ) => {
+ const isPremium = getL10nObject().isPremium;
+
+ // The reason of adding the check if Elementor is active or not is because `isBlockEditor` method also returns `true` for Elementor.
+ return hasAIFixes && isBlockEditor() && ! this.props.isElementor && (
+
+ );
+ };
+
/**
* Renders the SEO Analysis component.
*
@@ -247,6 +266,7 @@ class SeoAnalysis extends Component {
location={ location }
shouldUpsellHighlighting={ this.props.shouldUpsellHighlighting }
highlightingUpsellLink={ highlightingUpsellLink }
+ renderAIFixesButton={ this.renderAIFixesButton }
/>
{ this.renderTabIcon( location, score.className ) }
@@ -269,6 +289,7 @@ SeoAnalysis.propTypes = {
shouldUpsellWordFormRecognition: PropTypes.bool,
overallScore: PropTypes.number,
shouldUpsellHighlighting: PropTypes.bool,
+ isElementor: PropTypes.bool,
};
SeoAnalysis.defaultProps = {
@@ -279,6 +300,7 @@ SeoAnalysis.defaultProps = {
shouldUpsellWordFormRecognition: false,
overallScore: null,
shouldUpsellHighlighting: false,
+ isElementor: false,
};
export default withSelect( ( select, ownProps ) => {
@@ -286,6 +308,7 @@ export default withSelect( ( select, ownProps ) => {
getFocusKeyphrase,
getMarksButtonStatus,
getResultsForKeyword,
+ getIsElementorEditor,
} = select( "yoast-seo/editor" );
const keyword = getFocusKeyphrase();
@@ -294,5 +317,6 @@ export default withSelect( ( select, ownProps ) => {
...getResultsForKeyword( keyword ),
marksButtonStatus: ownProps.hideMarksButtons ? "disabled" : getMarksButtonStatus(),
keyword,
+ isElementor: getIsElementorEditor(),
};
} )( SeoAnalysis );
diff --git a/packages/js/src/components/contentAnalysis/mapResults.js b/packages/js/src/components/contentAnalysis/mapResults.js
index 51f1a294731..a9ef408c0e9 100644
--- a/packages/js/src/components/contentAnalysis/mapResults.js
+++ b/packages/js/src/components/contentAnalysis/mapResults.js
@@ -46,6 +46,7 @@ function mapResult( result, key = "" ) {
markerId: key.length > 0 ? `${key}:${id}` : id,
hasBetaBadge: result.hasBetaBadge(),
hasJumps: result.hasJumps(),
+ hasAIFixes: result.hasAIFixes(),
editFieldName: result.editFieldName,
};
diff --git a/packages/js/src/containers/Results.js b/packages/js/src/containers/Results.js
index 0c01a6b82a1..e98d92cd559 100644
--- a/packages/js/src/containers/Results.js
+++ b/packages/js/src/containers/Results.js
@@ -8,23 +8,27 @@ export default compose( [
getActiveMarker,
getIsPremium,
getShortcodesForParsing,
+ getActiveAIFixesButton,
} = select( "yoast-seo/editor" );
return {
activeMarker: getActiveMarker(),
isPremium: getIsPremium(),
shortcodesForParsing: getShortcodesForParsing(),
+ activeAIFixesButton: getActiveAIFixesButton(),
};
} ),
withDispatch( dispatch => {
const {
setActiveMarker,
setMarkerPauseStatus,
+ setActiveAIFixesButton,
} = dispatch( "yoast-seo/editor" );
return {
setActiveMarker,
setMarkerPauseStatus,
+ setActiveAIFixesButton,
};
} ),
] )( Results );
diff --git a/packages/js/src/redux/actions/activeAIButton.js b/packages/js/src/redux/actions/activeAIButton.js
new file mode 100644
index 00000000000..f112a4e58a0
--- /dev/null
+++ b/packages/js/src/redux/actions/activeAIButton.js
@@ -0,0 +1,17 @@
+const PREFIX = "WPSEO_";
+
+export const SET_ACTIVE_AI_FIXES_BUTTON = `${ PREFIX }SET_ACTIVE_AI_FIXES_BUTTON`;
+
+/**
+ * Updates the active AI fixes button id.
+ *
+ * @param {string} activeAIButton The active AI fixes button id.
+ *
+ * @returns {Object} An action for redux.
+ */
+export function setActiveAIFixesButton( activeAIButton ) {
+ return {
+ type: SET_ACTIVE_AI_FIXES_BUTTON,
+ activeAIButton,
+ };
+}
diff --git a/packages/js/src/redux/actions/index.js b/packages/js/src/redux/actions/index.js
index dd7cd148a90..642e020a785 100644
--- a/packages/js/src/redux/actions/index.js
+++ b/packages/js/src/redux/actions/index.js
@@ -5,6 +5,7 @@ export const { setPluginUrl } = pluginUrlActions;
export const { setWistiaEmbedPermission, setWistiaEmbedPermissionValue } = wistiaEmbedPermissionActions;
export * from "../../insights/redux/actions";
export * from "./activeMarker";
+export * from "./activeAIButton";
export * from "./advancedSettings";
export * from "./analysis";
export * from "./checklist";
diff --git a/packages/js/src/redux/reducers/activeAIButton.js b/packages/js/src/redux/reducers/activeAIButton.js
new file mode 100644
index 00000000000..f775e25d9da
--- /dev/null
+++ b/packages/js/src/redux/reducers/activeAIButton.js
@@ -0,0 +1,23 @@
+import { SET_ACTIVE_AI_FIXES_BUTTON } from "../actions/activeAIButton";
+
+const INITIAL_STATE = null;
+
+/**
+ * A reducer for the active AI fixes button.
+ *
+ * @param {Object} state The current state of the object.
+ * @param {Object} action The received action.
+ *
+ * @returns {Object} The state.
+ */
+function activeAIButton( state = INITIAL_STATE, action ) {
+ switch ( action.type ) {
+ case SET_ACTIVE_AI_FIXES_BUTTON:
+ // Create a toggle effect so that the AI fixes button can be turned off.
+ return action.activeAIButton;
+ default:
+ return state;
+ }
+}
+
+export default activeAIButton;
diff --git a/packages/js/src/redux/reducers/index.js b/packages/js/src/redux/reducers/index.js
index 316c170c224..bff57462695 100644
--- a/packages/js/src/redux/reducers/index.js
+++ b/packages/js/src/redux/reducers/index.js
@@ -8,6 +8,7 @@ import {
wistiaEmbedPermissionReducer,
} from "../../shared-admin/store";
import analysis from "../reducers/contentAnalysis";
+import activeAIButton from "./activeAIButton";
import activeMarker from "./activeMarker";
import advancedSettings from "./advancedSettings";
import analysisData from "./analysisData";
@@ -39,6 +40,7 @@ import WincherRequest from "./WincherRequest";
import WincherSEOPerformance from "./WincherSEOPerformance";
export default {
+ activeAIButton,
activeMarker,
advancedSettings,
analysis,
diff --git a/packages/js/src/redux/selectors/activeAIButton.js b/packages/js/src/redux/selectors/activeAIButton.js
new file mode 100644
index 00000000000..33ddb726dbf
--- /dev/null
+++ b/packages/js/src/redux/selectors/activeAIButton.js
@@ -0,0 +1,8 @@
+/**
+ * Returns the active AI Fixes button.
+ * @param {object} state The state.
+ * @returns {string} Active AI Fixes button id.
+ */
+export function getActiveAIFixesButton( state ) {
+ return state.activeAIButton;
+}
diff --git a/packages/js/src/redux/selectors/index.js b/packages/js/src/redux/selectors/index.js
index 2293c15e696..ff8e091cddf 100644
--- a/packages/js/src/redux/selectors/index.js
+++ b/packages/js/src/redux/selectors/index.js
@@ -9,6 +9,7 @@ export const {
selectWistiaEmbedPermissionError,
} = wistiaEmbedPermissionSelectors;
export * from "../../insights/redux/selectors";
+export * from "./activeAIButton";
export * from "./advancedSettings";
export * from "./analysis";
export * from "./cornerstoneContent";
diff --git a/packages/js/tests/ai-assessment-fixes/components/AIAssessmentFixesButton.test.js b/packages/js/tests/ai-assessment-fixes/components/AIAssessmentFixesButton.test.js
new file mode 100644
index 00000000000..9ebefd12640
--- /dev/null
+++ b/packages/js/tests/ai-assessment-fixes/components/AIAssessmentFixesButton.test.js
@@ -0,0 +1,60 @@
+import React from "react";
+import { render, screen } from "../../test-utils";
+import { useSelect } from "@wordpress/data";
+
+import AIAssessmentFixesButton from "../../../src/ai-assessment-fixes/components/AIAssessmentFixesButton";
+
+jest.mock( "@wordpress/data", () => {
+ return {
+ useDispatch: jest.fn( () => {
+ return {
+ setActiveAIFixesButton: jest.fn(),
+ };
+ } ),
+ useSelect: jest.fn(),
+ };
+} );
+
+/**
+ * Mock the useSelect function.
+ * @param {string} activeAIButton The active AI button ID.
+ * @returns {function} The mock.
+ */
+const mockSelect = ( activeAIButton ) => useSelect.mockImplementation( select => select( () => ( {
+ getActiveAIFixesButton: () => activeAIButton,
+ getActiveMarker: () => null,
+} ) ) );
+
+describe( "AIAssessmentFixesButton", () => {
+ test( "should find the correct aria-label in the document", () => {
+ render( );
+
+ const labelText = document.querySelector( 'button[aria-label="Fix with AI"]' );
+ expect( labelText ).toBeInTheDocument();
+ } );
+
+ test( "should find the correct button id", () => {
+ render( );
+ const button = screen.getByRole( "button" );
+ expect( button ).toBeInTheDocument();
+ } );
+
+ test( "should find the button with tooltip when the button is NOT pressed", () => {
+ render( );
+
+ const buttonWithTooltip = document.getElementsByClassName( "yoast-tooltip yoast-tooltip-w" );
+ expect( buttonWithTooltip ).toHaveLength( 1 );
+ } );
+
+ test( "should find the button without tooltip when the button is pressed", () => {
+ // The button is pressed when the active AI button id in the store is the same as the current button id.
+ // The button ID is the component ID + "AIFixes".
+ mockSelect( "keyphraseDensityAIFixes" );
+
+ render( );
+
+ const buttonWithTooltip = document.getElementsByClassName( "yoast-tooltip yoast-tooltip-w" );
+ expect( buttonWithTooltip ).toHaveLength( 0 );
+ } );
+} );
+
diff --git a/packages/js/tests/components/contentAnalysis/mapResults.test.js b/packages/js/tests/components/contentAnalysis/mapResults.test.js
index bd795ec8368..ef55bef1da3 100644
--- a/packages/js/tests/components/contentAnalysis/mapResults.test.js
+++ b/packages/js/tests/components/contentAnalysis/mapResults.test.js
@@ -11,6 +11,7 @@ const analysisResults = [
text: "feedback string",
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
editFieldName: "keyphrase",
} ),
new AssessmentResult( {
@@ -19,6 +20,7 @@ const analysisResults = [
text: "feedback string",
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
editFieldName: "keyphrase",
} ),
new AssessmentResult( {
@@ -27,6 +29,7 @@ const analysisResults = [
text: "feedback string",
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
editFieldName: "keyphrase",
} ),
new AssessmentResult( {
@@ -35,6 +38,7 @@ const analysisResults = [
text: "feedback string",
_hasBetaBadge: true,
_hasJumps: true,
+ _hasAIFixes: false,
editFieldName: "keyphrase",
} ),
new AssessmentResult( {
@@ -43,6 +47,7 @@ const analysisResults = [
text: "feedback string",
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
editFieldName: "",
} ),
new AssessmentResult( {
@@ -51,6 +56,7 @@ const analysisResults = [
text: "",
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
editFieldName: "",
} ),
];
@@ -106,6 +112,7 @@ describe( "mapResults", () => {
expect( result.hasBetaBadge ).toBe( expectedResult._hasBetaBadge );
expect( result.hasJumps ).toBe( expectedResult._hasJumps );
expect( result.editFieldName ).toBe( expectedResult.editFieldName );
+ expect( result.hasAIFixes ).toBe( expectedResult._hasAIFixes );
} );
it( "maps a result, using a keywordKey", () => {
diff --git a/packages/js/tests/redux/actions/activeAIButton.test.js b/packages/js/tests/redux/actions/activeAIButton.test.js
new file mode 100644
index 00000000000..21e5c2c985c
--- /dev/null
+++ b/packages/js/tests/redux/actions/activeAIButton.test.js
@@ -0,0 +1,16 @@
+import { setActiveAIFixesButton, SET_ACTIVE_AI_FIXES_BUTTON } from "../../../src/redux/actions";
+
+describe( "setActiveAIFixesButton", () => {
+ it( "setActiveAIFixesButton should return an action with the SET_ACTIVE_AI_FIXES_BUTTON type and the id of the active AI assessment fixes button", () => {
+ const activeAIButton = "keyphraseInIntroductionAIFixes";
+
+ const expected = {
+ type: SET_ACTIVE_AI_FIXES_BUTTON,
+ activeAIButton,
+
+ };
+ const actual = setActiveAIFixesButton( activeAIButton );
+
+ expect( actual ).toEqual( expected );
+ } );
+} );
diff --git a/packages/js/tests/redux/reducers/activeAIButton.test.js b/packages/js/tests/redux/reducers/activeAIButton.test.js
new file mode 100644
index 00000000000..eb9c05c9ced
--- /dev/null
+++ b/packages/js/tests/redux/reducers/activeAIButton.test.js
@@ -0,0 +1,16 @@
+import { SET_ACTIVE_AI_FIXES_BUTTON } from "../../../src/redux/actions/activeAIButton";
+import activeAIButton from "../../../src/redux/reducers/activeAIButton";
+
+describe( "activeAIButton on receiving the SET_ACTIVE_AI_FIXES_BUTTON action", () => {
+ it( "should set the message in the warning state", () => {
+ const state = null;
+ const action = {
+ type: SET_ACTIVE_AI_FIXES_BUTTON,
+ activeAIButton: "keyphraseDensityAIFixes",
+ };
+ const expected = "keyphraseDensityAIFixes";
+ const actual = activeAIButton( state, action );
+
+ expect( actual ).toEqual( expected );
+ } );
+} );
diff --git a/packages/js/tests/redux/selectors/activeAIButton.test.js b/packages/js/tests/redux/selectors/activeAIButton.test.js
new file mode 100644
index 00000000000..9cdd189dd8a
--- /dev/null
+++ b/packages/js/tests/redux/selectors/activeAIButton.test.js
@@ -0,0 +1,13 @@
+import { getActiveAIFixesButton } from "../../../src/redux/selectors/activeAIButton";
+
+const testState = {
+ activeAIButton: "keyphraseInSubheadingAIFixes",
+};
+
+describe( "getActiveAIFixesButton", () => {
+ it( "returns the id of the active AI Assessment Fixes button", () => {
+ const actual = getActiveAIFixesButton( testState );
+
+ expect( actual ).toEqual( "keyphraseInSubheadingAIFixes" );
+ } );
+} );
diff --git a/packages/yoastseo/spec/scoring/assessments/seo/IntroductionKeywordAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/IntroductionKeywordAssessmentSpec.js
index 16d18182241..31bbe2b9f0f 100644
--- a/packages/yoastseo/spec/scoring/assessments/seo/IntroductionKeywordAssessmentSpec.js
+++ b/packages/yoastseo/spec/scoring/assessments/seo/IntroductionKeywordAssessmentSpec.js
@@ -84,7 +84,19 @@ describe( "An assessment for finding the keyword in the first paragraph", functi
" Your keyphrase or its synonyms do not appear in the first paragraph. Make sure" +
" the topic is clear immediately." );
} );
+ it( "returns `hasAIFixes` to be true when the result is BAD", function() {
+ const paper = new Paper( "Some text with some keyword. A keyphrase comes here.",
+ { keyword: "ponies", synonyms: "doggies" } );
+ const researcher = Factory.buildMockResearcher( {
+ foundInOneSentence: false,
+ foundInParagraph: false,
+ keyphraseOrSynonym: "",
+ } );
+ const assessment = new IntroductionKeywordAssessment().getResult( paper, researcher );
+ expect( assessment.getScore() ).toBe( 3 );
+ expect( assessment.hasAIFixes() ).toBeTruthy();
+ } );
it( "returns no score if no keyword is defined", function() {
const isApplicableResult = new IntroductionKeywordAssessment().isApplicable( new Paper( "some text" ) );
expect( isApplicableResult ).toBe( false );
diff --git a/packages/yoastseo/spec/scoring/assessments/seo/KeyphraseDistributionAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/KeyphraseDistributionAssessmentSpec.js
index 9e420b8172f..7f1b124f8c9 100644
--- a/packages/yoastseo/spec/scoring/assessments/seo/KeyphraseDistributionAssessmentSpec.js
+++ b/packages/yoastseo/spec/scoring/assessments/seo/KeyphraseDistributionAssessmentSpec.js
@@ -8,6 +8,20 @@ import Mark from "../../../../src/values/Mark.js";
const keyphraseDistributionAssessment = new KeyphraseDistributionAssessment();
describe( "An assessment to check the keyphrase distribution in the text", function() {
+ it( "returns `hasAIFixes` to be true when the result is not good", function() {
+ const mockPaper = new Paper( "a string", { keyword: "keyword" } );
+ const assessment = keyphraseDistributionAssessment.getResult(
+ mockPaper,
+ Factory.buildMockResearcher( {
+ keyphraseDistributionScore: 100,
+ sentencesToHighlight: [],
+ } )
+ );
+
+ expect( assessment.getScore() ).toEqual( 0 );
+ expect( assessment.hasAIFixes() ).toBeTruthy();
+ } );
+
it( "returns a 'consideration' score when no keyword occurs", function() {
const mockPaper = new Paper( "a string", { keyword: "keyword" } );
const assessment = keyphraseDistributionAssessment.getResult(
diff --git a/packages/yoastseo/spec/scoring/assessments/seo/KeywordDensityAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/KeywordDensityAssessmentSpec.js
index 27e0a372a45..22309312ab8 100644
--- a/packages/yoastseo/spec/scoring/assessments/seo/KeywordDensityAssessmentSpec.js
+++ b/packages/yoastseo/spec/scoring/assessments/seo/KeywordDensityAssessmentSpec.js
@@ -248,6 +248,15 @@ describe( "Tests for the keyphrase density assessment for languages with morphol
"The keyphrase was found 32 times. That's more than the recommended maximum of 29 times for a text of this length. " +
"Don't overoptimize!" );
} );
+ it( "returns `hasAIFixes` to be true when the result is BAD", function() {
+ const paper = new Paper( nonkeyword.repeat( 968 ) + keyword.repeat( 32 ), { keyword: "keyword", locale: "en_EN" } );
+ const researcher = new EnglishResearcher( paper );
+ buildTree( paper, researcher );
+
+ const result = new KeyphraseDensityAssessment().getResult( paper, researcher );
+ expect( result.getScore() ).toBe( -10 );
+ expect( result.hasAIFixes() ).toBeTruthy();
+ } );
} );
describe( "A test for marking the keyphrase", function() {
diff --git a/packages/yoastseo/spec/scoring/assessments/seo/SubHeadingsKeywordAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/SubHeadingsKeywordAssessmentSpec.js
index 086d2455bde..e608bde1ce2 100644
--- a/packages/yoastseo/spec/scoring/assessments/seo/SubHeadingsKeywordAssessmentSpec.js
+++ b/packages/yoastseo/spec/scoring/assessments/seo/SubHeadingsKeywordAssessmentSpec.js
@@ -6,6 +6,17 @@ import Factory from "../../../../src/helpers/factory";
const matchKeywordAssessment = new SubheadingsKeywordAssessment();
describe( "An assessment for matching keywords in subheadings", () => {
+ it( "returns `hasAIFixes` to be true when the result is BAD", function() {
+ const mockPaper = new Paper();
+ const assessment = matchKeywordAssessment.getResult(
+ mockPaper,
+ Factory.buildMockResearcher( { count: 1, matches: 0, percentReflectingTopic: 0 } )
+ );
+
+ expect( assessment.getScore() ).toEqual( 3 );
+ expect( assessment.hasAIFixes() ).toBeTruthy();
+ } );
+
it( "returns a bad score and appropriate feedback when none of the subheadings contain the keyphrase: no matches.", function() {
const mockPaper = new Paper();
const assessment = matchKeywordAssessment.getResult(
diff --git a/packages/yoastseo/spec/values/AssessmentResultSpec.js b/packages/yoastseo/spec/values/AssessmentResultSpec.js
index 078474bee96..e4e942808c3 100644
--- a/packages/yoastseo/spec/values/AssessmentResultSpec.js
+++ b/packages/yoastseo/spec/values/AssessmentResultSpec.js
@@ -22,6 +22,7 @@ describe( "creating a new empty assessmentResult", function() {
expect( assessmentResult.getText() ).toBe( "" );
expect( assessmentResult.hasBetaBadge() ).toBe( false );
expect( assessmentResult.hasJumps() ).toBe( false );
+ expect( assessmentResult.hasAIFixes() ).toBe( false );
expect( assessmentResult.hasEditFieldName() ).toBe( false );
expect( assessmentResult.getEditFieldName() ).toBe( "" );
} );
@@ -126,4 +127,20 @@ describe( "AssessmentResult", function() {
expect( result.hasJumps() ).toBe( true );
} );
} );
+
+ describe( "setHasAIFixes", function() {
+ it( "defaults to false", function() {
+ const result = new AssessmentResult();
+
+ expect( result.hasAIFixes() ).toBe( false );
+ } );
+
+ it( "sets the value to true", function() {
+ const result = new AssessmentResult();
+
+ result.setHasAIFixes( true );
+
+ expect( result.hasAIFixes() ).toBe( true );
+ } );
+ } );
} );
diff --git a/packages/yoastseo/spec/worker/transporter/serializeSpec.js b/packages/yoastseo/spec/worker/transporter/serializeSpec.js
index 849909322a9..44115949d15 100644
--- a/packages/yoastseo/spec/worker/transporter/serializeSpec.js
+++ b/packages/yoastseo/spec/worker/transporter/serializeSpec.js
@@ -64,6 +64,7 @@ describe( "serialize", () => {
expect( serialize( thing ) ).toEqual( {
_hasBetaBadge: false,
_hasJumps: false,
+ _hasAIFixes: false,
_parseClass: "AssessmentResult",
editFieldName: "",
identifier: "",
diff --git a/packages/yoastseo/src/scoring/assessments/seo/IntroductionKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/IntroductionKeywordAssessment.js
index 0cb99babda2..26e481105ba 100644
--- a/packages/yoastseo/src/scoring/assessments/seo/IntroductionKeywordAssessment.js
+++ b/packages/yoastseo/src/scoring/assessments/seo/IntroductionKeywordAssessment.js
@@ -53,7 +53,9 @@ class IntroductionKeywordAssessment extends Assessment {
assessmentResult.setScore( calculatedResult.score );
assessmentResult.setText( calculatedResult.resultText );
-
+ if ( calculatedResult.score < 9 ) {
+ assessmentResult.setHasAIFixes( true );
+ }
return assessmentResult;
}
diff --git a/packages/yoastseo/src/scoring/assessments/seo/KeyphraseDistributionAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/KeyphraseDistributionAssessment.js
index 02181a94957..7a847ac2313 100644
--- a/packages/yoastseo/src/scoring/assessments/seo/KeyphraseDistributionAssessment.js
+++ b/packages/yoastseo/src/scoring/assessments/seo/KeyphraseDistributionAssessment.js
@@ -73,7 +73,9 @@ class KeyphraseDistributionAssessment extends Assessment {
assessmentResult.setScore( calculatedResult.score );
assessmentResult.setText( calculatedResult.resultText );
assessmentResult.setHasMarks( calculatedResult.hasMarks );
-
+ if ( calculatedResult.score < 9 ) {
+ assessmentResult.setHasAIFixes( true );
+ }
return assessmentResult;
}
diff --git a/packages/yoastseo/src/scoring/assessments/seo/KeywordDensityAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/KeywordDensityAssessment.js
index fc26826dd47..ec816abeb42 100644
--- a/packages/yoastseo/src/scoring/assessments/seo/KeywordDensityAssessment.js
+++ b/packages/yoastseo/src/scoring/assessments/seo/KeywordDensityAssessment.js
@@ -118,7 +118,9 @@ class KeyphraseDensityAssessment extends Assessment {
assessmentResult.setScore( calculatedScore.score );
assessmentResult.setText( calculatedScore.resultText );
assessmentResult.setHasMarks( this._keyphraseCount.count > 0 );
-
+ if ( calculatedScore.score < 9 ) {
+ assessmentResult.setHasAIFixes( true );
+ }
return assessmentResult;
}
diff --git a/packages/yoastseo/src/scoring/assessments/seo/SubHeadingsKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/SubHeadingsKeywordAssessment.js
index add9d8daf30..242872a7a1e 100644
--- a/packages/yoastseo/src/scoring/assessments/seo/SubHeadingsKeywordAssessment.js
+++ b/packages/yoastseo/src/scoring/assessments/seo/SubHeadingsKeywordAssessment.js
@@ -58,7 +58,9 @@ export default class SubHeadingsKeywordAssessment extends Assessment {
assessmentResult.setScore( calculatedResult.score );
assessmentResult.setText( calculatedResult.resultText );
-
+ if ( calculatedResult.score < 9 ) {
+ assessmentResult.setHasAIFixes( true );
+ }
return assessmentResult;
}
diff --git a/packages/yoastseo/src/values/AssessmentResult.js b/packages/yoastseo/src/values/AssessmentResult.js
index 07f394e66a5..9a0530552a2 100644
--- a/packages/yoastseo/src/values/AssessmentResult.js
+++ b/packages/yoastseo/src/values/AssessmentResult.js
@@ -21,6 +21,7 @@ var emptyMarker = function() {
var AssessmentResult = function( values ) {
this._hasScore = false;
this._identifier = "";
+ this._hasAIFixes = false;
this._hasMarks = false;
this._hasJumps = false;
this._hasEditFieldName = false;
@@ -58,6 +59,10 @@ var AssessmentResult = function( values ) {
if ( ! isUndefined( values.editFieldName ) ) {
this.setEditFieldName( values.editFieldName );
}
+
+ if ( ! isUndefined( values._hasAIFixes ) ) {
+ this.setHasAIFixes( values._hasAIFixes );
+ }
};
/**
@@ -272,6 +277,25 @@ AssessmentResult.prototype.setEditFieldName = function( editFieldName ) {
}
};
+/**
+ * Sets the value of _hasAIFixes to determine if the result has AI fixes.
+ *
+ * @param {boolean} hasAIFixes Whether this result has AI fixes.
+ * @returns {void}
+ */
+AssessmentResult.prototype.setHasAIFixes = function( hasAIFixes ) {
+ this._hasAIFixes = hasAIFixes;
+};
+
+/**
+ * Returns the value of _hasAIFixes to determine if the result has AI fixes.
+ *
+ * @returns {bool} Whether this result has AI fixes.
+ */
+AssessmentResult.prototype.hasAIFixes = function() {
+ return this._hasAIFixes;
+};
+
/**
* Serializes the AssessmentResult instance to an object.
*
@@ -286,6 +310,7 @@ AssessmentResult.prototype.serialize = function() {
marks: this.marks.map( mark => mark.serialize() ),
_hasBetaBadge: this._hasBetaBadge,
_hasJumps: this._hasJumps,
+ _hasAIFixes: this._hasAIFixes,
editFieldName: this.editFieldName,
};
};
@@ -304,6 +329,7 @@ AssessmentResult.parse = function( serialized ) {
marks: serialized.marks.map( mark => Mark.parse( mark ) ),
_hasBetaBadge: serialized._hasBetaBadge,
_hasJumps: serialized._hasJumps,
+ _hasAIFixes: serialized._hasAIFixes,
editFieldName: serialized.editFieldName,
} );
result.setIdentifier( serialized.identifier );