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.jsx24
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx20
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx15
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss15
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx57
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss12
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx77
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss32
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/Highlights/_Highlights.scss18
-rw-r--r--browser/components/newtab/content-src/components/TopSites/_TopSites.scss28
-rw-r--r--browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx19
-rw-r--r--browser/components/newtab/content-src/components/Weather/Weather.jsx350
-rw-r--r--browser/components/newtab/content-src/components/Weather/_Weather.scss393
-rw-r--r--browser/components/newtab/content-src/lib/link-menu-options.mjs64
-rw-r--r--browser/components/newtab/content-src/styles/_activity-stream.scss1
-rw-r--r--browser/components/newtab/content-src/styles/_icons.scss10
-rw-r--r--browser/components/newtab/content-src/styles/_theme.scss21
-rw-r--r--browser/components/newtab/content-src/styles/_variables.scss27
18 files changed, 1106 insertions, 77 deletions
diff --git a/browser/components/newtab/content-src/components/Base/Base.jsx b/browser/components/newtab/content-src/components/Base/Base.jsx
index 1738f8f51a..61722fd418 100644
--- a/browser/components/newtab/content-src/components/Base/Base.jsx
+++ b/browser/components/newtab/content-src/components/Base/Base.jsx
@@ -12,6 +12,7 @@ import { CustomizeMenu } from "content-src/components/CustomizeMenu/CustomizeMen
import React from "react";
import { Search } from "content-src/components/Search/Search";
import { Sections } from "content-src/components/Sections/Sections";
+import { Weather } from "content-src/components/Weather/Weather";
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -235,7 +236,7 @@ export class BaseContent extends React.PureComponent {
return (
<p
className={`wallpaper-attribution`}
- key={name}
+ key={name.string}
data-l10n-id="newtab-wallpaper-attribution"
data-l10n-args={JSON.stringify({
author_string: name.string,
@@ -278,6 +279,15 @@ export class BaseContent extends React.PureComponent {
`--newtab-wallpaper-dark`,
`url(${darkWallpaper?.wallpaperUrl || ""})`
);
+
+ // Add helper class to body if user has a wallpaper selected
+ if (lightWallpaper) {
+ global.document?.body.classList.add("hasWallpaperLight");
+ }
+
+ if (darkWallpaper) {
+ global.document?.body.classList.add("hasWallpaperDark");
+ }
}
}
@@ -290,6 +300,7 @@ export class BaseContent extends React.PureComponent {
const activeWallpaper =
prefs[`newtabWallpapers.wallpaper-${this.state.colorMode}`];
const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
+ const weatherEnabled = prefs.showWeather;
const { pocketConfig } = prefs;
@@ -322,10 +333,12 @@ export class BaseContent extends React.PureComponent {
showSponsoredPocketEnabled: prefs.showSponsored,
showRecentSavesEnabled: prefs.showRecentSaves,
topSitesRowsCount: prefs.topSitesRows,
+ weatherEnabled: prefs.showWeather,
};
const pocketRegion = prefs["feeds.system.topstories"];
const mayHaveSponsoredStories = prefs["system.showSponsored"];
+ const mayHaveWeather = prefs["system.showWeather"];
const { mayHaveSponsoredTopSites } = prefs;
const outerClassName = [
@@ -358,6 +371,7 @@ export class BaseContent extends React.PureComponent {
pocketRegion={pocketRegion}
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
mayHaveSponsoredStories={mayHaveSponsoredStories}
+ mayHaveWeather={mayHaveWeather}
spocMessageVariant={spocMessageVariant}
showing={customizeMenuVisible}
/>
@@ -393,6 +407,13 @@ export class BaseContent extends React.PureComponent {
<ConfirmDialog />
{wallpapersEnabled && this.renderWallpaperAttribution()}
</main>
+ <aside>
+ {weatherEnabled && (
+ <ErrorBoundary>
+ <Weather />
+ </ErrorBoundary>
+ )}
+ </aside>
</div>
</div>
);
@@ -410,4 +431,5 @@ export const Base = connect(state => ({
DiscoveryStream: state.DiscoveryStream,
Search: state.Search,
Wallpapers: state.Wallpapers,
+ Weather: state.Weather,
}))(_Base);
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 1dd13fc965..494d506da9 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
@@ -28,7 +28,7 @@ export class ContentSection extends React.PureComponent {
}
onPreferenceSelect(e) {
- // eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS
+ // eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS | WEATHER
const { preference, eventSource } = e.target.dataset;
let value;
if (e.target.nodeName === "SELECT") {
@@ -97,6 +97,7 @@ export class ContentSection extends React.PureComponent {
pocketRegion,
mayHaveSponsoredStories,
mayHaveRecentSaves,
+ mayHaveWeather,
openPreferences,
spocMessageVariant,
wallpapersEnabled,
@@ -107,6 +108,7 @@ export class ContentSection extends React.PureComponent {
topSitesEnabled,
pocketEnabled,
highlightsEnabled,
+ weatherEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showRecentSavesEnabled,
@@ -269,6 +271,22 @@ export class ContentSection extends React.PureComponent {
</label>
</div>
+ {mayHaveWeather && (
+ <div id="weather-section" className="section">
+ <label className="switch">
+ <moz-toggle
+ id="weather-toggle"
+ pressed={weatherEnabled || null}
+ onToggle={this.onPreferenceSelect}
+ data-preference="showWeather"
+ data-eventSource="WEATHER"
+ data-l10n-id="newtab-custom-weather-toggle"
+ data-l10n-attrs="label, description"
+ />
+ </label>
+ </div>
+ )}
+
{pocketRegion &&
mayHaveSponsoredStories &&
spocMessageVariant === "variant-c" && (
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
index f1c723fed2..035e84af58 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
@@ -55,12 +55,14 @@ export class _CustomizeMenu extends React.PureComponent {
role="dialog"
data-l10n-id="newtab-personalize-dialog-label"
>
- <button
- onClick={() => this.props.onClose()}
- className="close-button"
- data-l10n-id="newtab-custom-close-button"
- ref={c => (this.closeButton = c)}
- />
+ <div className="close-button-wrapper">
+ <button
+ onClick={() => this.props.onClose()}
+ className="close-button"
+ data-l10n-id="newtab-custom-close-button"
+ ref={c => (this.closeButton = c)}
+ />
+ </div>
<ContentSection
openPreferences={this.props.openPreferences}
setPref={this.props.setPref}
@@ -71,6 +73,7 @@ export class _CustomizeMenu extends React.PureComponent {
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
+ mayHaveWeather={this.props.mayHaveWeather}
spocMessageVariant={this.props.spocMessageVariant}
dispatch={this.props.dispatch}
/>
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
index c20da5ce50..403a62a50f 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
@@ -47,7 +47,8 @@
inset-block: 0;
inset-inline-end: 0;
z-index: 1001;
- padding: 16px;
+ padding-block: 0 var(--space-large);
+ padding-inline: var(--space-large);
overflow: auto;
transform: translateX(435px);
visibility: hidden;
@@ -85,9 +86,17 @@
box-shadow: $shadow-large;
}
+ .close-button-wrapper {
+ position: sticky;
+ top: 0;
+ padding-block-start: var(--space-large);
+ background-color: var(--newtab-background-color-secondary);
+ z-index: 1;
+ }
+
.close-button {
margin-inline-start: auto;
- margin-bottom: 28px;
+ margin-inline-end: var(--space-large);
white-space: nowrap;
display: block;
background-color: var(--newtab-element-secondary-color);
@@ -117,7 +126,7 @@
grid-template-columns: 1fr;
grid-template-rows: repeat(4, auto);
grid-row-gap: 32px;
- padding: 0 16px;
+ padding: var(--space-large);
.wallpapers-section h2 {
font-size: inherit;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
index 8b9d64dfc1..79d453a7c9 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
@@ -126,8 +126,11 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
this.systemTick = this.systemTick.bind(this);
this.syncRemoteSettings = this.syncRemoteSettings.bind(this);
this.onStoryToggle = this.onStoryToggle.bind(this);
+ this.handleWeatherSubmit = this.handleWeatherSubmit.bind(this);
+ this.handleWeatherUpdate = this.handleWeatherUpdate.bind(this);
this.state = {
toggledStories: {},
+ weatherQuery: "",
};
}
@@ -182,6 +185,16 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
this.dispatchSimpleAction(at.DISCOVERY_STREAM_DEV_SYNC_RS);
}
+ handleWeatherUpdate(e) {
+ this.setState({ weatherQuery: e.target.value || "" });
+ }
+
+ handleWeatherSubmit(e) {
+ e.preventDefault();
+ const { weatherQuery } = this.state;
+ this.props.dispatch(ac.SetPref("weather.query", weatherQuery));
+ }
+
renderComponent(width, component) {
return (
<table>
@@ -200,6 +213,46 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
);
}
+ renderWeatherData() {
+ const { suggestions } = this.props.state.Weather;
+ let weatherTable;
+ if (suggestions) {
+ weatherTable = (
+ <div className="weather-section">
+ <form onSubmit={this.handleWeatherSubmit}>
+ <label htmlFor="weather-query">Weather query</label>
+ <input
+ type="text"
+ min="3"
+ max="10"
+ id="weather-query"
+ onChange={this.handleWeatherUpdate}
+ value={this.weatherQuery}
+ />
+ <button type="submit">Submit</button>
+ </form>
+ <table>
+ <tbody>
+ {suggestions.map(suggestion => (
+ <tr className="message-item" key={suggestion.city_name}>
+ <td className="message-id">
+ <span>
+ {suggestion.city_name} <br />
+ </span>
+ </td>
+ <td className="message-summary">
+ <pre>{JSON.stringify(suggestion, null, 2)}</pre>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+ return weatherTable;
+ }
+
renderFeedData(url) {
const { feeds } = this.props.state.DiscoveryStream;
const feed = feeds.data[url].data;
@@ -376,6 +429,8 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
{this.renderSpocs()}
<h3>Feeds Data</h3>
{this.renderFeedsData()}
+ <h3>Weather Data</h3>
+ {this.renderWeatherData()}
</div>
);
}
@@ -412,6 +467,7 @@ export class DiscoveryStreamAdminInner extends React.PureComponent {
state={{
DiscoveryStream: this.props.DiscoveryStream,
Personalization: this.props.Personalization,
+ Weather: this.props.Weather,
}}
otherPrefs={this.props.Prefs.values}
dispatch={this.props.dispatch}
@@ -500,4 +556,5 @@ export const DiscoveryStreamAdmin = connect(state => ({
DiscoveryStream: state.DiscoveryStream,
Personalization: state.Personalization,
Prefs: state.Prefs,
+ Weather: state.Weather,
}))(_DiscoveryStreamAdmin);
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss
index a01227dd3d..dcad97c917 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss
@@ -334,4 +334,16 @@
}
}
}
+
+ .weather-section {
+ margin-block-end: 24px;
+
+ form {
+ display: flex;
+
+ label {
+ margin-inline-end: 12px;
+ }
+ }
+ }
}
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 b3d965530d..461d54899f 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -468,57 +468,34 @@ export class _DSCard extends React.PureComponent {
dispatch={this.props.dispatch}
spocMessageVariant={this.props.spocMessageVariant}
/>
- {saveToPocketCard && (
- <div className="card-stp-button-hover-background">
- <div className="card-stp-button-position-wrapper">
- {!this.props.flightId && stpButton()}
- <DSLinkMenu
- id={this.props.id}
- index={this.props.pos}
- dispatch={this.props.dispatch}
- url={this.props.url}
- title={this.props.title}
- source={source}
- type={this.props.type}
- pocket_id={this.props.pocket_id}
- shim={this.props.shim}
- bookmarkGuid={this.props.bookmarkGuid}
- flightId={
- !this.props.is_collection ? this.props.flightId : undefined
- }
- showPrivacyInfo={!!this.props.flightId}
- onMenuUpdate={this.onMenuUpdate}
- onMenuShow={this.onMenuShow}
- saveToPocketCard={saveToPocketCard}
- pocket_button_enabled={pocketButtonEnabled}
- isRecentSave={isRecentSave}
- />
- </div>
+
+ <div className="card-stp-button-hover-background">
+ <div className="card-stp-button-position-wrapper">
+ {saveToPocketCard && <>{!this.props.flightId && stpButton()}</>}
+
+ <DSLinkMenu
+ id={this.props.id}
+ index={this.props.pos}
+ dispatch={this.props.dispatch}
+ url={this.props.url}
+ title={this.props.title}
+ source={source}
+ type={this.props.type}
+ pocket_id={this.props.pocket_id}
+ shim={this.props.shim}
+ bookmarkGuid={this.props.bookmarkGuid}
+ flightId={
+ !this.props.is_collection ? this.props.flightId : undefined
+ }
+ showPrivacyInfo={!!this.props.flightId}
+ onMenuUpdate={this.onMenuUpdate}
+ onMenuShow={this.onMenuShow}
+ saveToPocketCard={saveToPocketCard}
+ pocket_button_enabled={pocketButtonEnabled}
+ isRecentSave={isRecentSave}
+ />
</div>
- )}
- {!saveToPocketCard && (
- <DSLinkMenu
- id={this.props.id}
- index={this.props.pos}
- dispatch={this.props.dispatch}
- url={this.props.url}
- title={this.props.title}
- source={source}
- type={this.props.type}
- pocket_id={this.props.pocket_id}
- shim={this.props.shim}
- bookmarkGuid={this.props.bookmarkGuid}
- flightId={
- !this.props.is_collection ? this.props.flightId : undefined
- }
- showPrivacyInfo={!!this.props.flightId}
- hostRef={this.contextMenuButtonHostRef}
- onMenuUpdate={this.onMenuUpdate}
- onMenuShow={this.onMenuShow}
- pocket_button_enabled={pocketButtonEnabled}
- isRecentSave={isRecentSave}
- />
- )}
+ </div>
</article>
);
}
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 9004e609df..e5ac19b553 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -54,12 +54,6 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
fill: $white;
}
- .context-menu-button {
- position: static;
- transition: none;
- border-radius: 3px;
- }
-
.context-menu-position-container {
position: relative;
}
@@ -83,6 +77,10 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
padding: 6px;
white-space: nowrap;
color: $white;
+
+ &:focus-visible {
+ outline: 2px solid var(--newtab-button-focus-border);
+ }
}
button,
@@ -95,6 +93,28 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
}
}
+ // Override note: The colors set here are intentionally static
+ // due to transparency issues over images.
+ .context-menu-button {
+ position: static;
+ transition: none;
+ border-radius: 3px;
+ background-color: var(--newtab-button-static-background);
+
+ &:hover {
+ background-color: var(--newtab-button-static-hover-background);
+
+ &:active {
+ background-color: var(--newtab-button-static-active-background);
+ }
+ }
+
+ &:focus-visible {
+ outline: 2px solid var(--newtab-button-focus-border);
+ background-color: var(--newtab-button-static-focus-background);
+ }
+ }
+
&.last-item {
.card-stp-button-hover-background {
.context-menu {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Highlights/_Highlights.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Highlights/_Highlights.scss
index 54b39524d8..f726e936b9 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Highlights/_Highlights.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Highlights/_Highlights.scss
@@ -37,6 +37,24 @@
a {
text-decoration: none;
}
+
+ // Override note: The colors set here are intentionally static
+ // due to transparency issues over images.
+ .context-menu-button {
+ background-color: var(--newtab-button-static-background);
+
+ &:hover {
+ background-color: var(--newtab-button-static-hover-background);
+
+ &:active {
+ background-color: var(--newtab-button-static-active-background);
+ }
+ }
+
+ &:focus-visible {
+ background-color: var(--newtab-button-static-focus-background);
+ }
+ }
}
}
}
diff --git a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
index 4e4019513d..09a20c235d 100644
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
+++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
@@ -126,12 +126,23 @@ $letter-fallback-color: $white;
}
}
+ // Necessary for when navigating by a keyboard, having the context
+ // menu open should display the "…" button. This style is a clone
+ // of the `:active` state for `.context-menu-button`
+ &.active {
+ .context-menu-button {
+ opacity: 1;
+ background-color: var(--newtab-button-active-background);
+ }
+ }
+
.context-menu-button {
background-image: url('chrome://global/skin/icons/more.svg');
+ background-color: var(--newtab-button-background);
border: 0;
border-radius: 4px;
cursor: pointer;
- fill: var(--newtab-text-primary-color);
+ fill: var(--newtab-button-text);
-moz-context-properties: fill;
height: 20px;
width: 20px;
@@ -141,11 +152,18 @@ $letter-fallback-color: $white;
top: -20px;
transition: opacity 200ms;
- &:is(:active, :focus) {
- outline: 0;
+ &:hover {
+ background-color: var(--newtab-button-hover-background);
+
+ &:active {
+ background-color: var(--newtab-button-active-background);
+ }
+ }
+
+ &:focus-visible {
+ background-color: var(--newtab-button-focus-background);
+ border-color: var(--newtab-button-focus-border);
opacity: 1;
- background-color: var(--newtab-element-hover-color);
- fill: var(--newtab-primary-action-background);
}
}
diff --git a/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx b/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx
index 0b51a146f5..6fcd4b3a15 100644
--- a/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx
+++ b/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx
@@ -4,6 +4,7 @@
import React from "react";
import { connect } from "react-redux";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
export class _WallpapersSection extends React.PureComponent {
constructor(props) {
@@ -25,6 +26,10 @@ export class _WallpapersSection extends React.PureComponent {
const prefs = this.props.Prefs.values;
const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
this.props.setPref(`newtabWallpapers.wallpaper-${colorMode}`, id);
+ this.handleUserEvent({
+ selected_wallpaper: id,
+ hadPreviousWallpaper: !!this.props.activeWallpaper,
+ });
// bug 1892095
if (
prefs["newtabWallpapers.wallpaper-dark"] === "" &&
@@ -50,6 +55,20 @@ export class _WallpapersSection extends React.PureComponent {
handleReset() {
const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
this.props.setPref(`newtabWallpapers.wallpaper-${colorMode}`, "");
+ this.handleUserEvent({
+ selected_wallpaper: "none",
+ hadPreviousWallpaper: !!this.props.activeWallpaper,
+ });
+ }
+
+ // Record user interaction when changing wallpaper and reseting wallpaper to default
+ handleUserEvent(data) {
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.WALLPAPER_CLICK,
+ data,
+ })
+ );
}
render() {
diff --git a/browser/components/newtab/content-src/components/Weather/Weather.jsx b/browser/components/newtab/content-src/components/Weather/Weather.jsx
new file mode 100644
index 0000000000..9273f9a4bd
--- /dev/null
+++ b/browser/components/newtab/content-src/components/Weather/Weather.jsx
@@ -0,0 +1,350 @@
+/* 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 { connect } from "react-redux";
+import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
+import React from "react";
+
+const VISIBLE = "visible";
+const VISIBILITY_CHANGE_EVENT = "visibilitychange";
+
+export class _Weather extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ contextMenuKeyboard: false,
+ showContextMenu: false,
+ url: "https://example.com",
+ impressionSeen: false,
+ errorSeen: false,
+ };
+ this.setImpressionRef = element => {
+ this.impressionElement = element;
+ };
+ this.setErrorRef = element => {
+ this.errorElement = element;
+ };
+ this.onClick = this.onClick.bind(this);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.onUpdate = this.onUpdate.bind(this);
+ this.onProviderClick = this.onProviderClick.bind(this);
+ }
+
+ componentDidMount() {
+ const { props } = this;
+
+ if (!props.dispatch) {
+ return;
+ }
+
+ if (props.document.visibilityState === VISIBLE) {
+ // Setup the impression observer once the page is visible.
+ this.setImpressionObservers();
+ } else {
+ // We should only ever send the latest impression stats ping, so remove any
+ // older listeners.
+ if (this._onVisibilityChange) {
+ props.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+
+ this._onVisibilityChange = () => {
+ if (props.document.visibilityState === VISIBLE) {
+ // Setup the impression observer once the page is visible.
+ this.setImpressionObservers();
+ props.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ };
+ props.document.addEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ }
+
+ componentWillUnmount() {
+ // Remove observers on unmount
+ if (this.observer && this.impressionElement) {
+ this.observer.unobserve(this.impressionElement);
+ }
+ if (this.observer && this.errorElement) {
+ this.observer.unobserve(this.errorElement);
+ }
+ if (this._onVisibilityChange) {
+ this.props.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ }
+
+ setImpressionObservers() {
+ if (this.impressionElement) {
+ this.observer = new IntersectionObserver(this.onImpression.bind(this));
+ this.observer.observe(this.impressionElement);
+ }
+ if (this.errorElement) {
+ this.observer = new IntersectionObserver(this.onError.bind(this));
+ this.observer.observe(this.errorElement);
+ }
+ }
+
+ onImpression(entries) {
+ if (this.state) {
+ const entry = entries.find(e => e.isIntersecting);
+
+ if (entry) {
+ if (this.impressionElement) {
+ this.observer.unobserve(this.impressionElement);
+ }
+
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.WEATHER_IMPRESSION,
+ })
+ );
+
+ // Stop observing since element has been seen
+ this.setState({
+ impressionSeen: true,
+ });
+ }
+ }
+ }
+
+ onError(entries) {
+ if (this.state) {
+ const entry = entries.find(e => e.isIntersecting);
+
+ if (entry) {
+ if (this.errorElement) {
+ this.observer.unobserve(this.errorElement);
+ }
+
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.WEATHER_LOAD_ERROR,
+ })
+ );
+
+ // Stop observing since element has been seen
+ this.setState({
+ errorSeen: true,
+ });
+ }
+ }
+ }
+
+ openContextMenu(isKeyBoard) {
+ if (this.props.onUpdate) {
+ this.props.onUpdate(true);
+ }
+ this.setState({
+ showContextMenu: true,
+ contextMenuKeyboard: isKeyBoard,
+ });
+ }
+
+ onClick(event) {
+ event.preventDefault();
+ this.openContextMenu(false, event);
+ }
+
+ onKeyDown(event) {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ this.openContextMenu(true, event);
+ }
+ }
+
+ onUpdate(showContextMenu) {
+ if (this.props.onUpdate) {
+ this.props.onUpdate(showContextMenu);
+ }
+ this.setState({ showContextMenu });
+ }
+
+ onProviderClick() {
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.WEATHER_OPEN_PROVIDER_URL,
+ data: {
+ source: "WEATHER",
+ },
+ })
+ );
+ }
+
+ render() {
+ // Check if weather should be rendered
+ const isWeatherEnabled = this.props.Prefs.values["system.showWeather"];
+
+ if (!isWeatherEnabled || !this.props.Weather.initialized) {
+ return false;
+ }
+
+ const { showContextMenu } = this.state;
+
+ const WEATHER_SUGGESTION = this.props.Weather.suggestions?.[0];
+
+ const {
+ className,
+ index,
+ dispatch,
+ eventSource,
+ shouldSendImpressionStats,
+ } = this.props;
+ const { props } = this;
+ const isContextMenuOpen = this.state.activeCard === index;
+
+ const outerClassName = [
+ "weather",
+ className,
+ isContextMenuOpen && "active",
+ props.placeholder && "placeholder",
+ ]
+ .filter(v => v)
+ .join(" ");
+
+ const showDetailedView =
+ this.props.Prefs.values["weather.display"] === "detailed";
+
+ // Note: The temperature units/display options will become secondary menu items
+ const WEATHER_SOURCE_CONTEXT_MENU_OPTIONS = [
+ ...(this.props.Prefs.values["weather.locationSearchEnabled"]
+ ? ["ChangeWeatherLocation"]
+ : []),
+ ...(this.props.Prefs.values["weather.temperatureUnits"] === "f"
+ ? ["ChangeTempUnitCelsius"]
+ : ["ChangeTempUnitFahrenheit"]),
+ ...(this.props.Prefs.values["weather.display"] === "simple"
+ ? ["ChangeWeatherDisplayDetailed"]
+ : ["ChangeWeatherDisplaySimple"]),
+ "HideWeather",
+ "OpenLearnMoreURL",
+ ];
+
+ // Only return the widget if we have data. Otherwise, show error state
+ if (WEATHER_SUGGESTION) {
+ return (
+ <div ref={this.setImpressionRef} className={outerClassName}>
+ <div className="weatherCard">
+ <a
+ data-l10n-id="newtab-weather-see-forecast"
+ data-l10n-args='{"provider": "AccuWeather"}'
+ href={WEATHER_SUGGESTION.forecast.url}
+ className="weatherInfoLink"
+ onClick={this.onProviderClick}
+ >
+ <div className="weatherIconCol">
+ <span
+ className={`weatherIcon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`}
+ />
+ </div>
+ <div className="weatherText">
+ <div className="weatherForecastRow">
+ <span className="weatherTemperature">
+ {
+ WEATHER_SUGGESTION.current_conditions.temperature[
+ this.props.Prefs.values["weather.temperatureUnits"]
+ ]
+ }
+ &deg;{this.props.Prefs.values["weather.temperatureUnits"]}
+ </span>
+ </div>
+ <div className="weatherCityRow">
+ <span className="weatherCity">
+ {WEATHER_SUGGESTION.city_name}
+ </span>
+ </div>
+ {showDetailedView ? (
+ <div className="weatherDetailedSummaryRow">
+ <div className="weatherHighLowTemps">
+ {/* Low Forecasted Temperature */}
+ <span>
+ {
+ WEATHER_SUGGESTION.forecast.high[
+ this.props.Prefs.values["weather.temperatureUnits"]
+ ]
+ }
+ &deg;
+ {this.props.Prefs.values["weather.temperatureUnits"]}
+ </span>
+ {/* Spacer / Bullet */}
+ <span>&bull;</span>
+ {/* Low Forecasted Temperature */}
+ <span>
+ {
+ WEATHER_SUGGESTION.forecast.low[
+ this.props.Prefs.values["weather.temperatureUnits"]
+ ]
+ }
+ &deg;
+ {this.props.Prefs.values["weather.temperatureUnits"]}
+ </span>
+ </div>
+ <span className="weatherTextSummary">
+ {WEATHER_SUGGESTION.current_conditions.summary}
+ </span>
+ </div>
+ ) : null}
+ </div>
+ </a>
+ <div className="weatherButtonContextMenuWrapper">
+ <button
+ aria-haspopup="true"
+ onKeyDown={this.onKeyDown}
+ onClick={this.onClick}
+ data-l10n-id="newtab-menu-section-tooltip"
+ className="weatherButtonContextMenu"
+ >
+ {showContextMenu ? (
+ <LinkMenu
+ dispatch={dispatch}
+ index={index}
+ source={eventSource}
+ onUpdate={this.onUpdate}
+ options={WEATHER_SOURCE_CONTEXT_MENU_OPTIONS}
+ site={{
+ url: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page",
+ }}
+ link="https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page"
+ shouldSendImpressionStats={shouldSendImpressionStats}
+ />
+ ) : null}
+ </button>
+ </div>
+ </div>
+ <span
+ data-l10n-id="newtab-weather-sponsored"
+ data-l10n-args='{"provider": "AccuWeather"}'
+ className="weatherSponsorText"
+ ></span>
+ </div>
+ );
+ }
+
+ return (
+ <div ref={this.setErrorRef} className={outerClassName}>
+ <div className="weatherNotAvailable">
+ <span className="icon icon-small-spacer icon-info-critical" />{" "}
+ <span data-l10n-id="newtab-weather-error-not-available"></span>
+ </div>
+ </div>
+ );
+ }
+}
+
+export const Weather = connect(state => ({
+ Weather: state.Weather,
+ Prefs: state.Prefs,
+ IntersectionObserver: globalThis.IntersectionObserver,
+ document: globalThis.document,
+}))(_Weather);
diff --git a/browser/components/newtab/content-src/components/Weather/_Weather.scss b/browser/components/newtab/content-src/components/Weather/_Weather.scss
new file mode 100644
index 0000000000..0616530f98
--- /dev/null
+++ b/browser/components/newtab/content-src/components/Weather/_Weather.scss
@@ -0,0 +1,393 @@
+// Custom font sizing for weather widget
+:root {
+ --newtab-weather-content-font-size: 11px;
+ --newtab-weather-sponsor-font-size: 8px;
+}
+
+.weather {
+ font-size: var(--font-size-root);
+ position: absolute;
+ left: var(--space-xlarge);
+ top: var(--space-xlarge);
+ z-index: 1;
+}
+
+// Unavailable / Error State
+.weatherNotAvailable {
+ font-size: var(--newtab-weather-content-font-size);
+ color: var(--text-color-error);
+ display: flex;
+ align-items: center;
+
+ .icon {
+ fill: var(--icon-color-critical);
+ -moz-context-properties: fill;
+ }
+}
+
+.weatherCard {
+ margin-block-end: var(--space-xsmall);
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: stretch;
+ border-radius: var(--border-radius-medium);
+ overflow: hidden;
+
+ &:hover, &:focus-within {
+ ~ .weatherSponsorText {
+ visibility: visible;
+ }
+ }
+
+ &:focus-within {
+ overflow: visible;
+ }
+
+ &:hover {
+ box-shadow: var(--box-shadow-10);
+ background: var(--background-color-box);
+ }
+
+ a {
+ color: var(--text-color);
+ }
+
+}
+
+.weatherSponsorText {
+ visibility: hidden;
+ font-size: var(--newtab-weather-sponsor-font-size);
+ color: var(--text-color-deemphasized);
+}
+
+.weatherInfoLink, .weatherButtonContextMenuWrapper {
+ appearance: none;
+ background-color: var(--background-color-ghost);
+ border: 0;
+ padding: var(--space-small);
+ cursor: pointer;
+
+ &:hover {
+ // TODO: Add Wallpaper Background Color Fix
+ background-color: var(--button-background-color-ghost-hover);
+
+ &::after {
+ background-color: transparent
+ }
+
+ &:active {
+ // TODO: Add Wallpaper Background Color Fix
+ background-color: var(--button-background-color-ghost-active);
+ }
+ }
+
+ &:focus-visible {
+ outline: var(--focus-outline);
+ }
+
+ // Contrast fix for users who have wallpapers set
+ .hasWallpaperDark & {
+ @media (prefers-color-scheme: dark) {
+ // TODO: Replace with token
+ background-color: rgba(35, 34, 43, 70%);
+
+ &:hover {
+ background-color: var(--newtab-button-static-hover-background);
+ }
+
+ &:hover:active {
+ background-color: var(--newtab-button-static-active-background);
+ }
+ }
+
+ @media (prefers-contrast) and (prefers-color-scheme: dark) {
+ background-color: var(--background-color-box);
+ }
+ }
+
+ .hasWallpaperLight & {
+ @media (prefers-color-scheme: light) {
+ // TODO: Replace with token
+ background-color: rgba(255, 255, 255, 70%);
+
+ &:hover {
+ background-color: var(--newtab-button-static-hover-background);
+ }
+
+ &:hover:active {
+ background-color: var(--newtab-button-static-active-background);
+ }
+ }
+
+ @media (prefers-contrast) and (prefers-color-scheme: light) {
+ background-color: var(--background-color-box);
+ }
+ }
+
+}
+
+.weatherInfoLink {
+ display: flex;
+ gap: var(--space-medium);
+ padding: var(--space-small) var(--space-medium);
+ border-radius: var(--border-radius-medium) 0 0 var(--border-radius-medium);
+ text-decoration: none;
+ color: var(--text-color);;
+ min-width: 130px;
+ max-width: 190px;
+ text-overflow: ellipsis;
+
+ @media(min-width: $break-point-medium) {
+ min-width: unset;
+ }
+
+ &:hover ~.weatherButtonContextMenuWrapper {
+ &::after {
+ background-color: transparent
+ }
+ }
+
+ &:focus-visible {
+ border-radius: var(--border-radius-medium);
+
+ ~ .weatherButtonContextMenuWrapper {
+ &::after {
+ background-color: transparent
+ }
+ }
+ }
+}
+
+.weatherButtonContextMenuWrapper {
+ position: relative;
+ cursor: pointer;
+ border-radius: 0 var(--border-radius-medium) var(--border-radius-medium) 0;
+ display: flex;
+ align-items: stretch;
+ width: 50px;
+ padding: 0;
+
+ &::after {
+ content: '';
+ left: 0;
+ top: 10px;
+ height: calc(100% - 20px);
+ width: 1px;
+ background-color: var(--newtab-button-static-background);
+ display: block;
+ position: absolute;
+ z-index: 0;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ &::after {
+ background-color: var(--color-gray-70);
+ }
+ }
+
+ &:hover {
+ &::after {
+ background-color: transparent
+ }
+ }
+
+ &:focus-visible {
+ border-radius: var(--border-radius-medium);
+
+ &::after {
+ background-color: transparent
+ }
+ }
+}
+
+.weatherButtonContextMenu {
+ background-image: url('chrome://global/skin/icons/more.svg');
+ background-repeat: no-repeat;
+ background-size: var(--size-item-small) auto;
+ background-position: center;
+ background-color: transparent;
+ cursor: pointer;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ appearance: none;
+ min-width: var(--size-item-large);
+}
+
+.weatherText {
+ height: min-content;
+}
+
+.weatherCityRow, .weatherForecastRow, .weatherDetailedSummaryRow {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: var(--space-small);
+}
+
+.weatherForecastRow {
+ text-transform: uppercase;
+ font-weight: var(--font-weight-bold);
+}
+
+.weatherCityRow {
+ color: var(--text-color-deemphasized);
+}
+
+.weatherCity {
+ text-overflow: ellipsis;
+ font-size: var(--font-size-small);
+}
+
+// Add additional margin if detailed summary is in view
+.weatherCityRow + .weatherDetailedSummaryRow {
+ margin-block-start: var(--space-xsmall);
+}
+
+.weatherDetailedSummaryRow {
+ font-size: var(--newtab-weather-content-font-size);
+ gap: var(--space-large);
+}
+
+.weatherHighLowTemps {
+ display: flex;
+ gap: var(--space-xxsmall);
+ text-transform: uppercase;
+ word-spacing: var(--space-xxsmall);
+}
+
+.weatherTextSummary {
+ text-align: center;
+ max-width: 90px;
+}
+
+.weatherTemperature {
+ font-size: var(--font-size-large);
+}
+
+// Weather Symbol Icons
+.weatherIconCol {
+ width: var(--size-item-large);
+ height: var(--size-item-large);
+ aspect-ratio: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-self: center;
+}
+
+.weatherIcon {
+ width: var(--size-item-large);
+ height: auto;
+ vertical-align: middle;
+
+ @media (prefers-contrast) {
+ -moz-context-properties: fill, stroke;
+ fill: currentColor;
+ stroke: currentColor;
+ }
+
+ &.iconId1 {
+ content: url('chrome://browser/skin/weather/sunny.svg');
+ // height: var(--size-item-large);
+ }
+
+ &.iconId2 {
+ content: url('chrome://browser/skin/weather/mostly-sunny.svg');
+ // height: var(--size-item-large);
+ }
+
+ &:is(.iconId3, .iconId4, .iconId6) {
+ content: url('chrome://browser/skin/weather/partly-sunny.svg');
+ // height: var(--size-item-large);
+ }
+
+ &.iconId5 {
+ content: url('chrome://browser/skin/weather/hazy-sunshine.svg');
+ // height: var(--size-item-large);
+ }
+
+ &:is(.iconId7, .iconId8) {
+ content: url('chrome://browser/skin/weather/cloudy.svg');
+ }
+
+ &.iconId11 {
+ content: url('chrome://browser/skin/weather/fog.svg');
+ }
+
+ &.iconId12 {
+ content: url('chrome://browser/skin/weather/showers.svg');
+ }
+
+ &:is(.iconId13, .iconId14) {
+ content: url('chrome://browser/skin/weather/mostly-cloudy-with-showers.svg');
+ // height: var(--size-item-large);
+ }
+
+ &.iconId15 {
+ content: url('chrome://browser/skin/weather/thunderstorms.svg');
+ }
+
+ &:is(.iconId16, .iconId17) {
+ content: url('chrome://browser/skin/weather/mostly-cloudy-with-thunderstorms.svg');
+ }
+
+ &.iconId18 {
+ content: url('chrome://browser/skin/weather/rain.svg');
+ }
+
+ &:is(.iconId19, .iconId20, .iconId25) {
+ content: url('chrome://browser/skin/weather/flurries.svg');
+ }
+
+ &.iconId21 {
+ content: url('chrome://browser/skin/weather/partly-sunny-with-flurries.svg');
+ }
+
+ &:is(.iconId22, .iconId23) {
+ content: url('chrome://browser/skin/weather/snow.svg');
+ }
+
+ &:is(.iconId24, .iconId31) {
+ content: url('chrome://browser/skin/weather/ice.svg');
+ }
+
+ &:is(.iconId26, .iconId29) {
+ content: url('chrome://browser/skin/weather/freezing-rain.svg');
+ }
+
+ &.iconId30 {
+ content: url('chrome://browser/skin/weather/hot.svg');
+ }
+
+ &.iconId32 {
+ content: url('chrome://browser/skin/weather/windy.svg');
+ }
+
+ &.iconId33 {
+ content: url('chrome://browser/skin/weather/night-clear.svg');
+ }
+
+ &:is(.iconId34, .iconId35, .iconId36, .iconId38) {
+ content: url('chrome://browser/skin/weather/night-mostly-clear.svg');
+ }
+
+ &.iconId37 {
+ content: url('chrome://browser/skin/weather/night-hazy-moonlight.svg');
+ }
+
+ &:is(.iconId39, .iconId40) {
+ content: url('chrome://browser/skin/weather/night-partly-cloudy-with-showers.svg');
+ height: var(--size-item-large);
+ }
+
+ &:is(.iconId41, .iconId42) {
+ content: url('chrome://browser/skin/weather/night-partly-cloudy-with-thunderstorms.svg');
+ }
+
+ &:is(.iconId43, .iconId44) {
+ content: url('chrome://browser/skin/weather/night-mostly-cloudy-with-flurries.svg');
+ }
+}
diff --git a/browser/components/newtab/content-src/lib/link-menu-options.mjs b/browser/components/newtab/content-src/lib/link-menu-options.mjs
index f10a5e34c6..23dcf8b050 100644
--- a/browser/components/newtab/content-src/lib/link-menu-options.mjs
+++ b/browser/components/newtab/content-src/lib/link-menu-options.mjs
@@ -306,4 +306,68 @@ export const LinkMenuOptions = {
: LinkMenuOptions.EmptyItem(),
OpenInPrivateWindow: (site, index, eventSource, isEnabled) =>
isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem(),
+ ChangeWeatherLocation: () => ({
+ id: "newtab-weather-menu-change-location",
+ action: ac.OnlyToMain({
+ type: at.CHANGE_WEATHER_LOCATION,
+ data: { url: "https://mozilla.org" },
+ }),
+ }),
+ ChangeWeatherDisplaySimple: () => ({
+ id: "newtab-weather-menu-change-weather-display-simple",
+ action: ac.OnlyToMain({
+ type: at.SET_PREF,
+ data: {
+ name: "weather.display",
+ value: "simple",
+ },
+ }),
+ }),
+ ChangeWeatherDisplayDetailed: () => ({
+ id: "newtab-weather-menu-change-weather-display-detailed",
+ action: ac.OnlyToMain({
+ type: at.SET_PREF,
+ data: {
+ name: "weather.display",
+ value: "detailed",
+ },
+ }),
+ }),
+ ChangeTempUnitFahrenheit: () => ({
+ id: "newtab-weather-menu-change-temperature-units-fahrenheit",
+ action: ac.OnlyToMain({
+ type: at.SET_PREF,
+ data: {
+ name: "weather.temperatureUnits",
+ value: "f",
+ },
+ }),
+ }),
+ ChangeTempUnitCelsius: () => ({
+ id: "newtab-weather-menu-change-temperature-units-celsius",
+ action: ac.OnlyToMain({
+ type: at.SET_PREF,
+ data: {
+ name: "weather.temperatureUnits",
+ value: "c",
+ },
+ }),
+ }),
+ HideWeather: () => ({
+ id: "newtab-weather-menu-hide-weather",
+ action: ac.OnlyToMain({
+ type: at.SET_PREF,
+ data: {
+ name: "showWeather",
+ value: false,
+ },
+ }),
+ }),
+ OpenLearnMoreURL: site => ({
+ id: "newtab-weather-menu-learn-more",
+ action: ac.OnlyToMain({
+ type: at.OPEN_LINK,
+ data: { url: site.url },
+ }),
+ }),
};
diff --git a/browser/components/newtab/content-src/styles/_activity-stream.scss b/browser/components/newtab/content-src/styles/_activity-stream.scss
index d2e66667b2..580f35416e 100644
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -149,6 +149,7 @@ input {
@import '../components/ConfirmDialog/ConfirmDialog';
@import '../components/CustomizeMenu/CustomizeMenu';
@import '../components/WallpapersSection/WallpapersSection';
+@import '../components/Weather/Weather';
@import '../components/Card/Card';
@import '../components/CollapsibleSection/CollapsibleSection';
@import '../components/DiscoveryStreamAdmin/DiscoveryStreamAdmin';
diff --git a/browser/components/newtab/content-src/styles/_icons.scss b/browser/components/newtab/content-src/styles/_icons.scss
index 8be97ad9ae..39879b2b44 100644
--- a/browser/components/newtab/content-src/styles/_icons.scss
+++ b/browser/components/newtab/content-src/styles/_icons.scss
@@ -4,7 +4,7 @@
background-size: $icon-size;
-moz-context-properties: fill;
display: inline-block;
- color: var(--newtab-text-primary-color);
+ color: var(--icon-color);
fill: currentColor;
height: $icon-size;
vertical-align: middle;
@@ -70,6 +70,10 @@
background-image: url('chrome://global/skin/icons/info.svg');
}
+ &.icon-info-critical {
+ background-image: url('chrome://activity-stream/content/data/content/assets/glyph-info-critical-16.svg');
+ }
+
&.icon-help {
background-image: url('chrome://global/skin/icons/help.svg');
}
@@ -167,6 +171,10 @@
background-image: url('chrome://activity-stream/content/data/content/assets/glyph-webextension-16.svg');
}
+ &.icon-weather {
+ background-image: url('chrome://browser/skin/weather/sunny.svg');
+ }
+
&.icon-highlights {
background-image: url('chrome://global/skin/icons/highlights.svg');
}
diff --git a/browser/components/newtab/content-src/styles/_theme.scss b/browser/components/newtab/content-src/styles/_theme.scss
index 6b097ae93e..78b54f4f8e 100644
--- a/browser/components/newtab/content-src/styles/_theme.scss
+++ b/browser/components/newtab/content-src/styles/_theme.scss
@@ -38,6 +38,21 @@ $shadow-image-inset: inset 0 0 0 0.5px $black-15;
--newtab-element-hover-color: color-mix(in srgb, var(--newtab-background-color) 90%, #{$black});
--newtab-element-active-color: color-mix(in srgb, var(--newtab-background-color) 80%, #{$black});
+ // --newtab-button-*-color is used on all new page card/top site options buttons
+ --newtab-button-background: var(--button-background-color);
+ --newtab-button-focus-background: var(--newtab-button-background);
+ --newtab-button-focus-border: var(--focus-outline-color);
+ --newtab-button-hover-background: var(--button-background-color-hover);
+ --newtab-button-active-background: var(--button-background-color-active);
+ --newtab-button-text: var(--button-text-color);
+
+ // --newtab-button-static*-color is used on pocket cards and require a
+ // static color unit due to transparency issues with `color-mix`
+ --newtab-button-static-background: #F0F0F4;
+ --newtab-button-static-focus-background: var(--newtab-button-static-background);
+ --newtab-button-static-hover-background: #E0E0E6;
+ --newtab-button-static-active-background: #CFCFD8;
+
// --newtab-element-secondary*-color is used when an element needs to be set
// off from the secondary background color.
--newtab-element-secondary-color: color-mix(in srgb, currentColor 5%, transparent);
@@ -87,6 +102,12 @@ $shadow-image-inset: inset 0 0 0 0.5px $black-15;
--newtab-primary-element-text-color: #{$primary-text-color-dark};
--newtab-wordmark-color: #{$newtab-wordmark-darktheme-color};
--newtab-status-success: #{$status-dark-green};
+
+ // --newtab-button-static*-color is used on pocket cards and require a
+ // static color unit due to transparency issues with `color-mix`
+ --newtab-button-static-background: #2B2A33;
+ --newtab-button-static-hover-background: #52525E;
+ --newtab-button-static-active-background: #5B5B66;
}
}
diff --git a/browser/components/newtab/content-src/styles/_variables.scss b/browser/components/newtab/content-src/styles/_variables.scss
index 9fd0083841..43672c7796 100644
--- a/browser/components/newtab/content-src/styles/_variables.scss
+++ b/browser/components/newtab/content-src/styles/_variables.scss
@@ -157,14 +157,17 @@ $customize-menu-border-tint: 1px solid rgba(0, 0, 0, 15%);
@mixin context-menu-button {
.context-menu-button {
background-clip: padding-box;
- background-color: var(--newtab-background-color-secondary);
+ background-color: var(--newtab-button-background);
background-image: url('chrome://global/skin/icons/more.svg');
background-position: 55%;
- border: $border-primary;
+ border: 0;
+ outline: $border-primary;
+ outline-width: 0;
border-radius: 100%;
box-shadow: $context-menu-button-boxshadow;
cursor: pointer;
- fill: var(--newtab-text-primary-color);
+ color: var(--button-text-color);
+ fill: var(--newtab-button-text);
height: $context-menu-button-size;
inset-inline-end: math.div(-$context-menu-button-size, 2);
opacity: 0;
@@ -175,10 +178,26 @@ $customize-menu-border-tint: 1px solid rgba(0, 0, 0, 15%);
transition-property: transform, opacity;
width: $context-menu-button-size;
- &:is(:active, :focus) {
+ &:is(:active, :focus-visible, :hover) {
opacity: 1;
transform: scale(1);
}
+
+ &:is(:hover) {
+ background-color: var(--newtab-button-hover-background);
+ }
+
+ &:is(:focus-visible) {
+ outline-color: var(--newtab-button-focus-border);
+ background-color: var(--newtab-button-focus-background);
+ outline-width: 4px;
+ }
+
+ &:is(:active) {
+ background-color: var(--newtab-button-active-background);
+ }
+
+
}
}