commit f71aa40659e07cafa692ab77ac81a1e6edbfbeb0
parent f65b5bd1491fe89e625bf8a01fde0413cc9ef440
Author: Maxx Crawford <mcrawford@mozilla.com>
Date: Wed, 19 Nov 2025 16:22:32 +0000
Bug 1996308 - Add container element around Widgets section r=home-newtab-reviewers,reemhamz
Differential Revision: https://phabricator.services.mozilla.com/D270924
Diffstat:
9 files changed, 169 insertions(+), 51 deletions(-)
diff --git a/browser/extensions/newtab/content-src/components/Base/_Base.scss b/browser/extensions/newtab/content-src/components/Base/_Base.scss
@@ -76,7 +76,8 @@ main {
padding-block-end: 0;
.ds-card {
- width: 296px;
+ // Note: This differs when the Sections layout is enabled
+ width: var(--newtab-card-grid-layout-width);
}
.ds-card-grid:not(.ds-section-grid) {
diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TrendingSearches/_TrendingSearches.scss b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TrendingSearches/_TrendingSearches.scss
@@ -106,7 +106,7 @@
.trending-searches-list-view {
.ds-column-grid & {
// Match width of other items in grid layout
- width: 296px;
+ width: var(--newtab-card-grid-layout-width);
}
.ds-section-grid & {
diff --git a/browser/extensions/newtab/content-src/components/Widgets/FocusTimer/_FocusTimer.scss b/browser/extensions/newtab/content-src/components/Widgets/FocusTimer/_FocusTimer.scss
@@ -6,7 +6,13 @@
@include newtab-card-style;
grid-column: span 1;
- width: var(--newtab-card-width-medium);
+ width: var(--newtab-card-grid-layout-width);
+
+ // Match the new card width if sections are enabled
+ .has-sections-grid & {
+ width: var(--newtab-card-width-medium);
+ }
+
border-radius: var(--border-radius-large);
padding-block: var(--space-medium);
height: var(--newtab-card-height);
diff --git a/browser/extensions/newtab/content-src/components/Widgets/Lists/_Lists.scss b/browser/extensions/newtab/content-src/components/Widgets/Lists/_Lists.scss
@@ -6,7 +6,13 @@
@include newtab-card-style;
grid-column: span 1;
- width: var(--newtab-card-width-medium);
+ width: var(--newtab-card-grid-layout-width);
+
+ // Match the new card width if sections are enabled
+ .has-sections-grid & {
+ width: var(--newtab-card-width-medium);
+ }
+
border-radius: var(--border-radius-large);
padding-block-start: var(--space-medium);
height: var(--newtab-card-height);
diff --git a/browser/extensions/newtab/content-src/components/Widgets/Widgets.jsx b/browser/extensions/newtab/content-src/components/Widgets/Widgets.jsx
@@ -105,19 +105,22 @@ function Widgets() {
return (
<div className="widgets-wrapper">
- <div className="widgets-container">
- {listsEnabled && (
- <Lists
- dispatch={dispatch}
- handleUserInteraction={handleUserInteraction}
- />
- )}
- {timerEnabled && (
- <FocusTimer
- dispatch={dispatch}
- handleUserInteraction={handleUserInteraction}
- />
- )}
+ <div className="widgets-section-container">
+ <h1 data-l10n-id="newtab-widget-section-title"></h1>
+ <div className="widgets-container">
+ {listsEnabled && (
+ <Lists
+ dispatch={dispatch}
+ handleUserInteraction={handleUserInteraction}
+ />
+ )}
+ {timerEnabled && (
+ <FocusTimer
+ dispatch={dispatch}
+ handleUserInteraction={handleUserInteraction}
+ />
+ )}
+ </div>
</div>
{messageData?.content?.messageType === "WidgetMessage" && (
<MessageWrapper dispatch={dispatch}>
diff --git a/browser/extensions/newtab/content-src/components/Widgets/_Widgets.scss b/browser/extensions/newtab/content-src/components/Widgets/_Widgets.scss
@@ -7,35 +7,86 @@
}
.widgets-wrapper {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- position: relative;
+ margin-inline: auto;
padding-block-end: var(--space-large);
+ max-width: max-content;
}
+.widgets-section-container {
+ padding-block: var(--space-xsmall) var(--space-large);
+ padding-inline: var(--space-large);
+ background-color: var(--button-background-color);
+ border-radius: var(--border-radius-large);
+
+ // Bug 1908010 - This overwrites the design system color because of a
+ // known transparency issue with color-mix syntax when a wallpaper is set
+ .lightWallpaper &,
+ .darkWallpaper & {
+ background-color: var(--newtab-weather-background-color); // stylelint-disable-line stylelint-plugin-mozilla/use-background-color-tokens
+
+ @media (prefers-contrast) {
+ background-color: var(--background-color-box);
+ }
+ }
+
+ // Mirrors the grid-gap spacing used on
+ // .ds-outer-wrapper-breakpoint-override .ds-card-grid
+ @media(min-width: $break-point-widest) {
+ padding-block-end: var(--space-xlarge);
+ padding-inline: var(--space-xlarge);
+ }
+
+ // Sections Layout Override
+ .has-sections-grid & {
+ @media(min-width: $break-point-widest) {
+ padding-block-end: var(--space-large);
+ padding-inline: var(--space-large);
+ }
+ }
+
+ h1 {
+ font-size: var(--font-size-large);
+ font-weight: var(--font-weight-bold);
+ }
+}
+
+// Bug 1996308: This layout is somewhat brittle as the design dictates the parent container (.widgets-wrapper)
+// be only as wide as the width of this container (.widgets-container) while still being responsive.
+// Once we have four widgets, we can revert most of this logic as the container will be 100%.
.widgets-container {
+ --widgets-card-width: var(--newtab-card-grid-layout-width);
+ // This is the maximum number of widgets the Widgets row can support
+ --widgets-max-cols: 2;
+ --widgets-grid-gap: var(--space-large);
+
+ // Update the card width when Sections is enabled
+ .has-sections-grid & {
+ --widgets-card-width: var(--newtab-card-width-medium);
+ }
+
&:has(.lists),
&:has(.focus-timer) {
display: grid;
- align-items: flex-start;
- grid-template-columns: repeat(auto-fit, minmax(300px, 300px));
- gap: var(--space-large);
- justify-content: center;
- width: 100%;
+ grid-template-columns: repeat(auto-fit, var(--widgets-card-width));
+ gap: var(--widgets-grid-gap);
+
+ // The max-width logic is only necessary when displaying more than one column
+ @media(min-width: $break-point-medium) {
+ max-width: calc(var(--widgets-max-cols) * var(--widgets-card-width) + (var(--widgets-max-cols) - 1) * var(--widgets-grid-gap));
+ justify-content: center;
+ }
// Mirrors the grid-gap spacing used on
// .ds-outer-wrapper-breakpoint-override .ds-card-grid
@media(min-width: $break-point-widest) {
- gap: var(--space-xlarge);
+ --widgets-grid-gap: var(--space-xlarge);
}
- }
- // Sections Layout Override
- .has-sections-grid & {
- @media(min-width: $break-point-widest) {
- gap: var(--space-large);
+ // Sections Layout Override
+ .has-sections-grid & {
+ @media(min-width: $break-point-widest) {
+ --widgets-grid-gap: var(--space-large);
+ }
}
}
}
diff --git a/browser/extensions/newtab/content-src/styles/_variables.scss b/browser/extensions/newtab/content-src/styles/_variables.scss
@@ -4,6 +4,7 @@
:root {
--newtab-card-height: 282px;
--newtab-card-width-medium: 300px;
+ --newtab-card-grid-layout-width: 296px; // Matches widget used in Base.jsx
}
$primary-blue: var(--color-accent-primary);
diff --git a/browser/extensions/newtab/css/activity-stream.css b/browser/extensions/newtab/css/activity-stream.css
@@ -31,6 +31,7 @@ input {
:root {
--newtab-card-height: 282px;
--newtab-card-width-medium: 300px;
+ --newtab-card-grid-layout-width: 296px;
}
:root {
@@ -516,7 +517,7 @@ main section {
padding-block-end: 0;
}
.ds-outer-wrapper-breakpoint-override main .ds-card {
- width: 296px;
+ width: var(--newtab-card-grid-layout-width);
}
.ds-outer-wrapper-breakpoint-override main .ds-card-grid:not(.ds-section-grid) {
grid-gap: var(--space-large);
@@ -4366,30 +4367,69 @@ dialog:dir(rtl)::after {
}
.widgets-wrapper {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- position: relative;
+ margin-inline: auto;
padding-block-end: var(--space-large);
+ max-width: max-content;
}
+.widgets-section-container {
+ padding-block: var(--space-xsmall) var(--space-large);
+ padding-inline: var(--space-large);
+ background-color: var(--button-background-color);
+ border-radius: var(--border-radius-large);
+}
+.lightWallpaper .widgets-section-container, .darkWallpaper .widgets-section-container {
+ background-color: var(--newtab-weather-background-color);
+}
+@media (prefers-contrast) {
+ .lightWallpaper .widgets-section-container, .darkWallpaper .widgets-section-container {
+ background-color: var(--background-color-box);
+ }
+}
+@media (min-width: 1122px) {
+ .widgets-section-container {
+ padding-block-end: var(--space-xlarge);
+ padding-inline: var(--space-xlarge);
+ }
+}
+@media (min-width: 1122px) {
+ .has-sections-grid .widgets-section-container {
+ padding-block-end: var(--space-large);
+ padding-inline: var(--space-large);
+ }
+}
+.widgets-section-container h1 {
+ font-size: var(--font-size-large);
+ font-weight: var(--font-weight-bold);
+}
+
+.widgets-container {
+ --widgets-card-width: var(--newtab-card-grid-layout-width);
+ --widgets-max-cols: 2;
+ --widgets-grid-gap: var(--space-large);
+}
+.has-sections-grid .widgets-container {
+ --widgets-card-width: var(--newtab-card-width-medium);
+}
.widgets-container:has(.lists), .widgets-container:has(.focus-timer) {
display: grid;
- align-items: flex-start;
- grid-template-columns: repeat(auto-fit, minmax(300px, 300px));
- gap: var(--space-large);
- justify-content: center;
- width: 100%;
+ grid-template-columns: repeat(auto-fit, var(--widgets-card-width));
+ gap: var(--widgets-grid-gap);
+}
+@media (min-width: 610px) {
+ .widgets-container:has(.lists), .widgets-container:has(.focus-timer) {
+ max-width: calc(var(--widgets-max-cols) * var(--widgets-card-width) + (var(--widgets-max-cols) - 1) * var(--widgets-grid-gap));
+ justify-content: center;
+ }
}
@media (min-width: 1122px) {
.widgets-container:has(.lists), .widgets-container:has(.focus-timer) {
- gap: var(--space-xlarge);
+ --widgets-grid-gap: var(--space-xlarge);
}
}
@media (min-width: 1122px) {
- .has-sections-grid .widgets-container {
- gap: var(--space-large);
+ .has-sections-grid .widgets-container:has(.lists), .has-sections-grid .widgets-container:has(.focus-timer) {
+ --widgets-grid-gap: var(--space-large);
}
}
@@ -4429,7 +4469,7 @@ dialog:dir(rtl)::after {
background: var(--newtab-background-card);
transition: opacity 0.2s ease;
grid-column: span 1;
- width: var(--newtab-card-width-medium);
+ width: var(--newtab-card-grid-layout-width);
border-radius: var(--border-radius-large);
padding-block-start: var(--space-medium);
height: var(--newtab-card-height);
@@ -4441,6 +4481,9 @@ dialog:dir(rtl)::after {
.lists:hover {
background: var(--newtab-background-color-secondary);
}
+.has-sections-grid .lists {
+ width: var(--newtab-card-width-medium);
+}
.lists .confetti-canvas {
position: absolute;
top: 0;
@@ -4649,7 +4692,7 @@ dialog:dir(rtl)::after {
background: var(--newtab-background-card);
transition: opacity 0.2s ease;
grid-column: span 1;
- width: var(--newtab-card-width-medium);
+ width: var(--newtab-card-grid-layout-width);
border-radius: var(--border-radius-large);
padding-block: var(--space-medium);
height: var(--newtab-card-height);
@@ -4661,6 +4704,9 @@ dialog:dir(rtl)::after {
.focus-timer:hover {
background: var(--newtab-background-color-secondary);
}
+.has-sections-grid .focus-timer {
+ width: var(--newtab-card-width-medium);
+}
.focus-timer-tabs {
display: flex;
@@ -9238,7 +9284,7 @@ dialog:dir(rtl)::after {
}
.ds-column-grid .trending-searches-list-view {
- width: 296px;
+ width: var(--newtab-card-grid-layout-width);
}
.ds-section-grid .trending-searches-list-view {
order: 1;
diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js
@@ -13609,6 +13609,10 @@ function Widgets() {
return /*#__PURE__*/external_React_default().createElement("div", {
className: "widgets-wrapper"
}, /*#__PURE__*/external_React_default().createElement("div", {
+ className: "widgets-section-container"
+ }, /*#__PURE__*/external_React_default().createElement("h1", {
+ "data-l10n-id": "newtab-widget-section-title"
+ }), /*#__PURE__*/external_React_default().createElement("div", {
className: "widgets-container"
}, listsEnabled && /*#__PURE__*/external_React_default().createElement(Lists, {
dispatch: dispatch,
@@ -13616,7 +13620,7 @@ function Widgets() {
}), timerEnabled && /*#__PURE__*/external_React_default().createElement(FocusTimer, {
dispatch: dispatch,
handleUserInteraction: handleUserInteraction
- })), messageData?.content?.messageType === "WidgetMessage" && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
+ }))), messageData?.content?.messageType === "WidgetMessage" && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
dispatch: dispatch
}, /*#__PURE__*/external_React_default().createElement(WidgetsFeatureHighlight, {
dispatch: dispatch