[Feed V2]: Introduce new UX design for cards in news-internals (#20322)

This commit is contained in:
Jay Harris
2023-10-03 07:39:17 +09:00
committed by GitHub
parent 809104dce3
commit c813f4b7ce
9 changed files with 181 additions and 44 deletions
@@ -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>