summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src')
-rw-r--r--browser/components/newtab/content-src/components/Base/Base.jsx15
-rw-r--r--browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx17
-rw-r--r--browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx2
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx19
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx1
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss35
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx7
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx8
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx76
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss32
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx9
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss3
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx6
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx74
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx34
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss98
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss33
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx9
-rw-r--r--browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx2
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSite.jsx79
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSites.jsx2
-rw-r--r--browser/components/newtab/content-src/components/TopSites/_TopSites.scss19
-rw-r--r--browser/components/newtab/content-src/lib/init-store.js2
-rw-r--r--browser/components/newtab/content-src/lib/link-menu-options.js2
-rw-r--r--browser/components/newtab/content-src/lib/selectLayoutRender.js2
-rw-r--r--browser/components/newtab/content-src/styles/_activity-stream.scss2
-rw-r--r--browser/components/newtab/content-src/styles/_icons.scss4
28 files changed, 498 insertions, 96 deletions
diff --git a/browser/components/newtab/content-src/components/Base/Base.jsx b/browser/components/newtab/content-src/components/Base/Base.jsx
index 0580267f26..20402b09f5 100644
--- a/browser/components/newtab/content-src/components/Base/Base.jsx
+++ b/browser/components/newtab/content-src/components/Base/Base.jsx
@@ -165,6 +165,7 @@ export class BaseContent extends React.PureComponent {
const { App } = props;
const { initialized, customizeMenuVisible } = App;
const prefs = props.Prefs.values;
+ const { pocketConfig } = prefs;
const isDiscoveryStream =
props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
@@ -172,6 +173,14 @@ export class BaseContent extends React.PureComponent {
section => section.id !== "topstories"
);
+ let spocMessageVariant = "";
+ if (
+ props.App.locale?.startsWith("en-") &&
+ pocketConfig?.spocMessageVariant === "variant-c"
+ ) {
+ spocMessageVariant = pocketConfig.spocMessageVariant;
+ }
+
const pocketEnabled =
prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
const noSectionsEnabled =
@@ -218,6 +227,7 @@ export class BaseContent extends React.PureComponent {
pocketRegion={pocketRegion}
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
mayHaveSponsoredStories={mayHaveSponsoredStories}
+ spocMessageVariant={spocMessageVariant}
showing={customizeMenuVisible}
/>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions*/}
@@ -239,7 +249,10 @@ export class BaseContent extends React.PureComponent {
<div className={`body-wrapper${initialized ? " on" : ""}`}>
{isDiscoveryStream ? (
<ErrorBoundary className="borderless-error">
- <DiscoveryStreamBase locale={props.App.locale} />
+ <DiscoveryStreamBase
+ locale={props.App.locale}
+ mayHaveSponsoredStories={mayHaveSponsoredStories}
+ />
</ErrorBoundary>
) : (
<Sections />
diff --git a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
index 679e8e137f..98bf88fbea 100644
--- a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
+++ b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
@@ -4,6 +4,7 @@
import { ErrorBoundary } from "content-src/components/ErrorBoundary/ErrorBoundary";
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
+import { SponsoredContentHighlight } from "../DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight";
import React from "react";
import { connect } from "react-redux";
@@ -48,7 +49,14 @@ export class _CollapsibleSection extends React.PureComponent {
render() {
const { isAnimating, maxHeight, menuButtonHover, showContextMenu } =
this.state;
- const { id, collapsed, learnMore, title, subTitle } = this.props;
+ const {
+ id,
+ collapsed,
+ learnMore,
+ title,
+ subTitle,
+ mayHaveSponsoredStories,
+ } = this.props;
const active = menuButtonHover || showContextMenu;
let bodyStyle;
if (isAnimating && !collapsed) {
@@ -91,6 +99,13 @@ export class _CollapsibleSection extends React.PureComponent {
<FluentOrText message={subTitle} />
</span>
)}
+ {mayHaveSponsoredStories &&
+ this.props.spocMessageVariant === "variant-a" && (
+ <SponsoredContentHighlight
+ position="inset-block-start inset-inline-start"
+ dispatch={this.props.dispatch}
+ />
+ )}
</h3>
</div>
<ErrorBoundary className="section-body-fallback">
diff --git a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
index 0364f5386a..58df7d012b 100644
--- a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
+++ b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
@@ -16,7 +16,7 @@ export class ContextMenuButton extends React.PureComponent {
this.onUpdate = this.onUpdate.bind(this);
}
- openContextMenu(isKeyBoard, event) {
+ openContextMenu(isKeyBoard) {
if (this.props.onUpdate) {
this.props.onUpdate(true);
}
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
index 57ed935e93..298dedcee5 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
@@ -4,6 +4,7 @@
import React from "react";
import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
export class ContentSection extends React.PureComponent {
constructor(props) {
@@ -96,6 +97,7 @@ export class ContentSection extends React.PureComponent {
mayHaveSponsoredStories,
mayHaveRecentSaves,
openPreferences,
+ spocMessageVariant,
} = this.props;
const {
topSitesEnabled,
@@ -254,6 +256,23 @@ export class ContentSection extends React.PureComponent {
</label>
</div>
+ {pocketRegion &&
+ mayHaveSponsoredStories &&
+ spocMessageVariant === "variant-c" && (
+ <div className="sponsored-content-info">
+ <div className="icon icon-help"></div>
+ <div>
+ Sponsored content supports our mission to build a better web.{" "}
+ <SafeAnchor
+ dispatch={this.props.dispatch}
+ url="https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ >
+ Find out how
+ </SafeAnchor>
+ </div>
+ </div>
+ )}
+
<span className="divider" role="separator"></span>
<div>
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
index 3d33f6fde7..54dcd550c4 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
@@ -71,6 +71,7 @@ export class _CustomizeMenu extends React.PureComponent {
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
+ spocMessageVariant={this.props.spocMessageVariant}
dispatch={this.props.dispatch}
/>
</div>
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
index f534b8701b..579e455a3f 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
@@ -143,6 +143,10 @@
border-radius: 4px;
appearance: none;
background-color: var(--newtab-element-secondary-color);
+
+ &:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+ }
}
.sponsored-checkbox:checked {
@@ -151,6 +155,14 @@
background: url('chrome://global/skin/icons/check.svg') center no-repeat;
background-color: var(--newtab-primary-action-background);
+ &:hover {
+ background-color: var(--newtab-primary-element-hover-color);
+ }
+
+ &:active {
+ background-color: var(--newtab-primary-element-active-color);
+ }
+
@media (forced-colors: active) {
fill: $black;
}
@@ -178,6 +190,10 @@
background-origin: content-box;
background-color: var(--newtab-background-color-secondary);
+ &:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+ }
+
&:dir(rtl) {
background-position-x: left;
}
@@ -201,6 +217,25 @@
}
}
+ .sponsored-content-info {
+ display: flex;
+ gap: var(--space-small);
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--newtab-element-secondary-color);
+ padding: var(--space-small) var(--space-large);
+
+ .icon-help {
+ flex-shrink: 0;
+ color: var(--color-accent-primary);
+ height: 20px;
+ }
+
+ a {
+ color: var(--newtab-primary-action-background);
+ }
+ }
+
.divider {
border-top: 1px var(--newtab-border-color) solid;
margin: 0 -16px;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
index 0112013391..3c31a5a29f 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
@@ -143,7 +143,7 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
);
}
- restorePrefDefaults(event) {
+ restorePrefDefaults() {
this.props.dispatch(
ac.OnlyToMain({
type: at.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS,
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
index dff122b366..0f0ee51ab9 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
@@ -110,7 +110,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
});
}
- renderComponent(component, embedWidth) {
+ renderComponent(component) {
switch (component.type) {
case "Highlights":
return <Highlights />;
@@ -196,6 +196,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
onboardingExperience={component.properties.onboardingExperience}
ctaButtonSponsors={component.properties.ctaButtonSponsors}
ctaButtonVariant={component.properties.ctaButtonVariant}
+ spocMessageVariant={component.properties.spocMessageVariant}
editorsPicksHeader={component.properties.editorsPicksHeader}
recentSavesEnabled={this.props.DiscoveryStream.recentSavesEnabled}
hideDescriptions={this.props.DiscoveryStream.hideDescriptions}
@@ -218,7 +219,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
}
render() {
- const { locale } = this.props;
+ const { locale, mayHaveSponsoredStories } = this.props;
// Select layout render data by adding spocs and position to recommendations
const { layoutRender } = selectLayoutRender({
state: this.props.DiscoveryStream,
@@ -322,6 +323,8 @@ export class _DiscoveryStreamBase extends React.PureComponent {
showPrefName={topStories.pref.feed}
title={sectionTitle}
subTitle={subTitle}
+ mayHaveSponsoredStories={mayHaveSponsoredStories}
+ spocMessageVariant={message?.properties?.spocMessageVariant}
eventSource="CARDGRID"
>
{this.renderLayout(layoutRender)}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
index f13e0eb7ed..cf00361df2 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -31,11 +31,7 @@ export function DSSubHeader({ children }) {
);
}
-export function OnboardingExperience({
- children,
- dispatch,
- windowObj = global,
-}) {
+export function OnboardingExperience({ dispatch, windowObj = global }) {
const [dismissed, setDismissed] = useState(false);
const [maxHeight, setMaxHeight] = useState(null);
const heightElement = useRef(null);
@@ -330,6 +326,7 @@ export class _CardGrid extends React.PureComponent {
onboardingExperience,
ctaButtonSponsors,
ctaButtonVariant,
+ spocMessageVariant,
widgets,
recentSavesEnabled,
hideDescriptions,
@@ -378,6 +375,7 @@ export class _CardGrid extends React.PureComponent {
saveToPocketCard={saveToPocketCard}
ctaButtonSponsors={ctaButtonSponsors}
ctaButtonVariant={ctaButtonVariant}
+ spocMessageVariant={spocMessageVariant}
recommendation_id={rec.recommendation_id}
/>
)
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
index 6aef56fb33..f3e1eab503 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -85,8 +85,9 @@ export const DefaultMeta = ({
sponsor,
sponsored_by_override,
saveToPocketCard,
- isRecentSave,
ctaButtonVariant,
+ dispatch,
+ spocMessageVariant,
}) => (
<div className="meta">
<div className="info-wrap">
@@ -100,9 +101,7 @@ export const DefaultMeta = ({
sponsored_by_override={sponsored_by_override}
/>
)}
- <header title={title} className="title clamp">
- {title}
- </header>
+ <header className="title clamp">{title}</header>
{excerpt && <p className="excerpt clamp">{excerpt}</p>}
</div>
{!newSponsoredLabel && (
@@ -113,6 +112,8 @@ export const DefaultMeta = ({
sponsored_by_override={sponsored_by_override}
cta_button_variant={ctaButtonVariant}
source={source}
+ dispatch={dispatch}
+ spocMessageVariant={spocMessageVariant}
/>
)}
{/* Sponsored label is normally in the way of any message.
@@ -183,7 +184,7 @@ export class _DSCard extends React.PureComponent {
];
}
- onLinkClick(event) {
+ onLinkClick() {
if (this.props.dispatch) {
this.props.dispatch(
ac.DiscoveryStreamUserEvent({
@@ -223,7 +224,7 @@ export class _DSCard extends React.PureComponent {
}
}
- onSaveClick(event) {
+ onSaveClick() {
if (this.props.dispatch) {
this.props.dispatch(
ac.AlsoToMain({
@@ -408,43 +409,28 @@ export class _DSCard extends React.PureComponent {
};
return (
- <div
+ <article
className={`ds-card ${compactImagesClassName} ${imageGradientClassName} ${titleLinesName} ${descLinesClassName} ${ctaButtonClassName} ${ctaButtonVariantClassName}`}
ref={this.setContextMenuButtonHostRef}
>
+ <div className="img-wrapper">
+ <DSImage
+ extraClassNames="img"
+ source={this.props.image_src}
+ rawSource={this.props.raw_image_src}
+ sizes={this.dsImageSizes}
+ url={this.props.url}
+ title={this.props.title}
+ isRecentSave={isRecentSave}
+ />
+ </div>
<SafeAnchor
className="ds-card-link"
dispatch={this.props.dispatch}
onLinkClick={!this.props.placeholder ? this.onLinkClick : undefined}
url={this.props.url}
+ title={this.props.title}
>
- <div className="img-wrapper">
- <DSImage
- extraClassNames="img"
- source={this.props.image_src}
- rawSource={this.props.raw_image_src}
- sizes={this.dsImageSizes}
- url={this.props.url}
- title={this.props.title}
- isRecentSave={isRecentSave}
- />
- </div>
- {ctaButtonVariant === "variant-b" && (
- <div className="cta-header">Shop Now</div>
- )}
- <DefaultMeta
- source={source}
- title={this.props.title}
- excerpt={excerpt}
- newSponsoredLabel={newSponsoredLabel}
- timeToRead={timeToRead}
- context={this.props.context}
- context_type={this.props.context_type}
- sponsor={this.props.sponsor}
- sponsored_by_override={this.props.sponsored_by_override}
- saveToPocketCard={saveToPocketCard}
- ctaButtonVariant={ctaButtonVariant}
- />
<ImpressionStats
flightId={this.props.flightId}
rows={[
@@ -461,6 +447,24 @@ export class _DSCard extends React.PureComponent {
source={this.props.type}
/>
</SafeAnchor>
+ {ctaButtonVariant === "variant-b" && (
+ <div className="cta-header">Shop Now</div>
+ )}
+ <DefaultMeta
+ source={source}
+ title={this.props.title}
+ excerpt={excerpt}
+ newSponsoredLabel={newSponsoredLabel}
+ timeToRead={timeToRead}
+ context={this.props.context}
+ context_type={this.props.context_type}
+ sponsor={this.props.sponsor}
+ sponsored_by_override={this.props.sponsored_by_override}
+ saveToPocketCard={saveToPocketCard}
+ ctaButtonVariant={ctaButtonVariant}
+ dispatch={this.props.dispatch}
+ spocMessageVariant={this.props.spocMessageVariant}
+ />
{saveToPocketCard && (
<div className="card-stp-button-hover-background">
<div className="card-stp-button-position-wrapper">
@@ -512,7 +516,7 @@ export class _DSCard extends React.PureComponent {
isRecentSave={isRecentSave}
/>
)}
- </div>
+ </article>
);
}
}
@@ -526,4 +530,4 @@ export const DSCard = connect(state => ({
DiscoveryStream: state.DiscoveryStream,
}))(_DSCard);
-export const PlaceholderDSCard = props => <DSCard placeholder={true} />;
+export const PlaceholderDSCard = () => <DSCard placeholder={true} />;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
index 92afedff26..9004e609df 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -118,6 +118,16 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
transform: scale(1);
}
}
+
+ header {
+ color: var(--newtab-primary-action-background);
+ }
+ }
+
+ &:active {
+ header {
+ color: var(--newtab-primary-element-active-color);
+ }
}
.img {
@@ -131,31 +141,15 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
}
.ds-card-link {
+ position: absolute;
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
text-decoration: none;
- &:hover {
- header {
- color: var(--newtab-primary-action-background);
- }
- }
-
&:focus {
@include ds-focus;
transition: none;
-
- header {
- color: var(--newtab-primary-action-background);
- }
- }
-
- &:active {
- header {
- color: var(--newtab-primary-element-active-color);
- }
}
}
@@ -255,7 +249,7 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
- column-gap: var(--space-small);
+ gap: 0 var(--space-small);
margin-top: 0;
}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
index 5c7e79685e..6c0641cfc1 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
@@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { cardContextTypes } from "../../Card/types.js";
+import { SponsoredContentHighlight } from "../FeatureHighlight/SponsoredContentHighlight";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
import React from "react";
@@ -82,6 +83,8 @@ export class DSContextFooter extends React.PureComponent {
sponsored_by_override,
cta_button_variant,
source,
+ spocMessageVariant,
+ dispatch,
} = this.props;
const sponsorLabel = SponsorLabel({
@@ -119,6 +122,12 @@ export class DSContextFooter extends React.PureComponent {
return (
<div className="story-footer">
{sponsorLabel}
+ {sponsorLabel && spocMessageVariant === "variant-b" && (
+ <SponsoredContentHighlight
+ dispatch={dispatch}
+ position="inset-block-end inset-inline-start"
+ />
+ )}
{dsMessageLabel}
</div>
);
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
index c23bb1c661..b28b498682 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
@@ -2,7 +2,10 @@
color: var(--newtab-text-secondary-color);
inset-inline-start: 0;
margin-top: 12px;
+ display: flex;
+ gap: var(--space-large);
position: relative;
+ pointer-events: none;
.story-sponsored-label span {
display: inline-block;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
index f342c9829b..b251fb0401 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
@@ -17,7 +17,7 @@ export class DSPrivacyModal extends React.PureComponent {
this.onManageLinkClick = this.onManageLinkClick.bind(this);
}
- onLearnLinkClick(event) {
+ onLearnLinkClick() {
this.props.dispatch(
ac.DiscoveryStreamUserEvent({
event: "CLICK_PRIVACY_INFO",
@@ -26,7 +26,7 @@ export class DSPrivacyModal extends React.PureComponent {
);
}
- onManageLinkClick(event) {
+ onManageLinkClick() {
this.props.dispatch(ac.OnlyToMain({ type: at.SETTINGS_OPEN }));
}
@@ -50,7 +50,7 @@ export class DSPrivacyModal extends React.PureComponent {
className="modal-link modal-link-privacy"
data-l10n-id="newtab-privacy-modal-link"
onClick={this.onLearnLinkClick}
- href="https://help.getpocket.com/article/1142-firefox-new-tab-recommendations-faq"
+ href="https://support.mozilla.org/kb/pocket-recommendations-firefox-new-tab"
/>
<button
className="modal-link modal-link-manage"
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
new file mode 100644
index 0000000000..792be40ba3
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import React, { useState, useCallback, useRef, useEffect } from "react";
+import { actionCreators as ac } from "common/Actions.sys.mjs";
+
+export function FeatureHighlight({
+ message,
+ icon,
+ toggle,
+ position = "top-left",
+ title,
+ ariaLabel,
+ feature = "FEATURE_HIGHLIGHT_DEFAULT",
+ dispatch = () => {},
+ windowObj = global,
+}) {
+ const [opened, setOpened] = useState(false);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handleOutsideClick = e => {
+ if (!ref?.current?.contains(e.target)) {
+ setOpened(false);
+ }
+ };
+
+ windowObj.document.addEventListener("click", handleOutsideClick);
+ return () => {
+ windowObj.document.removeEventListener("click", handleOutsideClick);
+ };
+ }, [windowObj]);
+
+ const onToggleClick = useCallback(() => {
+ if (!opened) {
+ dispatch(
+ ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "FEATURE_HIGHLIGHT",
+ value: {
+ feature,
+ },
+ })
+ );
+ }
+ setOpened(!opened);
+ }, [dispatch, feature, opened]);
+
+ const openedClassname = opened ? `opened` : `closed`;
+ return (
+ <div ref={ref} className="feature-highlight">
+ <button
+ title={title}
+ aria-haspopup="true"
+ aria-label={ariaLabel}
+ className="toggle-button"
+ onClick={onToggleClick}
+ >
+ {toggle}
+ </button>
+ <div className={`feature-highlight-modal ${position} ${openedClassname}`}>
+ <div className="message-icon">{icon}</div>
+ <p>{message}</p>
+ <button
+ title="Dismiss"
+ aria-label="Close sponsored content more info popup"
+ className="icon icon-dismiss"
+ onClick={() => setOpened(false)}
+ ></button>
+ </div>
+ </div>
+ );
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx
new file mode 100644
index 0000000000..8fdd03be6d
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { FeatureHighlight } from "./FeatureHighlight";
+import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
+import React from "react";
+
+export function SponsoredContentHighlight({ position, dispatch }) {
+ return (
+ <div className="sponsored-content-highlight">
+ <FeatureHighlight
+ position={position}
+ ariaLabel="Sponsored content supports our mission to build a better web."
+ title="Sponsored content more info"
+ feature="SPONSORED_CONTENT_INFO"
+ dispatch={dispatch}
+ message={
+ <span>
+ Sponsored content supports our mission to build a better web.{" "}
+ <SafeAnchor
+ dispatch={dispatch}
+ url="https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ >
+ Find out how
+ </SafeAnchor>
+ </span>
+ }
+ icon={<div className="sponsored-message-icon"></div>}
+ toggle={<div className="icon icon-help"></div>}
+ />
+ </div>
+ );
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss
new file mode 100644
index 0000000000..c0fdd52f58
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss
@@ -0,0 +1,98 @@
+.feature-highlight {
+ position: relative;
+ // This is needed because in 1 case this is positioned under a link
+ // and in an element that's not clickable.
+ pointer-events: auto;
+ z-index: 1;
+
+ .feature-highlight-modal {
+ position: absolute;
+ display: flex;
+ opacity: 0;
+ visibility: hidden;
+ cursor: default;
+ justify-content: space-between;
+ border-radius: var(--border-radius-small);
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 15%);
+ width: 298px;
+ transition: opacity 0.3s, visibility 0.3s;
+
+ .icon-dismiss {
+ flex-shrink: 0;
+ cursor: pointer;
+ background-size: $smaller-icon-size;
+ height: $smaller-icon-size;
+ width: $smaller-icon-size;
+ margin: var(--space-medium);
+ color: var(--icon-color);
+ border: none;
+ }
+
+ .message-icon {
+ margin-block: var(--space-large);
+ margin-inline: var(--space-large) var(--space-medium);
+ }
+
+ &.opened {
+ opacity: 1;
+ visibility: visible;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 4px 4px 6px -2px rgba(0, 0, 0, 15%);
+ }
+
+ &.inset-block-start {
+ inset-block-end: 100%;
+ margin-bottom: var(--space-xlarge);
+
+ &::after {
+ inset-block-end: -12px;
+ transform: rotate(45deg);
+ }
+ }
+
+ &.inset-block-end {
+ inset-block-start: 100%;
+ margin-top: var(--space-xlarge);
+
+ &::after {
+ inset-block-start: -12px;
+ transform: rotate(225deg);
+ }
+ }
+
+ &.inset-inline-start {
+ inset-inline-end: calc(var(--space-xxlarge) * -1);
+
+ &::after {
+ inset-inline-end: calc(var(--space-xxlarge) - 12px);
+ }
+ }
+
+ &.inset-inline-end {
+ inset-inline-start: calc(var(--space-xxlarge) * -1);
+
+ &::after {
+ inset-inline-start: calc(var(--space-xxlarge) - 12px);
+ }
+ }
+
+ p {
+ font-size: var(--font-size-small);
+ font-weight: normal;
+ margin: var(--space-large) 0;
+ }
+ }
+
+ .toggle-button {
+ border: none;
+ padding: 0;
+ }
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss
new file mode 100644
index 0000000000..1dc024b7c0
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss
@@ -0,0 +1,33 @@
+.sponsored-content-highlight {
+ float: inline-end;
+
+ .sponsored-message-icon {
+ background-image: url('chrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg');
+ background-size: 18px;
+ height: 18px;
+ width: 18px;
+ }
+
+ .icon-help {
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ background-size: 20px;
+ color: var(--icon-color);
+ vertical-align: text-bottom;
+ }
+
+ .feature-highlight-modal {
+ &.inset-inline-start {
+ &::after {
+ margin-inline-end: 11px;
+ }
+ }
+
+ &.inset-inline-end {
+ &::after {
+ margin-inline-start: 9px;
+ }
+ }
+ }
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
index cfbc6fe6cb..72ec94e1fe 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
@@ -55,9 +55,14 @@ export class SafeAnchor extends React.PureComponent {
}
render() {
- const { url, className } = this.props;
+ const { url, className, title } = this.props;
return (
- <a href={this.safeURI(url)} className={className} onClick={this.onClick}>
+ <a
+ href={this.safeURI(url)}
+ title={title}
+ className={className}
+ onClick={this.onClick}
+ >
{this.props.children}
</a>
);
diff --git a/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx b/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
index 1834a0a521..6bf1614ce7 100644
--- a/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
+++ b/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
@@ -52,7 +52,7 @@ export class ErrorBoundary extends React.PureComponent {
this.state = { hasError: false };
}
- componentDidCatch(error, info) {
+ componentDidCatch() {
this.setState({ hasError: true });
}
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
index b7f0038558..c0932104af 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
@@ -159,7 +159,10 @@ export class TopSiteLink extends React.PureComponent {
// If we have tabbed to a search shortcut top site, and we click 'enter',
// we should execute the onClick function. This needs to be added because
// search top sites are anchor tags without an href. See bug 1483135
- if (this.props.link.searchTopSite && event.key === "Enter") {
+ if (
+ event.key === "Enter" &&
+ (this.props.link.searchTopSite || this.props.isAddButton)
+ ) {
this.props.onClick(event);
}
}
@@ -250,8 +253,15 @@ export class TopSiteLink extends React.PureComponent {
}
render() {
- const { children, className, isDraggable, link, onClick, title } =
- this.props;
+ const {
+ children,
+ className,
+ isDraggable,
+ link,
+ onClick,
+ title,
+ isAddButton,
+ } = this.props;
const topSiteOuterClassName = `top-site-outer${
className ? ` ${className}` : ""
}${link.isDragged ? " dragged" : ""}${
@@ -266,6 +276,10 @@ export class TopSiteLink extends React.PureComponent {
selectedColor,
} = this.calculateStyle();
+ let addButtonl10n = {
+ "data-l10n-id": "newtab-topsites-add-shortcut-label",
+ };
+
let draggableProps = {};
if (isDraggable) {
draggableProps = {
@@ -380,14 +394,14 @@ export class TopSiteLink extends React.PureComponent {
: ""
}`}
>
- <span dir="auto">
+ <span dir="auto" {...(isAddButton && { ...addButtonl10n })}>
{link.isPinned && <div className="icon icon-pin-small" />}
{title || <br />}
- <span
- className="sponsored-label"
- data-l10n-id="newtab-topsite-sponsored"
- />
</span>
+ <span
+ className="sponsored-label"
+ data-l10n-id="newtab-topsite-sponsored"
+ />
</div>
</a>
{children}
@@ -635,19 +649,23 @@ export class TopSitePlaceholder extends React.PureComponent {
}
render() {
+ let addButtonProps = {};
+ if (this.props.isAddButton) {
+ addButtonProps = {
+ title: "newtab-topsites-add-shortcut-label",
+ onClick: this.onEditButtonClick,
+ };
+ }
+
return (
<TopSiteLink
{...this.props}
- className={`placeholder ${this.props.className || ""}`}
+ {...(this.props.isAddButton ? { ...addButtonProps } : {})}
+ className={`placeholder ${this.props.className || ""} ${
+ this.props.isAddButton ? "add-button" : ""
+ }`}
isDraggable={false}
- >
- <button
- aria-haspopup="dialog"
- className="context-menu-button edit-button icon"
- data-l10n-id="newtab-menu-topsites-placeholder-tooltip"
- onClick={this.onEditButtonClick}
- />
- </TopSiteLink>
+ />
);
}
}
@@ -758,6 +776,17 @@ export class _TopSiteList extends React.PureComponent {
// Make a copy of the sites to truncate or extend to desired length
let topSites = this.props.TopSites.rows.slice();
topSites.length = this.props.TopSitesRows * TOP_SITES_MAX_SITES_PER_ROW;
+ // if topSites do not fill an entire row add 'Add shortcut' button to array of topSites
+ // (there should only be one of these)
+ let firstPlaceholder = topSites.findIndex(Object.is.bind(null, undefined));
+ // make sure placeholder exists and there already isnt a add button
+ if (firstPlaceholder && !topSites.includes(site => site.isAddButton)) {
+ topSites[firstPlaceholder] = { isAddButton: true };
+ } else if (topSites.includes(site => site.isAddButton)) {
+ topSites.push(
+ topSites.splice(topSites.indexOf({ isAddButton: true }), 1)[0]
+ );
+ }
return topSites;
}
@@ -855,8 +884,20 @@ export class _TopSiteList extends React.PureComponent {
let topSiteLink;
// Use a placeholder if the link is empty or it's rendering a sponsored
// tile for the about:home startup cache.
- if (!link || (props.App.isForStartupCache && isSponsored(link))) {
- topSiteLink = <TopSitePlaceholder {...slotProps} {...commonProps} />;
+ if (
+ !link ||
+ (props.App.isForStartupCache && isSponsored(link)) ||
+ topSites[i]?.isAddButton
+ ) {
+ if (link) {
+ topSiteLink = (
+ <TopSitePlaceholder
+ {...slotProps}
+ {...commonProps}
+ isAddButton={topSites[i] && topSites[i].isAddButton}
+ />
+ );
+ }
} else {
topSiteLink = (
<TopSite
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
index c69156c514..ba7676fd10 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
@@ -206,7 +206,7 @@ export class _TopSites extends React.PureComponent {
}
}
-export const TopSites = connect((state, props) => ({
+export const TopSites = connect(state => ({
TopSites: state.TopSites,
Prefs: state.Prefs,
TopSitesRows: state.Prefs.values.topSitesRows,
diff --git a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
index ff2e2df826..4e4019513d 100644
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
+++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
@@ -246,7 +246,24 @@ $letter-fallback-color: $white;
&.placeholder {
.tile {
- box-shadow: $inner-box-shadow;
+ box-shadow: $shadow-card;
+ cursor: default;
+ }
+
+ &.add-button {
+ .tile {
+ background-color: var(--button-background-color);
+
+ .icon-wrapper {
+ background-image: url('chrome://global/skin/icons/plus-20.svg');
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
+ }
+ }
}
}
diff --git a/browser/components/newtab/content-src/lib/init-store.js b/browser/components/newtab/content-src/lib/init-store.js
index 20fcedc6c0..f0ab2db86a 100644
--- a/browser/components/newtab/content-src/lib/init-store.js
+++ b/browser/components/newtab/content-src/lib/init-store.js
@@ -44,7 +44,7 @@ function mergeStateReducer(mainReducer) {
/**
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
-const messageMiddleware = store => next => action => {
+const messageMiddleware = () => next => action => {
const skipLocal = action.meta && action.meta.skipLocal;
if (au.isSendToMain(action)) {
RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
diff --git a/browser/components/newtab/content-src/lib/link-menu-options.js b/browser/components/newtab/content-src/lib/link-menu-options.js
index caac738170..12e47259c1 100644
--- a/browser/components/newtab/content-src/lib/link-menu-options.js
+++ b/browser/components/newtab/content-src/lib/link-menu-options.js
@@ -25,7 +25,7 @@ const _OpenInPrivateWindow = site => ({
export const LinkMenuOptions = {
Separator: () => ({ type: "separator" }),
EmptyItem: () => ({ type: "empty" }),
- ShowPrivacyInfo: site => ({
+ ShowPrivacyInfo: () => ({
id: "newtab-menu-show-privacy-info",
icon: "info",
action: {
diff --git a/browser/components/newtab/content-src/lib/selectLayoutRender.js b/browser/components/newtab/content-src/lib/selectLayoutRender.js
index aa3eb927d2..8ef4dd428f 100644
--- a/browser/components/newtab/content-src/lib/selectLayoutRender.js
+++ b/browser/components/newtab/content-src/lib/selectLayoutRender.js
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-export const selectLayoutRender = ({ state = {}, prefs = {}, locale = "" }) => {
+export const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const { layout, feeds, spocs } = state;
let spocIndexPlacementMap = {};
diff --git a/browser/components/newtab/content-src/styles/_activity-stream.scss b/browser/components/newtab/content-src/styles/_activity-stream.scss
index 8bd3a7a397..88ed530b6a 100644
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -166,6 +166,8 @@ input {
@import '../components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal';
@import '../components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink';
@import '../components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget';
+@import '../components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight';
+@import '../components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight';
// AS Router
@import '../../../asrouter/content-src/components/Button/Button';
diff --git a/browser/components/newtab/content-src/styles/_icons.scss b/browser/components/newtab/content-src/styles/_icons.scss
index 4074f0a6a6..8be97ad9ae 100644
--- a/browser/components/newtab/content-src/styles/_icons.scss
+++ b/browser/components/newtab/content-src/styles/_icons.scss
@@ -70,6 +70,10 @@
background-image: url('chrome://global/skin/icons/info.svg');
}
+ &.icon-help {
+ background-image: url('chrome://global/skin/icons/help.svg');
+ }
+
&.icon-new-window {
@include flip-icon;