From 352b921b1e407f25887129d9ee0aa76e92a02e71 Mon Sep 17 00:00:00 2001 From: Ed Lee Date: Fri, 1 Apr 2022 16:20:45 +0000 Subject: [PATCH] Bug 1762488 - Remove text configuration nesting with raw unlocalized text r=emcminn Also allow configuring styles like zap and color. Clean up some unnecessary conditional rendering as Localized already handles that. Differential Revision: https://phabricator.services.mozilla.com/D142673 --- .../content/aboutwelcome.bundle.js | 79 +++++++++++++------ .../aboutwelcome/lib/AboutWelcomeDefaults.jsm | 6 +- .../aboutwelcome/components/MSLocalized.jsx | 65 ++++++++++++--- .../components/MultiStageProtonScreen.jsx | 32 +++----- .../aboutwelcome/components/Themes.jsx | 8 +- .../aboutwelcome/MultiStageAWProton.test.jsx | 8 +- 6 files changed, 126 insertions(+), 72 deletions(-) diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js index 0e9c439dc065..1a1a90e0ea26 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -355,10 +355,12 @@ __webpack_require__.r(__webpack_exports__); * 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/. */ -const MS_STRING_PROP = "string_id"; +const CONFIGURABLE_STYLES = ["color", "fontSize"]; +const ZAP_SIZE_THRESHOLD = 160; /** * Based on the .text prop, localizes an inner element if a string_id * is provided, OR renders plain text, OR hides it if nothing is provided. + * Allows configuring of some styles including zap underline and color. * * Examples: * @@ -373,6 +375,7 @@ const MS_STRING_PROP = "string_id"; * Unlocalized text * jsx: *

+ *

* output: *

Welcome

*/ @@ -381,29 +384,55 @@ const Localized = ({ text, children }) => { + // Dynamically determine the size of the zap style. + const zapRef = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createRef(); + (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => { + const { + current + } = zapRef; + if (current) requestAnimationFrame(() => current === null || current === void 0 ? void 0 : current.classList.replace("short", current.getBoundingClientRect().width > ZAP_SIZE_THRESHOLD ? "long" : "short")); + }); // Skip rendering of children with no text. + if (!text) { return null; - } + } // Allow augmenting existing child container properties. - let props = children ? children.props : {}; - let textNode; - if (typeof text === "object" && text[MS_STRING_PROP]) { - props = { ...props - }; - props["data-l10n-id"] = text[MS_STRING_PROP]; + const props = { + children: [], + className: "", + style: {}, + ...(children === null || children === void 0 ? void 0 : children.props) + }; // Support nested Localized by starting with their children. + + const textNodes = props.children; // Pick desired fluent or raw/plain text to render. + + if (text.string_id) { + props["data-l10n-id"] = text.string_id; if (text.args) props["data-l10n-args"] = JSON.stringify(text.args); + } else if (text.raw) { + textNodes.push(text.raw); } else if (typeof text === "string") { - textNode = text; - } + textNodes.push(text); + } // Add zap style and content in a way that allows fluent to insert too. - if (!children) { - return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", props, textNode); - } else if (textNode) { - return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().cloneElement(children, props, textNode); - } - return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().cloneElement(children, props); + if (text.zap) { + props.className += " welcomeZap"; + textNodes.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { + className: "short zap", + "data-l10n-name": "zap", + ref: zapRef + }, text.zap)); + } // Apply certain configurable styles. + + + CONFIGURABLE_STYLES.forEach(style => { + if (text[style]) props.style[style] = text[style]; + }); + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().cloneElement( // Provide a default container for the text if necessary. + children ?? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null), props, // Conditionally pass in as void elements can't accept empty array. + textNodes.length ? textNodes : null); }; /***/ }), @@ -679,7 +708,7 @@ class ProtonScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCom } render() { - var _this$props$appAndSys, _content$primary_butt; + var _this$props$appAndSys, _content$primary_butt, _content$primary_butt2; const { autoAdvance, @@ -716,11 +745,11 @@ class ProtonScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCom text: content.hero_text }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h1", null)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "spacer-bottom" - })), content.help_text && content.help_text.text ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { - text: content.help_text.text + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { + text: content.help_text }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", { className: "attrib-text" - })) : null) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { + }))) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "section-main" }, content.secondary_button_top ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_5__.SecondaryCTA, { content: content, @@ -749,19 +778,19 @@ class ProtonScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCom text: content.title }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h1", { id: "mainContentHeader" - })), content.subtitle ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { text: content.subtitle }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("h2", { "data-l10n-args": JSON.stringify({ "addon-name": this.props.addonName, ...((_this$props$appAndSys = this.props.appAndSystemLocaleInfo) === null || _this$props$appAndSys === void 0 ? void 0 : _this$props$appAndSys.displayNames) }) - })) : null), this.renderContentTiles(), this.renderLanguageSwitcher(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { - text: content.primary_button ? content.primary_button.label : null + }))), this.renderContentTiles(), this.renderLanguageSwitcher(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { + text: (_content$primary_butt = content.primary_button) === null || _content$primary_butt === void 0 ? void 0 : _content$primary_butt.label }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", { className: "primary", value: "primary_button", - disabled: ((_content$primary_butt = content.primary_button) === null || _content$primary_butt === void 0 ? void 0 : _content$primary_butt.disabled) === true, + disabled: ((_content$primary_butt2 = content.primary_button) === null || _content$primary_butt2 === void 0 ? void 0 : _content$primary_butt2.disabled) === true, onClick: this.props.handleAction })), content.secondary_button ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_5__.SecondaryCTA, { content: content, @@ -1077,7 +1106,7 @@ const Themes = props => { onClick: props.handleAction })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: `icon ${theme === props.activeTheme ? " selected" : ""} ${theme}` - }), label && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, { text: label }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "text" diff --git a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm index 0ab22c4b3b54..40f6e9108939 100644 --- a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm +++ b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm @@ -38,9 +38,7 @@ const DEFAULT_WELCOME_CONTENT = { string_id: "mr1-welcome-screen-hero-text", }, help_text: { - text: { - string_id: "mr1-onboarding-welcome-image-caption", - }, + string_id: "mr1-onboarding-welcome-image-caption", }, has_noodles: true, primary_button: { @@ -443,7 +441,7 @@ async function prepareContentForReact(content) { if (Services.locale.appLocaleAsBCP47.split("-")[0] !== "en") { delete content.screens?.find( screen => screen.content?.help_text?.deleteIfNotEn - )?.content.help_text.text; + )?.content.help_text; } let shouldRemoveLanguageMismatchScreen = true; diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MSLocalized.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MSLocalized.jsx index 6571a941f7f3..7c430d17129e 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/MSLocalized.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/MSLocalized.jsx @@ -2,12 +2,14 @@ * 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 from "react"; -const MS_STRING_PROP = "string_id"; +import React, { useEffect } from "react"; +const CONFIGURABLE_STYLES = ["color", "fontSize"]; +const ZAP_SIZE_THRESHOLD = 160; /** * Based on the .text prop, localizes an inner element if a string_id * is provided, OR renders plain text, OR hides it if nothing is provided. + * Allows configuring of some styles including zap underline and color. * * Examples: * @@ -22,30 +24,67 @@ const MS_STRING_PROP = "string_id"; * Unlocalized text * jsx: *

+ *

* output: *

Welcome

*/ export const Localized = ({ text, children }) => { + // Dynamically determine the size of the zap style. + const zapRef = React.createRef(); + useEffect(() => { + const { current } = zapRef; + if (current) + requestAnimationFrame(() => + current?.classList.replace( + "short", + current.getBoundingClientRect().width > ZAP_SIZE_THRESHOLD + ? "long" + : "short" + ) + ); + }); + + // Skip rendering of children with no text. if (!text) { return null; } - let props = children ? children.props : {}; - let textNode; + // Allow augmenting existing child container properties. + const props = { children: [], className: "", style: {}, ...children?.props }; + // Support nested Localized by starting with their children. + const textNodes = props.children; - if (typeof text === "object" && text[MS_STRING_PROP]) { - props = { ...props }; - props["data-l10n-id"] = text[MS_STRING_PROP]; + // Pick desired fluent or raw/plain text to render. + if (text.string_id) { + props["data-l10n-id"] = text.string_id; if (text.args) props["data-l10n-args"] = JSON.stringify(text.args); + } else if (text.raw) { + textNodes.push(text.raw); } else if (typeof text === "string") { - textNode = text; + textNodes.push(text); } - if (!children) { - return React.createElement("span", props, textNode); - } else if (textNode) { - return React.cloneElement(children, props, textNode); + // Add zap style and content in a way that allows fluent to insert too. + if (text.zap) { + props.className += " welcomeZap"; + textNodes.push( + + {text.zap} + + ); } - return React.cloneElement(children, props); + + // Apply certain configurable styles. + CONFIGURABLE_STYLES.forEach(style => { + if (text[style]) props.style[style] = text[style]; + }); + + return React.cloneElement( + // Provide a default container for the text if necessary. + children ?? , + props, + // Conditionally pass in as void elements can't accept empty array. + textNodes.length ? textNodes : null + ); }; diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx index 072d1db84f45..1144f15adb59 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx @@ -197,11 +197,9 @@ export class ProtonScreen extends React.PureComponent {
- {content.help_text && content.help_text.text ? ( - - - - ) : null} + + + ) : null}
@@ -237,25 +235,19 @@ export class ProtonScreen extends React.PureComponent {

- {content.subtitle ? ( - -

- - ) : null} + +

+

{this.renderContentTiles()} {this.renderLanguageSwitcher()}
- +