From 881dbfaddc310073f6b72e2f21de7121ed4f867a Mon Sep 17 00:00:00 2001 From: zenparsing Date: Mon, 28 Oct 2024 17:37:25 -0400 Subject: [PATCH] Add prototype for NTP refresh --- .storybook/main.ts | 6 +- browser/about_flags.cc | 7 + browser/brave_browser_features.cc | 4 + browser/brave_browser_features.h | 1 + browser/brave_content_browser_client.cc | 11 + .../brave_new_tab_page_refresh/BUILD.gn | 32 + .../assets/favorites_active.svg | 42 ++ .../assets/favorites_active_dark.svg | 42 ++ .../assets/favorites_inactive.svg | 37 ++ .../assets/favorites_inactive_dark.svg | 37 ++ .../assets/frequently_visited_active.svg | 42 ++ .../assets/frequently_visited_active_dark.svg | 42 ++ .../assets/frequently_visited_inactive.svg | 37 ++ .../frequently_visited_inactive_dark.svg | 37 ++ .../assets/guardian_vpn_logo.svg | 3 + .../assets/rewards_bat_coin.svg | 23 + .../assets/talk_graphic.svg | 16 + .../assets/vpn_shield_connected.svg | 11 + .../assets/vpn_shield_disconnected.svg | 4 + .../brave_new_tab_page.html | 22 + .../brave_new_tab_page.tsx | 56 ++ .../components/app.style.ts | 225 +++++++ .../components/app.tsx | 72 +++ .../components/background.style.ts | 54 ++ .../components/background.tsx | 172 ++++++ .../components/background_caption.style.ts | 47 ++ .../components/background_caption.tsx | 69 +++ .../components/clock.tsx | 45 ++ .../components/context/locale_context.tsx | 59 ++ .../components/context/new_tab_context.tsx | 32 + .../components/context/rewards_context.tsx | 32 + .../components/context/search_context.tsx | 32 + .../components/context/top_sites_context.tsx | 32 + .../components/context/vpn_context.tsx | 32 + .../components/link.tsx | 43 ++ .../components/modal.style.ts | 57 ++ .../components/modal.tsx | 63 ++ .../components/pcdn_image.tsx | 26 + .../components/popover.tsx | 46 ++ .../components/search/engine_icon.tsx | 47 ++ .../components/search/search_box.style.ts | 158 +++++ .../components/search/search_box.tsx | 318 ++++++++++ .../components/search/search_results.style.ts | 120 ++++ .../components/search/search_results.tsx | 144 +++++ .../settings/background_panel.style.ts | 100 +++ .../components/settings/background_panel.tsx | 196 ++++++ .../settings/background_type_panel.tsx | 143 +++++ .../components/settings/clock_panel.style.ts | 23 + .../components/settings/clock_panel.tsx | 71 +++ .../components/settings/search_panel.style.ts | 62 ++ .../components/settings/search_panel.tsx | 78 +++ .../settings/settings_modal.style.ts | 57 ++ .../components/settings/settings_modal.tsx | 116 ++++ .../settings/top_sites_panel.style.ts | 99 +++ .../components/settings/top_sites_panel.tsx | 84 +++ .../settings/widget_position_icon.tsx | 35 ++ .../settings/widgets_panel.style.ts | 37 ++ .../components/settings/widgets_panel.tsx | 125 ++++ .../top_sites/remove_toast.style.ts | 63 ++ .../components/top_sites/remove_toast.tsx | 63 ++ .../top_sites/top_site_edit_modal.style.ts | 34 ++ .../top_sites/top_site_edit_modal.tsx | 98 +++ .../components/top_sites/top_site_tile.tsx | 175 ++++++ .../components/top_sites/top_sites.style.ts | 309 ++++++++++ .../components/top_sites/top_sites.tsx | 244 ++++++++ .../components/widgets/ntp_widget.style.ts | 20 + .../components/widgets/ntp_widget.tsx | 20 + .../widgets/product_widget_stack.style.ts | 41 ++ .../widgets/product_widget_stack.tsx | 116 ++++ .../widgets/rewards_widget.style.ts | 85 +++ .../components/widgets/rewards_widget.tsx | 105 ++++ .../components/widgets/stats_widget.style.ts | 46 ++ .../components/widgets/stats_widget.tsx | 139 +++++ .../components/widgets/talk_widget.style.ts | 59 ++ .../components/widgets/talk_widget.tsx | 42 ++ .../components/widgets/vpn_widget.style.ts | 135 +++++ .../components/widgets/vpn_widget.tsx | 144 +++++ .../lib/favicon_url.ts | 8 + .../lib/image_loader.ts | 38 ++ .../lib/inline_css_vars.ts | 14 + .../lib/locale_strings.ts | 93 +++ .../lib/optional.ts | 31 + .../lib/scoped_css.ts | 58 ++ .../brave_new_tab_page_refresh/lib/store.ts | 93 +++ .../lib/url_input.ts | 27 + .../lib/url_sanitizer.ts | 19 + .../lib/use_model_state.ts | 35 ++ .../models/backgrounds.ts | 99 +++ .../models/new_tab_model.ts | 139 +++++ .../models/rewards_model.ts | 37 ++ .../models/search_model.ts | 96 +++ .../models/top_sites_model.ts | 53 ++ .../models/vpn_model.ts | 56 ++ .../stories/index.tsx | 44 ++ .../stories/sb_locale.ts | 107 ++++ .../stories/sb_new_tab_model.ts | 151 +++++ .../stories/sb_rewards_model.ts | 38 ++ .../stories/sb_search_model.ts | 100 +++ .../stories/sb_top_sites_model.ts | 114 ++++ .../stories/sb_vpn_model.ts | 50 ++ .../brave_new_tab_page_refresh/tsconfig.json | 8 + .../webui/callback_listeners.ts | 25 + .../webui/debounce_listener.ts | 10 + .../webui/new_tab_page_proxy.ts | 35 ++ .../webui/search_box_proxy.ts | 35 ++ .../webui/webui_locale.ts | 18 + .../webui/webui_new_tab_model.ts | 302 +++++++++ .../webui/webui_rewards_model.ts | 72 +++ .../webui/webui_search_model.ts | 233 +++++++ .../webui/webui_top_sites_model.ts | 117 ++++ .../webui/webui_vpn_model.ts | 148 +++++ browser/sources.gni | 8 + browser/ui/config.gni | 1 + .../webui/brave_new_tab_page_refresh/BUILD.gn | 78 +++ .../ui/webui/brave_new_tab_page_refresh/DEPS | 4 + .../background_facade.cc | 342 +++++++++++ .../background_facade.h | 76 +++ .../brave_new_tab_page.mojom | 240 ++++++++ .../brave_new_tab_page_ui.cc | 336 ++++++++++ .../brave_new_tab_page_ui.h | 75 +++ .../custom_image_chooser.cc | 86 +++ .../custom_image_chooser.h | 55 ++ .../new_tab_page_handler.cc | 573 ++++++++++++++++++ .../new_tab_page_handler.h | 199 ++++++ .../top_sites_facade.cc | 142 +++++ .../top_sites_facade.h | 79 +++ .../update_observer.cc | 85 +++ .../update_observer.h | 56 ++ .../brave_new_tab_page_refresh/vpn_facade.cc | 60 ++ .../brave_new_tab_page_refresh/vpn_facade.h | 75 +++ .../webui/brave_web_ui_controller_factory.cc | 5 + components/resources/BUILD.gn | 2 + .../resources/brave_components_resources.grd | 1 + .../resources/brave_components_strings.grd | 1 + .../brave_new_tab_page_resources.grdp | 6 + .../resources/brave_new_tab_page_strings.grdp | 323 ++++++++++ ...tools-gritsettings-resource_ids.spec.patch | 2 +- resources/resource_ids.spec | 4 + ui/webui/resources/BUILD.gn | 2 + 139 files changed, 11018 insertions(+), 2 deletions(-) create mode 100644 browser/resources/brave_new_tab_page_refresh/BUILD.gn create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/favorites_active.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/favorites_active_dark.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive_dark.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active_dark.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive_dark.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/guardian_vpn_logo.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/rewards_bat_coin.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/talk_graphic.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_connected.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_disconnected.svg create mode 100644 browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.html create mode 100644 browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/app.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/app.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/background.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/background.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/background_caption.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/background_caption.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/clock.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/locale_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/new_tab_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/rewards_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/search_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/top_sites_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/context/vpn_context.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/link.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/modal.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/modal.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/pcdn_image.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/popover.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/search/engine_icon.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/search/search_box.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/search/search_box.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/search/search_results.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/search/search_results.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/background_panel.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/background_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/background_type_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/clock_panel.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/clock_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/search_panel.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/search_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/settings_modal.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/settings_modal.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/top_sites_panel.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/top_sites_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/widget_position_icon.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/widgets_panel.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/settings/widgets_panel.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/remove_toast.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/remove_toast.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/top_site_edit_modal.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/top_site_edit_modal.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/top_site_tile.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/top_sites.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/top_sites/top_sites.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/ntp_widget.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/ntp_widget.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/product_widget_stack.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/product_widget_stack.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/rewards_widget.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/rewards_widget.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/stats_widget.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/stats_widget.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/talk_widget.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/talk_widget.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/vpn_widget.style.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/components/widgets/vpn_widget.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/favicon_url.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/image_loader.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/inline_css_vars.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/locale_strings.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/optional.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/scoped_css.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/store.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/url_input.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/url_sanitizer.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/lib/use_model_state.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/backgrounds.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/new_tab_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/rewards_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/search_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/top_sites_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/models/vpn_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/index.tsx create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_locale.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_new_tab_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_rewards_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_search_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_top_sites_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/stories/sb_vpn_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/tsconfig.json create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/callback_listeners.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/debounce_listener.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/new_tab_page_proxy.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/search_box_proxy.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_locale.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_new_tab_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_rewards_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_search_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_top_sites_model.ts create mode 100644 browser/resources/brave_new_tab_page_refresh/webui/webui_vpn_model.ts create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/BUILD.gn create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/DEPS create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/background_facade.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/background_facade.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/brave_new_tab_page.mojom create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/brave_new_tab_page_ui.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/brave_new_tab_page_ui.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/custom_image_chooser.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/custom_image_chooser.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/new_tab_page_handler.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/new_tab_page_handler.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/top_sites_facade.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/top_sites_facade.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/update_observer.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/update_observer.h create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/vpn_facade.cc create mode 100644 browser/ui/webui/brave_new_tab_page_refresh/vpn_facade.h create mode 100644 components/resources/brave_new_tab_page_resources.grdp create mode 100644 components/resources/brave_new_tab_page_strings.grdp diff --git a/.storybook/main.ts b/.storybook/main.ts index c201a222ea00..1d022664c8e9 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -22,7 +22,11 @@ const slashStoriesIndexer: Indexer = { const config: StorybookConfig = { stories: process.env.STORYBOOK_STORYPATH ? [`../${process.env.STORYBOOK_STORYPATH}`] - : ['../components/**/stories/*.tsx', '../components/**/*.stories.tsx'], + : [ + '../components/**/stories/*.tsx', + '../components/**/*.stories.tsx', + '../browser/resources/**/stories/*.tsx' + ], typescript: { check: false, reactDocgen: false, diff --git a/browser/about_flags.cc b/browser/about_flags.cc index 06e4dff3521c..c74a47a6517d 100644 --- a/browser/about_flags.cc +++ b/browser/about_flags.cc @@ -535,6 +535,13 @@ const flags_ui::FeatureEntry::FeatureVariation kZCashFeatureVariations[] = { kOsDesktop, \ FEATURE_VALUE_TYPE(features::kBraveNtpSearchWidget), \ }, \ + { \ + "brave-use-updated-ntp", \ + "Use the updated New Tab Page", \ + "Uses an updated version of the New Tab Page", \ + kOsDesktop, \ + FEATURE_VALUE_TYPE(features::kUseUpdatedNTP), \ + }, \ { \ "brave-adblock-cname-uncloaking", \ "Enable CNAME uncloaking", \ diff --git a/browser/brave_browser_features.cc b/browser/brave_browser_features.cc index 85c244faad08..8e3ca0b1dd30 100644 --- a/browser/brave_browser_features.cc +++ b/browser/brave_browser_features.cc @@ -9,6 +9,10 @@ namespace features { +BASE_FEATURE(kUseUpdatedNTP, + "BraveUseUpdatedNewTabPage", + base::FEATURE_DISABLED_BY_DEFAULT); + // Cleanup Session Cookies on browser restart if Session Restore is enabled. BASE_FEATURE(kBraveCleanupSessionCookiesOnSessionRestore, "BraveCleanupSessionCookiesOnSessionRestore", diff --git a/browser/brave_browser_features.h b/browser/brave_browser_features.h index ad30895a251e..5d06cd360b4f 100644 --- a/browser/brave_browser_features.h +++ b/browser/brave_browser_features.h @@ -13,6 +13,7 @@ namespace features { +BASE_DECLARE_FEATURE(kUseUpdatedNTP); BASE_DECLARE_FEATURE(kBraveCleanupSessionCookiesOnSessionRestore); BASE_DECLARE_FEATURE(kBraveCopyCleanLinkByDefault); BASE_DECLARE_FEATURE(kBraveCopyCleanLinkFromJs); diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index 38cc978efdb4..262bb23199a9 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -90,6 +90,7 @@ #include "brave/components/decentralized_dns/content/decentralized_dns_navigation_throttle.h" #include "brave/components/google_sign_in_permission/google_sign_in_permission_throttle.h" #include "brave/components/google_sign_in_permission/google_sign_in_permission_util.h" +#include "brave/components/ntp_background_images/browser/mojom/ntp_background_images.mojom.h" #include "brave/components/playlist/common/buildflags/buildflags.h" #include "brave/components/playlist/common/features.h" #include "brave/components/request_otr/common/buildflags/buildflags.h" @@ -234,6 +235,7 @@ using extensions::ChromeContentBrowserClientExtensionsPart; #if !BUILDFLAG(IS_ANDROID) #include "brave/browser/new_tab/new_tab_shows_navigation_throttle.h" #include "brave/browser/ui/geolocation/brave_geolocation_permission_tab_helper.h" +#include "brave/browser/ui/webui/brave_new_tab_page_refresh/brave_new_tab_page_ui.h" #include "brave/browser/ui/webui/brave_news_internals/brave_news_internals_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_page_top_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_panel_ui.h" @@ -666,14 +668,23 @@ void BraveContentBrowserClient::RegisterWebUIInterfaceBrokers( .Add() .Add(); + auto ntp_refresh_registration = + registry.ForWebUI() + .Add() + .Add() + .Add< + ntp_background_images::mojom::SponsoredRichMediaAdEventHandler>(); + #if BUILDFLAG(ENABLE_BRAVE_VPN) if (brave_vpn::IsBraveVPNFeatureEnabled()) { ntp_registration.Add(); + ntp_refresh_registration.Add(); } #endif if (base::FeatureList::IsEnabled(features::kBraveNtpSearchWidget)) { ntp_registration.Add(); + ntp_refresh_registration.Add(); } if (base::FeatureList::IsEnabled( diff --git a/browser/resources/brave_new_tab_page_refresh/BUILD.gn b/browser/resources/brave_new_tab_page_refresh/BUILD.gn new file mode 100644 index 000000000000..e98d8dcbb2e0 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (c) 2025 The Brave Authors. All rights reserved. +# 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 https://mozilla.org/MPL/2.0/. + +import("//brave/components/common/typescript.gni") +import("//mojo/public/tools/bindings/mojom.gni") + +assert(!is_android) + +transpile_web_ui("resources") { + entry_points = [ [ + "brave_new_tab_page", + rebase_path("brave_new_tab_page.tsx"), + ] ] + resource_name = "brave_new_tab_page" + output_module = true + deps = [ + "//brave/browser/ui/webui/brave_new_tab_page_refresh:mojom_js", + "//brave/components/brave_ads/core/mojom:mojom_js", + "//brave/components/brave_rewards/core/mojom:webui_js", + "//brave/components/brave_vpn/common/mojom:mojom_js", + "//brave/components/ntp_background_images/browser/mojom:mojom_js", + ] +} + +pack_web_resources("generated_resources") { + resource_name = "brave_new_tab_page" + output_dir = + "$root_gen_dir/brave/browser/resources/brave_new_tab_page_refresh" + deps = [ ":resources" ] +} diff --git a/browser/resources/brave_new_tab_page_refresh/assets/favorites_active.svg b/browser/resources/brave_new_tab_page_refresh/assets/favorites_active.svg new file mode 100644 index 000000000000..52232b6c9cd2 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/favorites_active.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/favorites_active_dark.svg b/browser/resources/brave_new_tab_page_refresh/assets/favorites_active_dark.svg new file mode 100644 index 000000000000..9a7d067763a5 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/favorites_active_dark.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive.svg b/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive.svg new file mode 100644 index 000000000000..b8013a6b7fb9 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive_dark.svg b/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive_dark.svg new file mode 100644 index 000000000000..1b97ecad6758 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/favorites_inactive_dark.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active.svg b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active.svg new file mode 100644 index 000000000000..1390115434f0 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active_dark.svg b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active_dark.svg new file mode 100644 index 000000000000..ee6a933aba6a --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_active_dark.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive.svg b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive.svg new file mode 100644 index 000000000000..8f7c89a505e5 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive_dark.svg b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive_dark.svg new file mode 100644 index 000000000000..8b64a2d17311 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/frequently_visited_inactive_dark.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/guardian_vpn_logo.svg b/browser/resources/brave_new_tab_page_refresh/assets/guardian_vpn_logo.svg new file mode 100644 index 000000000000..0558e74de79a --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/guardian_vpn_logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/rewards_bat_coin.svg b/browser/resources/brave_new_tab_page_refresh/assets/rewards_bat_coin.svg new file mode 100644 index 000000000000..fc7e80963f8b --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/rewards_bat_coin.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/talk_graphic.svg b/browser/resources/brave_new_tab_page_refresh/assets/talk_graphic.svg new file mode 100644 index 000000000000..d8ce336182f8 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/talk_graphic.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_connected.svg b/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_connected.svg new file mode 100644 index 000000000000..80a553b7d6f4 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_connected.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_disconnected.svg b/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_disconnected.svg new file mode 100644 index 000000000000..749ceed5d497 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/assets/vpn_shield_disconnected.svg @@ -0,0 +1,4 @@ + + + + diff --git a/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.html b/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.html new file mode 100644 index 000000000000..c3dc7bd690fb --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.html @@ -0,0 +1,22 @@ + + + + + + New Tab + + + + + + + + + +
+ + diff --git a/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.tsx b/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.tsx new file mode 100644 index 000000000000..4b2055eaf887 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/brave_new_tab_page.tsx @@ -0,0 +1,56 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { setIconBasePath } from '@brave/leo/react/icon' + +import { LocaleContext } from './components/context/locale_context' +import { NewTabContext } from './components/context/new_tab_context' +import { createNewTabModel } from './webui/webui_new_tab_model' +import { SearchContext } from './components/context/search_context' +import { createSearchModel } from './webui/webui_search_model' +import { TopSitesContext } from './components/context/top_sites_context' +import { createTopSitesModel } from './webui/webui_top_sites_model' +import { RewardsContext } from './components/context/rewards_context' +import { createRewardsModel } from './webui/webui_rewards_model' +import { VPNContext } from './components/context/vpn_context' +import { createVPNModel } from './webui/webui_vpn_model' +import { createLocale } from './webui/webui_locale' +import { App } from './components/app' + +setIconBasePath('chrome://resources/brave-icons') + +const newTabModel = createNewTabModel() +const searchModel = createSearchModel() +const topSitesModel = createTopSitesModel() +const rewardsModel = createRewardsModel() +const vpnModel = createVPNModel() + +Object.assign(self, { + [Symbol.for('ntpInternals')]: { + newTabModel, + searchModel, + topSitesModel, + rewardsModel, + vpnModel + } +}) + +createRoot(document.getElementById('root')!).render( + + + + + + + + + + + + + +) diff --git a/browser/resources/brave_new_tab_page_refresh/components/app.style.ts b/browser/resources/brave_new_tab_page_refresh/components/app.style.ts new file mode 100644 index 000000000000..aa28f233d86f --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/components/app.style.ts @@ -0,0 +1,225 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +import { color, font } from '@brave/leo/tokens/css/variables' +import { scoped, global } from '../lib/scoped_css' + +export const style = scoped.css` + + & { + --search-transition-duration: 120ms; + } + + .top-controls { + position: absolute; + inset-block-start: 4px; + inset-inline-end: 4px; + min-height: 24px; + display: flex; + gap: 8px; + align-items: center; + z-index: 2; + } + + .settings { + --leo-icon-size: 20px; + + opacity: 0.5; + color: #fff; + filter: drop-shadow(0px 1px 4px rgba(0, 0, 0, 0.60)); + + &:hover { + opacity: 0.7; + cursor: pointer; + } + } + + .clock { + font: ${font.large.semibold}; + color: #fff; + opacity: .8; + } + + .allow-background-pointer-events { + /* This element will allow pointer events to target the background. */ + pointer-events: none; + + /* But children will not (unless the explicitly allow it). */ + > :not(.allow-background-pointer-events) { + pointer-events: auto; + } + + /* And not when a popover is open. When a popover is open, pointer events + on a background iframe will not "light-dismiss" the popover. */ + :scope:has(:popover-open) & { + pointer-events: auto; + } + } + + main { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; + gap: 16px; + padding: 16px 24px; + + > * { + transition: + opacity var(--search-transition-duration), + transform var(--search-transition-duration); + + .search-box-expanded & { + opacity: 0; + transform: scale(0.9); + } + } + } + + .topsites-container { + padding: 16px 0; + align-self: stretch; + display: flex; + gap: 16px; + } + + .searchbox-container { + align-self: stretch; + + .search-box-expanded & { + opacity: 1; + transform: none; + } + } + + .spacer { + flex: 1 1 auto; + align-self: stretch; + } + + .widget-container { + align-self: stretch; + flex: 0 0 120px; + display: flex; + justify-content: center; + align-items: stretch; + gap: 16px; + } + + &.widget-position-top { + .widget-container { + order: 1; + margin-top: 16px; + margin-bottom: 18px; + } + + .searchbox-container { + order: 2; + } + + .spacer { + order: 3; + } + + .background-caption-container { + order: 4; + } + + .topsites-container { + order: 5; + } + } + +` + +global.css` + @scope (${style.selector}) { + + & { + font: ${font.default.regular}; + color: ${color.text.primary}; + interpolate-size: allow-keywords; + } + + button { + margin: 0; + padding: 0; + background: 0; + border: none; + text-align: unset; + width: unset; + font: inherit; + cursor: pointer; + + &:disabled { + cursor: default; + } + } + + h2 { + font: ${font.heading.h2}; + margin: 0; + } + + h3 { + font: ${font.heading.h3}; + margin: 0; + } + + h4 { + font: ${font.heading.h4}; + margin: 0; + } + + p { + margin: 0; + } + + dialog, [popover] { + border: none; + color: inherit; + margin: 0; + padding: 0; + background: none; + + &::backdrop { + background-color: transparent; + } + } + + .popover-menu { + padding: 4px; + border-radius: 8px; + border: solid 1px ${color.divider.subtle}; + background: ${color.container.background}; + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + gap: 4px; + min-width: 180px; + + .divider { + height: 1px; + background: ${color.divider.subtle}; + } + + button { + --leo-icon-size: 20px; + + padding: 8px 24px 8px 8px; + border-radius: 4px; + display: flex; + align-items: center; + gap: 16px; + + &:hover, &.highlight { + background: ${color.container.highlight}; + } + } + } + } +` diff --git a/browser/resources/brave_new_tab_page_refresh/components/app.tsx b/browser/resources/brave_new_tab_page_refresh/components/app.tsx new file mode 100644 index 000000000000..21c3ae8cd022 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/components/app.tsx @@ -0,0 +1,72 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' +import Icon from '@brave/leo/react/icon' + +import { useNewTabState } from './context/new_tab_context' +import { SearchBox } from './search/search_box' +import { Background } from './background' +import { BackgroundCaption } from './background_caption' +import { SettingsModal, SettingsView } from './settings/settings_modal' +import { TopSites } from './top_sites/top_sites' +import { Clock } from './clock' +import { StatsWidget } from './widgets/stats_widget' +import { ProductWidgetStack } from './widgets/product_widget_stack' + +import { style } from './app.style' + +export function App() { + const widgetPosition = useNewTabState((state) => state.widgetPosition) + + const [settingsView, setSettingsView] = + React.useState(null) + + return ( +
+ +
+ + +
+
+
+ +
+
+ setSettingsView('search')} + /> +
+
+
+ +
+
+ + +
+
+ setSettingsView(null)} + /> +
+ ) +} diff --git a/browser/resources/brave_new_tab_page_refresh/components/background.style.ts b/browser/resources/brave_new_tab_page_refresh/components/background.style.ts new file mode 100644 index 000000000000..4d7310613ef3 --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/components/background.style.ts @@ -0,0 +1,54 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +import { scoped } from '../lib/scoped_css' + +export const style = scoped.css` + + & { + position: fixed; + inset: 0; + z-index: 0; + display: flex; + animation-name: fade-in; + animation-timing-function: ease-in-out; + animation-duration: 350ms; + animation-delay: 0s; + animation-fill-mode: both; + + > * { + flex: 1 1 auto; + } + } + + .image-background { + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + background-image: + linear-gradient( + rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0) 80%, + rgba(0, 0, 0, 0.6) 100%), + var(--ntp-background); + } + + .color-background { + background: var(--ntp-background); + } + + iframe { + border: none; + + &.loading { + opacity: 1; + } + } + + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } + } + +` diff --git a/browser/resources/brave_new_tab_page_refresh/components/background.tsx b/browser/resources/brave_new_tab_page_refresh/components/background.tsx new file mode 100644 index 000000000000..f8466fa9466e --- /dev/null +++ b/browser/resources/brave_new_tab_page_refresh/components/background.tsx @@ -0,0 +1,172 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +import { SponsoredRichMediaEventType, SponsoredImageBackground } from '../models/new_tab_model' +import { useNewTabState, useNewTabModel } from './context/new_tab_context' +import { openLink } from './link' +import { loadImage } from '../lib/image_loader' + +import { style } from './background.style' + +export function Background() { + const currentBackground = useNewTabState((state) => state.currentBackground) + + function renderBackground() { + if (!currentBackground) { + return + } + + switch (currentBackground.type) { + case 'brave': + case 'custom': + case 'sponsored-image': + return + case 'sponsored-rich-media': + return + case 'solid': + case 'gradient': + return + } + } + + return ( +
+ {renderBackground()} +
+ ) +} + +function ColorBackground(props: { colorValue: string }) { + React.useEffect(() => { + setBackgroundVariable(props.colorValue) + }, [props.colorValue]) + + return
+} + +function setBackgroundVariable(value: string) { + if (value) { + document.body.style.setProperty('--ntp-background', value) + } else { + document.body.style.removeProperty('--ntp-background') + } +} + +function ImageBackground(props: { url: string }) { + // In order to avoid a "flash-of-unloaded-image", load the image in the + // background and only update the background CSS variable when the image has + // finished loading. + React.useEffect(() => { + loadImage(props.url).then((loaded) => { + if (loaded) { + setBackgroundVariable(`url(${CSS.escape(props.url)})`) + } + }) + }, [props.url]) + + return
+} + +function SponsoredRichMediaBackground( + props: { background: SponsoredImageBackground } +) { + const newTabModel = useNewTabModel() + + const [sponsoredRichMediaBaseUrl] = useNewTabState((state) => [ + state.sponsoredRichMediaBaseUrl + ]) + + return ( + { + const eventType = getRichMediaEventType(data) + if (eventType) { + newTabModel.notifySponsoredRichMediaEvent(eventType) + } + if (eventType === 'click') { + const url = props.background.logo?.destinationUrl + if (url) { + openLink(url) + } + } + }} + /> + ) +} + +function getRichMediaEventType(data: any): SponsoredRichMediaEventType | null { + if (!data || data.type !== 'richMediaEvent') { + return null + } + const value = String(data.value || '') + switch (value) { + case 'click': + case 'interaction': + case 'mediaPlay': + case 'media25': + case 'media100': + return value + } + return null +} + +interface IframeBackgroundProps { + url: string + expectedOrigin: string + onMessage: (data: unknown) => void +} + +function IframeBackground(props: IframeBackgroundProps) { + const iframeRef = React.useRef(null) + const [contentLoaded, setContentLoaded] = React.useState(false) + + React.useEffect(() => { + function listener(event: MessageEvent) { + if (!event.origin || event.origin !== props.expectedOrigin) { + return + } + if (!event.source || event.source !== iframeRef.current?.contentWindow) { + return + } + props.onMessage(event.data) + } + + window.addEventListener('message', listener) + return () => { window.removeEventListener('message', listener) } + }, [props.expectedOrigin, props.onMessage]) + + return ( +