diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/components/newtab/content-src/asrouter/templates/SimpleSnippet | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/content-src/asrouter/templates/SimpleSnippet')
3 files changed, 512 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx new file mode 100644 index 0000000000..96570e2dbd --- /dev/null +++ b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx @@ -0,0 +1,222 @@ +/* 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 { Button } from "../../components/Button/Button"; +import ConditionalWrapper from "../../components/ConditionalWrapper/ConditionalWrapper"; +import React from "react"; +import { RichText } from "../../components/RichText/RichText"; +import { safeURI } from "../../template-utils"; +import { SnippetBase } from "../../components/SnippetBase/SnippetBase"; + +const DEFAULT_ICON_PATH = "chrome://branding/content/icon64.png"; +// Alt text placeholder in case the prop from the server isn't available +const ICON_ALT_TEXT = ""; + +export class SimpleSnippet extends React.PureComponent { + constructor(props) { + super(props); + this.onButtonClick = this.onButtonClick.bind(this); + } + + onButtonClick() { + if (this.props.provider !== "preview") { + this.props.sendUserActionTelemetry({ + event: "CLICK_BUTTON", + id: this.props.UISurface, + }); + } + const { button_url, button_entrypoint_value, button_entrypoint_name } = + this.props.content; + // If button_url is defined handle it as OPEN_URL action + const type = this.props.content.button_action || (button_url && "OPEN_URL"); + // Assign the snippet referral for the action + const entrypoint = button_entrypoint_name + ? new URLSearchParams([ + [button_entrypoint_name, button_entrypoint_value], + ]).toString() + : button_entrypoint_value; + this.props.onAction({ + type, + data: { + args: this.props.content.button_action_args || button_url, + ...(entrypoint && { entrypoint }), + }, + }); + if (!this.props.content.do_not_autoblock) { + this.props.onBlock(); + } + } + + _shouldRenderButton() { + return ( + this.props.content.button_action || + this.props.onButtonClick || + this.props.content.button_url + ); + } + + renderTitle() { + const { title } = this.props.content; + return title ? ( + <h3 + className={`title ${this._shouldRenderButton() ? "title-inline" : ""}`} + > + {this.renderTitleIcon()} {title} + </h3> + ) : null; + } + + renderTitleIcon() { + const titleIconLight = safeURI(this.props.content.title_icon); + const titleIconDark = safeURI( + this.props.content.title_icon_dark_theme || this.props.content.title_icon + ); + if (!titleIconLight) { + return null; + } + + return ( + <React.Fragment> + <span + className="titleIcon icon-light-theme" + style={{ backgroundImage: `url("${titleIconLight}")` }} + /> + <span + className="titleIcon icon-dark-theme" + style={{ backgroundImage: `url("${titleIconDark}")` }} + /> + </React.Fragment> + ); + } + + renderButton() { + const { props } = this; + if (!this._shouldRenderButton()) { + return null; + } + + return ( + <Button + onClick={props.onButtonClick || this.onButtonClick} + color={props.content.button_color} + backgroundColor={props.content.button_background_color} + > + {props.content.button_label} + </Button> + ); + } + + renderText() { + const { props } = this; + return ( + <RichText + text={props.content.text} + customElements={this.props.customElements} + localization_id="text" + links={props.content.links} + sendClick={props.sendClick} + /> + ); + } + + wrapSectionHeader(url) { + return function (children) { + return <a href={url}>{children}</a>; + }; + } + + wrapSnippetContent(children) { + return <div className="innerContentWrapper">{children}</div>; + } + + renderSectionHeader() { + const { props } = this; + + // an icon and text must be specified to render the section header + if (props.content.section_title_icon && props.content.section_title_text) { + const sectionTitleIconLight = safeURI(props.content.section_title_icon); + const sectionTitleIconDark = safeURI( + props.content.section_title_icon_dark_theme || + props.content.section_title_icon + ); + const sectionTitleURL = props.content.section_title_url; + + return ( + <div className="section-header"> + <h3 className="section-title"> + <ConditionalWrapper + condition={sectionTitleURL} + wrap={this.wrapSectionHeader(sectionTitleURL)} + > + <span + className="icon icon-small-spacer icon-light-theme" + style={{ backgroundImage: `url("${sectionTitleIconLight}")` }} + /> + <span + className="icon icon-small-spacer icon-dark-theme" + style={{ backgroundImage: `url("${sectionTitleIconDark}")` }} + /> + <span className="section-title-text"> + {props.content.section_title_text} + </span> + </ConditionalWrapper> + </h3> + </div> + ); + } + + return null; + } + + render() { + const { props } = this; + const sectionHeader = this.renderSectionHeader(); + let className = "SimpleSnippet"; + + if (props.className) { + className += ` ${props.className}`; + } + if (props.content.tall) { + className += " tall"; + } + if (sectionHeader) { + className += " has-section-header"; + } + + return ( + <div className="snippet-hover-wrapper"> + <SnippetBase + {...props} + className={className} + textStyle={this.props.textStyle} + > + {sectionHeader} + <ConditionalWrapper + condition={sectionHeader} + wrap={this.wrapSnippetContent} + > + <img + src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} + className="icon icon-light-theme" + alt={props.content.icon_alt_text || ICON_ALT_TEXT} + /> + <img + src={ + safeURI(props.content.icon_dark_theme || props.content.icon) || + DEFAULT_ICON_PATH + } + className="icon icon-dark-theme" + alt={props.content.icon_alt_text || ICON_ALT_TEXT} + /> + <div> + {this.renderTitle()} <p className="body">{this.renderText()}</p> + {this.props.extraContent} + </div> + {<div>{this.renderButton()}</div>} + </ConditionalWrapper> + </SnippetBase> + </div> + ); + } +} diff --git a/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json new file mode 100644 index 0000000000..4970b124af --- /dev/null +++ b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json @@ -0,0 +1,159 @@ +{ + "title": "SimpleSnippet", + "description": "A simple template with an icon, text, and optional button.", + "version": "1.1.2", + "type": "object", + "definitions": { + "plainText": { + "description": "Plain text (no HTML allowed)", + "type": "string" + }, + "richText": { + "description": "Text with HTML subset allowed: i, b, u, strong, em, br", + "type": "string" + }, + "link_url": { + "description": "Target for links or buttons", + "type": "string", + "format": "uri" + } + }, + "properties": { + "title": { + "allOf": [ + { "$ref": "#/definitions/plainText" }, + { "description": "Snippet title displayed before snippet text" } + ] + }, + "text": { + "allOf": [ + { "$ref": "#/definitions/richText" }, + { + "description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br" + } + ] + }, + "icon": { + "type": "string", + "description": "Snippet icon. 64x64px. SVG or PNG preferred." + }, + "icon_dark_theme": { + "type": "string", + "description": "Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred." + }, + "icon_alt_text": { + "type": "string", + "description": "Alt text describing icon for screen readers", + "default": "" + }, + "title_icon": { + "type": "string", + "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale." + }, + "title_icon_dark_theme": { + "type": "string", + "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale." + }, + "title_icon_alt_text": { + "type": "string", + "description": "Alt text describing title icon for screen readers", + "default": "" + }, + "button_action": { + "type": "string", + "description": "The type of action the button should trigger." + }, + "button_url": { + "allOf": [ + { "$ref": "#/definitions/link_url" }, + { "description": "A url, button_label links to this" } + ] + }, + "button_action_args": { + "description": "Additional parameters for button action, example which specific menu the button should open" + }, + "button_entrypoint_value": { + "description": "String used for telemetry attribution of clicks", + "type": "string" + }, + "button_entrypoint_name": { + "description": "String used for telemetry attribution of clicks", + "type": "string" + }, + "button_label": { + "allOf": [ + { "$ref": "#/definitions/plainText" }, + { + "description": "Text for a button next to main snippet text that links to button_url. Requires button_url." + } + ] + }, + "button_color": { + "type": "string", + "description": "The text color of the button. Valid CSS color." + }, + "button_background_color": { + "type": "string", + "description": "The background color of the button. Valid CSS color." + }, + "block_button_text": { + "type": "string", + "description": "Tooltip text used for dismiss button.", + "default": "Remove this" + }, + "tall": { + "type": "boolean", + "description": "To be used by fundraising only, increases height to roughly 120px. Defaults to false." + }, + "do_not_autoblock": { + "type": "boolean", + "description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked" + }, + "links": { + "additionalProperties": { + "url": { + "allOf": [ + { "$ref": "#/definitions/link_url" }, + { "description": "The url where the link points to." } + ] + }, + "metric": { + "type": "string", + "description": "Custom event name sent with telemetry event." + }, + "args": { + "type": "string", + "description": "Additional parameters for link action, example which specific menu the button should open" + } + } + }, + "section_title_icon": { + "type": "string", + "description": "Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display." + }, + "section_title_icon_dark_theme": { + "type": "string", + "description": "Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display." + }, + "section_title_text": { + "type": "string", + "description": "Section title text. section_title_icon must also be specified to display." + }, + "section_title_url": { + "allOf": [ + { "$ref": "#/definitions/link_url" }, + { "description": "A url, section_title_text links to this" } + ] + } + }, + "additionalProperties": false, + "required": ["text"], + "dependencies": { + "button_action": ["button_label"], + "button_url": ["button_label"], + "button_color": ["button_label"], + "button_background_color": ["button_label"], + "section_title_url": ["section_title_text"], + "button_entrypoint_name": ["button_entrypoint_value"] + } +} diff --git a/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/_SimpleSnippet.scss b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/_SimpleSnippet.scss new file mode 100644 index 0000000000..1ee83a5cc9 --- /dev/null +++ b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/_SimpleSnippet.scss @@ -0,0 +1,131 @@ +$section-header-height: 30px; +$icon-width: 54px; // width of primary icon + margin + +.SimpleSnippet { + &.tall { + padding: 27px 0; + } + + p em { + color: var(--newtab-text-emphasis-text-color); + font-style: normal; + background: var(--newtab-text-emphasis-background); + } + + &.bold { + height: 176px; + + .body { + font-size: 14px; + line-height: 20px; + margin-bottom: 20px; + } + + .icon { + width: 71px; + height: 71px; + } + } + + &.takeover { + height: 344px; + + .body { + font-size: 16px; + line-height: 24px; + margin-bottom: 35px; + } + + .icon { + width: 79px; + height: 79px; + } + } + + .title { + font-size: inherit; + margin: 0; + } + + .title-inline { + display: inline; + } + + .titleIcon { + background-repeat: no-repeat; + background-size: 14px; + background-position: center; + height: 16px; + width: 16px; + margin-top: 2px; + margin-inline-end: 2px; + display: inline-block; + vertical-align: top; + } + + .body { + display: inline; + margin: 0; + } + + &.tall .icon { + margin-inline-end: 20px; + } + + &.bold, + &.takeover { + .donation-form-url, + .donation-amount { + padding-block: 8px; + } + + .icon { + margin-inline-end: 20px; + } + } + + .icon { + align-self: flex-start; + } + + &.has-section-header .innerWrapper { + // account for section header being 100% width + flex-wrap: wrap; + padding-top: 7px; + } + + // wrapper div added if section-header is displayed that allows icon/text/button + // to squish instead of wrapping. this is effectively replicating layout behavior + // when section-header is *not* present. + .innerContentWrapper { + align-items: center; + display: flex; + } + + .section-header { + flex: 0 0 100%; + margin-bottom: 10px; + } + + .section-title { + // color should match that of 'Recommended by Pocket' and 'Highlights' in newtab page + color: var(--newtab-text-primary-color); + display: inline-block; + font-size: 13px; + font-weight: bold; + margin: 0; + + a { + color: var(--newtab-text-primary-color); + font-weight: inherit; + text-decoration: none; + } + + .icon { + height: 16px; + margin-inline-end: 6px; + margin-top: -2px; + width: 16px; + } + } +} |