diff --git a/browser/ui/webui/brave_webui_source.cc b/browser/ui/webui/brave_webui_source.cc index 0b22235c3bc3..aee21c474b8c 100644 --- a/browser/ui/webui/brave_webui_source.cc +++ b/browser/ui/webui/brave_webui_source.cc @@ -232,10 +232,8 @@ void CustomizeWebUIHTMLSource(content::WebUI* web_ui, { "braveNewsNoContentActionLabel", IDS_BRAVE_NEWS_NO_CONTENT_ACTION_LABEL}, // NOLINT { "braveNewsPopularTitle", IDS_BRAVE_NEWS_POPULAR_TITLE}, { "braveNewsNewsPeek", IDS_BRAVE_NEWS_NEWS_PEEK}, - { "braveNewsMyFeedHeading", IDS_BRAVE_NEWS_MY_FEED_HEADING}, { "braveNewsForYouFeed",IDS_BRAVE_NEWS_FOR_YOU_FEED}, { "braveNewsFollowingFeed", IDS_BRAVE_NEWS_FOLLOWING_FEED}, - { "braveNewsAddChannelsOrPublishers",IDS_BRAVE_NEWS_ADD_CHANNELS_OR_PUBLISHERS}, { "braveNewsPublishersHeading", IDS_BRAVE_NEWS_PUBLISHERS_HEADING}, { "braveNewsShowAll", IDS_BRAVE_NEWS_SHOW_ALL}, { "braveNewsShowLess", IDS_BRAVE_NEWS_SHOW_LESS}, diff --git a/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx b/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx index 405192205b63..a823019b97bf 100644 --- a/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx +++ b/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx @@ -4,19 +4,26 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import Flex from '$web-common/Flex' import { getLocale } from '$web-common/locale' -import Button from '@brave/leo/react/button' import Icon from '@brave/leo/react/icon' import { radius, spacing } from '@brave/leo/tokens/css' import * as React from 'react' import styled from 'styled-components' import Feed from '../../../../brave_news/browser/resources/Feed' -import FeedNavigation from '../../../../brave_news/browser/resources/FeedNavigation' import NewsButton from '../../../../brave_news/browser/resources/NewsButton' import Variables from '../../../../brave_news/browser/resources/Variables' import { useBraveNews } from '../../../../brave_news/browser/resources/shared/Context' import { CLASSNAME_PAGE_STUCK } from '../page' +import SettingsButton from '../../../../brave_news/browser/resources/SettingsButton' +import useMediaQuery from '$web-common/useMediaQuery' + +const SidebarMenu = React.lazy(() => import('./SidebarMenu')) +const FeedNavigation = React.lazy(() => import('../../../../brave_news/browser/resources/FeedNavigation')) + +const isSmallQuery = '(max-width: 1024px)' const Root = styled(Variables)` + --bn-top-bar-height: 78px; + padding-top: ${spacing.xl}; display: grid; @@ -50,17 +57,30 @@ const ButtonsContainer = styled.div` visibility: visible; } - display: flex; - gap: ${spacing.m}; padding: ${spacing.m}; background: var(--bn-glass-container); + backdrop-filter: blur(64px); + + @media ${isSmallQuery} { + height: var(--bn-top-bar-height); + + inset: 0; + bottom: unset; + padding: ${spacing['2Xl']} ${spacing.xl}; + border-radius: 0; + } ` -const SettingsButton = styled(Button)` - --leo-button-color: var(--bn-glass-50); - --leo-button-radius: ${radius.s}; - --leo-button-padding: ${spacing.s}; +const ButtonSpacer = styled.div` + max-width: min(540px, 100vw); + + display: flex; + justify-content: flex-end; + gap: ${spacing.m}; + + margin-left: auto; + margin-right: auto; ` const LoadNewContentButton = styled(NewsButton)` @@ -69,9 +89,15 @@ const LoadNewContentButton = styled(NewsButton)` top: ${spacing['3Xl']}; flex-grow: 0; + + @media ${isSmallQuery} { + top: calc(var(--bn-top-bar-height) + var(--leo-spacing-m)); + } ` export default function FeedV2() { + const isSmall = useMediaQuery(isSmallQuery) + const { feedV2, setCustomizePage, refreshFeedV2, feedV2UpdatesAvailable } = useBraveNews() const ref = React.useRef() @@ -88,7 +114,7 @@ export default function FeedV2() { return - + {!isSmall && } {feedV2UpdatesAvailable && @@ -98,12 +124,15 @@ export default function FeedV2() { - setCustomizePage('news')} title={getLocale('braveNewsCustomizeFeed')}> - - - { - refreshFeedV2() - }}> + + {isSmall && } + setCustomizePage('news')} title={getLocale('braveNewsCustomizeFeed')}> + + + { + refreshFeedV2() + }}> + } diff --git a/components/brave_new_tab_ui/components/default/braveNews/SidebarMenu.tsx b/components/brave_new_tab_ui/components/default/braveNews/SidebarMenu.tsx new file mode 100644 index 000000000000..29228f361cfa --- /dev/null +++ b/components/brave_new_tab_ui/components/default/braveNews/SidebarMenu.tsx @@ -0,0 +1,60 @@ +// Copyright (c) 2024 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 styled from 'styled-components'; +import FeedNavigation from '../../../../brave_news/browser/resources/FeedNavigation'; +import SettingsButton from '../../../../brave_news/browser/resources/SettingsButton'; +import Icon from '@brave/leo/react/icon'; + +const Container = styled.dialog` + top: var(--bn-top-bar-height); + height: calc(100vh - var(--bn-top-bar-height)); + + overflow: hidden; + + margin-left: 0; + margin-top: 0; + padding: 0; + + position: fixed; + display: flex; + + transition: transform 0.2s ease-in-out; + transform: translateX(-100%); + + background: var(--bn-glass-card); + backdrop-filter: blur(64px); + border: none; + outline: none; + + &[open] { + transform: translateX(0); + } + + &> div { + background: unset; + } +` + +const MenuButton = styled(SettingsButton)` + margin-right: auto; +` + +export default function SidebarMenu() { + const dialogRef = React.useRef() + return dialogRef.current?.showModal()}> + + { + // Close the menu on click outside. + const bounds = e.currentTarget.getBoundingClientRect() + if (e.clientX < bounds.x || e.clientY < bounds.y || e.clientX > bounds.right || e.clientY > bounds.bottom) { + e.currentTarget.close() + } + }}> + + + +} diff --git a/components/brave_news/browser/combined_feed_parsing.cc b/components/brave_news/browser/combined_feed_parsing.cc index b57c4cf22fa9..88200ac75eae 100644 --- a/components/brave_news/browser/combined_feed_parsing.cc +++ b/components/brave_news/browser/combined_feed_parsing.cc @@ -9,6 +9,7 @@ #include #include +#include "base/feature_list.h" #include "base/logging.h" #include "base/notreached.h" #include "base/strings/strcat.h" @@ -16,9 +17,10 @@ #include "base/time/time.h" #include "base/types/expected.h" #include "brave/components/brave_news/api/combined_feed.h" +#include "brave/components/brave_news/browser/channel_migrator.h" #include "brave/components/brave_news/common/brave_news.mojom-forward.h" -#include "brave/components/brave_news/common/brave_news.mojom-shared.h" #include "brave/components/brave_news/common/brave_news.mojom.h" +#include "brave/components/brave_news/common/features.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/l10n/time_format.h" #include "url/gurl.h" @@ -49,9 +51,13 @@ base::expected ParseFeedItem( base::StrCat({"Item url was not HTTP or HTTPS: url=", url.spec()})); } - if (feed_item.padded_img.empty()) { - return base::unexpected(base::StrCat( - {"Found feed item with missing image. url=", feed_item.url})); + // FeedV2 supports articles with no images, such as the ones from Brave Blog. + if (!base::FeatureList::IsEnabled( + brave_news::features::kBraveNewsFeedUpdate)) { + if (feed_item.padded_img.empty()) { + return base::unexpected(base::StrCat( + {"Found feed item with missing image. url=", feed_item.url})); + } } if (feed_item.publisher_id.empty()) { @@ -70,7 +76,7 @@ base::expected ParseFeedItem( } auto metadata = mojom::FeedItemMetadata::New(); - metadata->category_name = feed_item.category; + metadata->category_name = GetMigratedChannel(feed_item.category); metadata->title = feed_item.title; metadata->description = feed_item.description; metadata->publisher_id = feed_item.publisher_id; diff --git a/components/brave_news/browser/resources/FeedNavigation.tsx b/components/brave_news/browser/resources/FeedNavigation.tsx index 8620f11c95af..2cc12e13ecc7 100644 --- a/components/brave_news/browser/resources/FeedNavigation.tsx +++ b/components/brave_news/browser/resources/FeedNavigation.tsx @@ -12,6 +12,7 @@ import { useBraveNews } from './shared/Context'; import { isPublisherEnabled } from './shared/api'; import { FeedView } from './shared/useFeedV2'; import { getLocale } from '$web-common/locale'; +import SettingsButton from './SettingsButton'; const DEFAULT_SHOW_COUNT = 4; @@ -32,15 +33,7 @@ const Container = styled(Card)` scrollbar-color: var(--bn-glass-10) var(--bn-glass-10); ` -const Heading = styled.h3` - font: ${font.default.semibold}; - color: var(--bn-glass-25); - margin: 0; - - padding-left: ${PAD_LEFT}; -` - -const CustomButton = styled.button <{ selected?: boolean, faint?: boolean, bold?: boolean }>` +const CustomButton = styled.button <{ selected?: boolean, faint?: boolean, large?: boolean, bold?: boolean }>` padding: ${spacing.m}; padding-left: ${PAD_LEFT}; @@ -52,8 +45,8 @@ const CustomButton = styled.button <{ selected?: boolean, faint?: boolean, bold? text-align: left; width: 100%; - color: ${p => p.faint ? `var(--bn-glass-25)` : `var(--bn-glass-70)`}; - font: ${p => font.small[p.bold ? 'semibold' : 'regular']}; + color: ${p => p.faint ? `var(--bn-glass-50)` : `var(--bn-glass-100)`}; + font: ${p => font[p.large ? 'default' : 'small'][p.bold ? 'semibold' : 'regular']}; cursor: pointer; &:hover { @@ -79,7 +72,7 @@ const Section = styled.details` align-items: center; gap: ${spacing.m}; list-style: none; - font: ${font.small.semibold}; + font: ${font.default.semibold}; cursor: pointer; @@ -87,12 +80,7 @@ const Section = styled.details` box-shadow: ${effect.focusState}; } - ${CustomButton} { - padding: 0; - flex: 0; - display: flex; - gap: ${spacing.m}; - align-items: center; + ${SettingsButton} { margin-left: auto; } } @@ -118,11 +106,12 @@ function usePersistedState(name: string, defaultValue: T) { } const Marker = +const PlaceholderMarker = export function Item(props: { id: FeedView, name: string }) { const { feedView, setFeedView } = useBraveNews() - - return setFeedView(props.id)} bold={props.id === 'all'}> + const topLevel = ['all', 'following'].includes(props.id) + return setFeedView(props.id)} bold={topLevel}> {props.name} } @@ -147,17 +136,18 @@ export default function Sidebar() { .slice(0, showingMoreChannels ? undefined : DEFAULT_SHOW_COUNT), [subscribedChannels, showingMoreChannels]) return - {getLocale('braveNewsMyFeedHeading')} {!!subscribedChannels.length &&
- {Marker} + {subscribedChannels.length ? Marker : PlaceholderMarker} {getLocale('braveNewsChannelsHeader')} - setCustomizePage('news')}> + { + setCustomizePage('news') + e.stopPropagation() + }}> - {getLocale('braveNewsAddChannelsOrPublishers')} - + {slicedChannelIds.map(c => )} {subscribedChannels.length > DEFAULT_SHOW_COUNT @@ -169,12 +159,14 @@ export default function Sidebar() {
} {!!subscribedPublisherIds.length &&
- {Marker} + {subscribedPublisherIds.length ? Marker : PlaceholderMarker} {getLocale('braveNewsPublishersHeading')} - setCustomizePage('popular')}> + { + setCustomizePage('popular') + e.stopPropagation() + }}> - {getLocale('braveNewsAddChannelsOrPublishers')} - + {slicedPublisherIds.map(p => )} {subscribedPublisherIds.length > DEFAULT_SHOW_COUNT diff --git a/components/brave_news/browser/resources/Peek.tsx b/components/brave_news/browser/resources/Peek.tsx index 2a6b1695847f..09cbae3ecc2e 100644 --- a/components/brave_news/browser/resources/Peek.tsx +++ b/components/brave_news/browser/resources/Peek.tsx @@ -3,17 +3,17 @@ // 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 { getLocale } from '$web-common/locale'; import Icon from '@brave/leo/react/icon'; -import { color, effect, gradient, radius, spacing } from '@brave/leo/tokens/css'; +import { color, effect, font, radius, spacing } from '@brave/leo/tokens/css'; import * as React from 'react'; import styled, { keyframes } from 'styled-components'; import { NEWS_FEED_CLASS } from './Feed'; +import Variables from './Variables'; import { MetaInfo } from './feed/ArticleMetaRow'; import Card, { SmallImage, Title } from './feed/Card'; import { useBraveNews } from './shared/Context'; import { useUnpaddedImageUrl } from './shared/useUnpaddedImageUrl'; -import Variables from './Variables'; -import { getLocale } from '$web-common/locale' const NewsButton = styled.button` cursor: pointer; @@ -27,10 +27,7 @@ const NewsButton = styled.button` backdrop-filter: blur(40px); color: ${color.white}; - - & > leo-icon[name="news-default"] { - --leo-icon-color: ${gradient.iconsActive}; - } + font: ${font.default.semibold}; & > leo-icon[name="carat-down"] { --leo-icon-color: rgba(255, 255, 255, 0.25); @@ -114,7 +111,7 @@ export default function Peek() { return isShowOnNTPPrefEnabled ? - {(!isOptInPrefEnabled || data) && + {(!isOptInPrefEnabled || feedV2) && {getLocale('braveNewsNewsPeek')} diff --git a/components/brave_news/browser/resources/SettingsButton.tsx b/components/brave_news/browser/resources/SettingsButton.tsx new file mode 100644 index 000000000000..5b4a3d7f2d24 --- /dev/null +++ b/components/brave_news/browser/resources/SettingsButton.tsx @@ -0,0 +1,24 @@ +// Copyright (c) 2024 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 Button from "@brave/leo/react/button"; +import { radius, spacing } from "@brave/leo/tokens/css"; +import styled from "styled-components"; + + +const SettingsButton = styled(Button)` + --leo-button-color: var(--bn-glass-50); + --leo-button-radius: ${radius.s}; + --leo-button-padding: ${spacing.s}; + + flex: 0; +` + +SettingsButton.defaultProps = { + fab: true, + kind: 'outline' +} + +export default SettingsButton diff --git a/components/brave_news/browser/resources/feed/Ad.tsx b/components/brave_news/browser/resources/feed/Ad.tsx index 0475b6a20a37..2f6132bb16c7 100644 --- a/components/brave_news/browser/resources/feed/Ad.tsx +++ b/components/brave_news/browser/resources/feed/Ad.tsx @@ -114,7 +114,7 @@ export default function Advert(props: Props) { return - e.stopPropagation()} href="brave://rewards">{getLocale('braveNewsAdvertBadge')} + e.stopPropagation()} href="chrome://rewards">{getLocale('braveNewsAdvertBadge')} • {' ' + advert.description} diff --git a/components/brave_news/browser/resources/shared/Icons.tsx b/components/brave_news/browser/resources/shared/Icons.tsx index 4fb4df02b456..f7a96f5f67d7 100644 --- a/components/brave_news/browser/resources/shared/Icons.tsx +++ b/components/brave_news/browser/resources/shared/Icons.tsx @@ -15,32 +15,34 @@ export const ArrowRight = , - 'Business': , - 'Cars': , - 'Crypto': , - 'Culture': , - 'Entertainment': , - 'Entertainment News': , - 'Fashion': , - 'Film and TV': , - 'Food': , - 'Fun': , - 'Gaming': , - 'Health': , - 'Home': , - 'Music': , - 'Politics': , - 'Regional News': , - 'Science': , - 'Sports': , - 'Travel': , - 'Technology': , - 'Tech News': , - 'Tech Reviews': , - 'Top News': , - 'US News': , - 'Weather': , + 'default': , + 'Brave': + + , + 'Business': , + 'Cars': , + 'Crypto': , + 'Culture': , + 'Entertainment': , + 'Entertainment News': , + 'Fashion': , + 'Film and TV': , + 'Food': , + 'Fun': , + 'Gaming': , + 'Health': , + 'Home': , + 'Music': , + 'Politics': , + 'Regional News': , + 'Science': , + 'Sports': , + 'Travel': , + 'Technology': , + 'Tech News': , + 'Tech Reviews': , + 'Top News': , + 'US News': , + 'Weather': , 'World News': } diff --git a/components/common/useMediaQuery.ts b/components/common/useMediaQuery.ts new file mode 100644 index 000000000000..b719573c4dcf --- /dev/null +++ b/components/common/useMediaQuery.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2024 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 { useEffect, useState } from "react"; + +export default function useMediaQuery(query: string) { + const [result, setResult] = useState(() => window.matchMedia(query).matches) + + useEffect(() => { + const media = window.matchMedia(query) + const handler = () => setResult(media.matches) + media.addEventListener('change', handler) + setResult(media.matches) + + return () => { + media.removeEventListener('change', handler) + } + }, [query]) + + return result +} diff --git a/components/resources/brave_news_strings.grdp b/components/resources/brave_news_strings.grdp index fedd12a8dab3..33336eae8057 100644 --- a/components/resources/brave_news_strings.grdp +++ b/components/resources/brave_news_strings.grdp @@ -183,18 +183,12 @@ News - - My Feed - For You Following - - Add - Publishers diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn index 0e15debedfd6..c9eb8b115f4e 100644 --- a/ui/webui/resources/BUILD.gn +++ b/ui/webui/resources/BUILD.gn @@ -206,6 +206,7 @@ leo_icons = [ "fullscreen-on.svg", "graph.svg", "grid04.svg", + "hamburger-menu.svg", "help-outline.svg", "history.svg", "image-off.svg",