diff options
Diffstat (limited to 'browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid')
2 files changed, 177 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx new file mode 100644 index 0000000000..d089a5c8ab --- /dev/null +++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx @@ -0,0 +1,139 @@ +/* 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 { actionCreators as ac } from "common/Actions.sys.mjs"; +import { CardGrid } from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid"; +import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss"; +import { LinkMenuOptions } from "content-src/lib/link-menu-options"; +import React from "react"; + +export class CollectionCardGrid extends React.PureComponent { + constructor(props) { + super(props); + this.onDismissClick = this.onDismissClick.bind(this); + this.state = { + dismissed: false, + }; + } + + onDismissClick() { + const { data } = this.props; + if (this.props.dispatch && data && data.spocs && data.spocs.length) { + this.setState({ + dismissed: true, + }); + const pos = 0; + const source = this.props.type.toUpperCase(); + // Grab the available items in the array to dismiss. + // This fires a ping for all items available, even if below the fold. + const spocsData = data.spocs.map(item => ({ + url: item.url, + guid: item.id, + shim: item.shim, + flight_id: item.flightId, + })); + + const blockUrlOption = LinkMenuOptions.BlockUrls(spocsData, pos, source); + const { action, impression, userEvent } = blockUrlOption; + this.props.dispatch(action); + + this.props.dispatch( + ac.DiscoveryStreamUserEvent({ + event: userEvent, + source, + action_position: pos, + }) + ); + if (impression) { + this.props.dispatch(impression); + } + } + } + + render() { + const { data, dismissible, pocket_button_enabled } = this.props; + if ( + this.state.dismissed || + !data || + !data.spocs || + !data.spocs[0] || + // We only display complete collections. + data.spocs.length < 3 + ) { + return null; + } + const { spocs, placement, feed } = this.props; + // spocs.data is spocs state data, and not an array of spocs. + const { title, context, sponsored_by_override, sponsor } = + spocs.data[placement.name] || {}; + // Just in case of bad data, don't display a broken collection. + if (!title) { + return null; + } + + let sponsoredByMessage = ""; + + // If override is not false or an empty string. + if (sponsored_by_override || sponsored_by_override === "") { + // We specifically want to display nothing if the server returns an empty string. + // So the server can turn off the label. + // This is to support the use cases where the sponsored context is displayed elsewhere. + sponsoredByMessage = sponsored_by_override; + } else if (sponsor) { + sponsoredByMessage = { + id: `newtab-label-sponsored-by`, + values: { sponsor }, + }; + } else if (context) { + sponsoredByMessage = context; + } + + // Generally a card grid displays recs with spocs already injected. + // Normally it doesn't care which rec is a spoc and which isn't, + // it just displays content in a grid. + // For collections, we're only displaying a list of spocs. + // We don't need to tell the card grid that our list of cards are spocs, + // it shouldn't need to care. So we just pass our spocs along as recs. + // Think of it as injecting all rec positions with spocs. + // Consider maybe making recommendations in CardGrid use a more generic name. + const recsData = { + recommendations: data.spocs, + }; + + // All cards inside of a collection card grid have a slightly different type. + // For the case of interactions to the card grid, we use the type "COLLECTIONCARDGRID". + // Example, you dismiss the whole collection, we use the type "COLLECTIONCARDGRID". + // For interactions inside the card grid, example, you dismiss a single card in the collection, + // we use the type "COLLECTIONCARDGRID_CARD". + const type = `${this.props.type}_card`; + + const collectionGrid = ( + <div className="ds-collection-card-grid"> + <CardGrid + pocket_button_enabled={pocket_button_enabled} + title={title} + context={sponsoredByMessage} + data={recsData} + feed={feed} + type={type} + is_collection={true} + dispatch={this.props.dispatch} + items={this.props.items} + /> + </div> + ); + + if (dismissible) { + return ( + <DSDismiss + onDismissClick={this.onDismissClick} + extraClasses={`ds-dismiss-ds-collection`} + > + {collectionGrid} + </DSDismiss> + ); + } + return collectionGrid; + } +} diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/_CollectionCardGrid.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/_CollectionCardGrid.scss new file mode 100644 index 0000000000..f4778f3b95 --- /dev/null +++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/_CollectionCardGrid.scss @@ -0,0 +1,38 @@ +.ds-dismiss.ds-dismiss-ds-collection { + .ds-dismiss-button { + margin: 15px 0 0; + inset-inline-end: 25px; + } + + &.hovering { + background: var(--newtab-element-hover-color); + } +} + +.ds-collection-card-grid { + padding: 10px 25px 25px; + margin: 0 0 20px; + + .story-footer { + display: none; + } + + .ds-header { + padding: 0 40px 0 0; + margin-bottom: 12px; + + .title { + color: var(--newtab-text-primary-color); + font-weight: 600; + font-size: 17px; + line-height: 24px; + } + + .ds-context { + color: var(--newtab-text-secondary-color); + font-weight: normal; + font-size: 13px; + line-height: 24px; + } + } +} |