diff options
Diffstat (limited to 'browser/components/newtab/content-src/components/DiscoveryStreamComponents')
11 files changed, 317 insertions, 65 deletions
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> ); |