[Feed V2]: Introduce new UX design for cards in news-internals (#20322)
This commit is contained in:
@@ -26,7 +26,7 @@ const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${spacing.m};
|
||||
max-width: 800px;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
`
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function InspectContext(props: React.PropsWithChildren<{}>) {
|
||||
feed,
|
||||
truncate,
|
||||
setTruncate: setAndSaveTruncate
|
||||
}), [publishers, channels, feed, truncate, setAndSaveTruncate])
|
||||
}), [publishers, channels, signals, feed, truncate, setAndSaveTruncate])
|
||||
|
||||
return <Context.Provider value={context}>
|
||||
{props.children}
|
||||
|
||||
@@ -2,39 +2,45 @@
|
||||
// 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 { spacing } from '@brave/leo/tokens/css';
|
||||
import { FeedItemMetadata, Article as Info } from 'gen/brave/components/brave_news/common/brave_news.mojom.m';
|
||||
import * as React from 'react';
|
||||
import Card from './Card';
|
||||
import { HeroArticle, Article as Info } from 'gen/brave/components/brave_news/common/brave_news.mojom.m';
|
||||
import styled from 'styled-components';
|
||||
import { color, font } from '@brave/leo/tokens/css';
|
||||
import Flex from '../../../../brave_new_tab_ui/components/Flex';
|
||||
import { useLazyUnpaddedImageUrl } from '../../../../brave_new_tab_ui/components/default/braveNews/useUnpaddedImageUrl';
|
||||
import ArticleMetaRow from './ArticleMetaRow';
|
||||
import Card, { Title } from './Card';
|
||||
|
||||
interface Props {
|
||||
info: Info | HeroArticle
|
||||
isHero?: boolean
|
||||
info: Info
|
||||
hideChannel?: boolean
|
||||
}
|
||||
|
||||
const Container = styled(Card)`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Header = styled.h2<{ isHero?: boolean }>`
|
||||
all: unset;
|
||||
font: ${s => s.isHero ? font.primary.heading.h2 : font.primary.heading.h4};
|
||||
`
|
||||
const ArticleImage = styled.img`
|
||||
width: 96px;
|
||||
min-width: 96px;
|
||||
height: 64px;
|
||||
|
||||
const Publisher = styled.div`
|
||||
color: ${color.text.secondary};
|
||||
`
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
|
||||
const Description = styled.div`
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
`
|
||||
export const openArticle = (article: FeedItemMetadata) => window.open(article.url.url, '_blank', 'noopener noreferrer')
|
||||
|
||||
export default function Article({ info, isHero }: Props) {
|
||||
return <Container onClick={() => window.open(info.data.url.url, '_blank', 'noopener noreferrer')}>
|
||||
<Header isHero={isHero}>{isHero && 'Hero: '}{info.data.title}{('isDiscover' in info && info.isDiscover) && " (discovering)"}</Header>
|
||||
<Publisher>{info.data.publisherName} - {info.data.relativeTimeDescription}</Publisher>
|
||||
<Description>{info.data.description}</Description>
|
||||
export default function Article({ info, hideChannel }: Props) {
|
||||
const { url, setElementRef } = useLazyUnpaddedImageUrl(info.data.image.paddedImageUrl?.url, { useCache: true })
|
||||
|
||||
return <Container onClick={() => openArticle(info.data)} ref={setElementRef}>
|
||||
<ArticleMetaRow article={info.data} hideChannel={hideChannel} />
|
||||
<Flex direction='row' gap={spacing.m} justify='space-between'>
|
||||
<Title>{info.data.title}{('isDiscover' in info && info.isDiscover) && " (discovering)"}</Title>
|
||||
<ArticleImage src={url} />
|
||||
</Flex>
|
||||
</Container>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2023 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, spacing } from "@brave/leo/tokens/css"
|
||||
import { FeedItemMetadata, UserEnabled } from "../../../../brave_new_tab_ui/api/brave_news"
|
||||
import { channelIcons } from "../../../../brave_new_tab_ui/components/default/braveNews/customize/Icons"
|
||||
import styled from "styled-components";
|
||||
import * as React from "react";
|
||||
import Flex from "../../../../brave_new_tab_ui/components/Flex";
|
||||
import ButtonMenu from "@brave/leo/react/buttonMenu";
|
||||
import Button from "@brave/leo/react/button";
|
||||
import Icon from "@brave/leo/react/icon";
|
||||
import { api } from "../context";
|
||||
|
||||
const MenuButton = styled(Button)`
|
||||
flex-grow: 0;
|
||||
--leo-button-padding: ${spacing.s};
|
||||
`
|
||||
|
||||
export const MetaInfoContainer = styled.h4`
|
||||
margin: 0;
|
||||
|
||||
font: ${font.primary.xSmall.regular};
|
||||
color: ${color.text.secondary};
|
||||
|
||||
opacity: 0.5;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${spacing.s};
|
||||
|
||||
--leo-icon-size: 12px;
|
||||
`
|
||||
|
||||
export const getOrigin = (article: FeedItemMetadata) => {
|
||||
const host = new URL(article.url.url).host
|
||||
return host.startsWith('www.') ? host.substring(4) : host
|
||||
}
|
||||
|
||||
export function MetaInfo(props: { article: FeedItemMetadata, hideChannel?: boolean }) {
|
||||
|
||||
const maybeChannel = !props.hideChannel && <>
|
||||
• {channelIcons[props.article.categoryName] ?? channelIcons.default} {props.article.categoryName}
|
||||
</>
|
||||
return <MetaInfoContainer>
|
||||
{getOrigin(props.article)} {maybeChannel} • {props.article.relativeTimeDescription}
|
||||
</MetaInfoContainer>
|
||||
}
|
||||
|
||||
export default function ArticleMetaRow(props: { article: FeedItemMetadata, hideChannel?: boolean }) {
|
||||
return <Flex direction="row" justify="space-between" align="center">
|
||||
<MetaInfo {...props} />
|
||||
|
||||
<ButtonMenu>
|
||||
<MenuButton slot='anchor-content' kind='plain-faint' onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
<Icon name='more-horizontal' />
|
||||
</MenuButton>
|
||||
<leo-menu-item onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
api.setPublisherPref(props.article.publisherId, UserEnabled.DISABLED)
|
||||
}}>Hide content from {getOrigin(props.article)}</leo-menu-item>
|
||||
</ButtonMenu>
|
||||
</Flex>
|
||||
}
|
||||
@@ -2,14 +2,31 @@
|
||||
// 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, radius, spacing } from '@brave/leo/tokens/css';
|
||||
import styled from "styled-components";
|
||||
import { color, effect, radius, spacing } from '@brave/leo/tokens/css'
|
||||
|
||||
export const Header = styled.h2`
|
||||
margin: 0;
|
||||
|
||||
font: ${font.primary.heading.h2};
|
||||
color: ${color.text.primary};
|
||||
|
||||
--leo-icon-size: 18px;
|
||||
`
|
||||
|
||||
export const Title = styled.h3`
|
||||
margin: 0;
|
||||
|
||||
font: ${font.primary.default.regular};
|
||||
color: ${color.text.primary};
|
||||
`
|
||||
|
||||
export default styled.div`
|
||||
background: ${color.container.background};
|
||||
box-shadow: ${effect.elevation[7]};
|
||||
border-radius: ${radius.m};
|
||||
border: 1px solid ${color.gray[20]};
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: ${radius.xl};
|
||||
color: ${color.text.primary};
|
||||
padding: ${spacing.m};
|
||||
padding: ${spacing["2Xl"]};
|
||||
|
||||
${p => p.onClick && 'cursor: pointer'}
|
||||
`
|
||||
|
||||
@@ -6,19 +6,29 @@ import * as React from 'react';
|
||||
import { Cluster as Info } from 'gen/brave/components/brave_news/common/brave_news.mojom.m';
|
||||
import Card from './Card';
|
||||
import Article from './Article';
|
||||
import HeroArticle from './Hero';
|
||||
import styled from 'styled-components';
|
||||
import { spacing } from '@brave/leo/tokens/css';
|
||||
import { channelIcons } from '../../../../brave_new_tab_ui/components/default/braveNews/customize/Icons';
|
||||
import { MetaInfoContainer } from './ArticleMetaRow';
|
||||
|
||||
interface Props {
|
||||
info: Info
|
||||
}
|
||||
|
||||
const Container = styled(Card)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${spacing.m};
|
||||
`
|
||||
|
||||
export default function Cluster({ info }: Props) {
|
||||
return <>
|
||||
<Card>
|
||||
Cluster: {info.type} {info.id}
|
||||
</Card>
|
||||
{info.articles.map(a => a.article
|
||||
? <Article key={a.article.data.url.url} info={a.article} />
|
||||
: <HeroArticle key={a.hero!.data.url.url} info={a.hero!} />)}
|
||||
</>
|
||||
return <Container>
|
||||
<MetaInfoContainer>
|
||||
{channelIcons[info.id] ?? channelIcons.default} {info.id}
|
||||
</MetaInfoContainer>
|
||||
{info.articles.map((a, i) => {
|
||||
const info: any = a.article || a.hero
|
||||
return <Article key={i} info={info} hideChannel />
|
||||
})}
|
||||
</Container>
|
||||
}
|
||||
|
||||
@@ -2,14 +2,37 @@
|
||||
// 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 { HeroArticle as Info } from 'gen/brave/components/brave_news/common/brave_news.mojom.m';
|
||||
import Article from './Article';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useLazyUnpaddedImageUrl } from '../../../../brave_new_tab_ui/components/default/braveNews/useUnpaddedImageUrl';
|
||||
import { openArticle } from './Article';
|
||||
import ArticleMetaRow from './ArticleMetaRow';
|
||||
import Card, { Title } from './Card';
|
||||
|
||||
interface Props {
|
||||
info: Info
|
||||
}
|
||||
|
||||
const HeroImage = styled.img`
|
||||
width: 100%;
|
||||
height: 269px;
|
||||
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
|
||||
border-radius: 6px;
|
||||
`
|
||||
|
||||
export default function HeroArticle({ info }: Props) {
|
||||
return <Article info={info} isHero />
|
||||
const { url, setElementRef } = useLazyUnpaddedImageUrl(info.data.image.paddedImageUrl?.url, {
|
||||
useCache: true,
|
||||
rootElement: document.body,
|
||||
rootMargin: '0px 0px 200px 0px'
|
||||
})
|
||||
return <Card onClick={() => openArticle(info.data)} ref={setElementRef}>
|
||||
<HeroImage src={url} />
|
||||
<ArticleMetaRow article={info.data} />
|
||||
<Title>{info.data.title}</Title>
|
||||
</Card>
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<style>
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
background: var(--leo-color-page-background);
|
||||
background: var(--leo-gradient-toolbar-background);
|
||||
color: var(--leo-color-text-primary);
|
||||
font: var(--leo-font-primary-default-regular);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,24 @@ const Grid = styled.div`
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const Blur = styled.div`
|
||||
--edges: -128px;
|
||||
|
||||
z-index: -1;
|
||||
position: fixed;
|
||||
inset: var(--edges);
|
||||
filter: blur(64px);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
`
|
||||
|
||||
function App() {
|
||||
return (
|
||||
return <>
|
||||
<Blur />
|
||||
<Grid>
|
||||
<SignalsPage />
|
||||
<FeedPage />
|
||||
</Grid>
|
||||
)
|
||||
</>
|
||||
}
|
||||
|
||||
render(<InspectContext>
|
||||
|
||||
Reference in New Issue
Block a user