Skip to content

Commit

Permalink
Begin work on Carousel
Browse files Browse the repository at this point in the history
Begin work on scroll buttons

Wire up scroll buttons

Only show scroll buttons on hover

Clean up discover page

Popular sources should use carousel

Button opacity transitions

Work when the container shrinks

Begin work on pages

Linking

Improve perf

Get link to align nicely

Popular page

Page headers

localize

Keep settings open on back pressed
  • Loading branch information
fallaciousreasoning committed Nov 2, 2022
1 parent f3bac66 commit 4792000
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 128 deletions.
3 changes: 2 additions & 1 deletion browser/ui/webui/brave_webui_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,13 @@ void CustomizeWebUIHTMLSource(content::WebUI* web_ui,
{ "ad", IDS_BRAVE_TODAY_DISPLAY_AD_LABEL },

{ "braveNewsBackToDashboard", IDS_BRAVE_NEWS_BACK_TO_DASHBOARD },
{ "braveNewsBackButton", IDS_BRAVE_NEWS_BACK_BUTTON },
{ "braveNewsDisabledPlaceholderHeader", IDS_BRAVE_NEWS_DISABLED_PLACEHOLDER_HEADER }, // NOLINT
{ "braveNewsDisabledPlaceholderSubtitle", IDS_BRAVE_NEWS_DISABLED_PLACEHOLDER_SUBTITLE }, // NOLINT
{ "braveNewsDisabledPlaceholderEnableButton", IDS_BRAVE_NEWS_DISABLED_PLACEHOLDER_ENABLE_BUTTON }, // NOLINT
{ "braveNewsSearchPlaceholderLabel", IDS_BRAVE_NEWS_SEARCH_PLACEHOLDER_LABEL}, // NOLINT
{ "braveNewsChannelsHeader", IDS_BRAVE_NEWS_BROWSE_CHANNELS_HEADER}, // NOLINT
{ "braveNewsShowMoreButton", IDS_BRAVE_NEWS_SHOW_MORE_BUTTON},
{ "braveNewsViewAllButton", IDS_BRAVE_NEWS_VIEW_ALL_BUTTON},
{ "braveNewsAllSourcesHeader", IDS_BRAVE_NEWS_ALL_SOURCES_HEADER},
{ "braveNewsFeedsHeading", IDS_BRAVE_NEWS_FEEDS_HEADING},
{ "braveNewsFollowButtonFollowing", IDS_BRAVE_NEWS_FOLLOW_BUTTON_FOLLOWING}, // NOLINT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.

import * as React from 'react'
import styled, { css } from 'styled-components'
import Flex from '../../../Flex'
import FeedCard from './FeedCard'
import { ArrowRight } from './Icons'

const CARD_SIZE = 208
const CARD_SIZE_PX = `${CARD_SIZE}px`
const CARD_GAP = '16px'

const ScrollButton = styled.button<{ hidden: boolean }>`
all: unset;
position: absolute;
width: 32px;
height: 32px;
top: 32px;
background: white;
border-radius: 32px;
box-shadow: 0px 1px 4px rgba(63, 76, 99, 0.35);
display: flex;
align-items: center;
justify-content: center;
color: var(--text2);
cursor: pointer;
:hover {
box-shadow: 0px 1px 4px rgba(63, 76, 99, 0.5);
color: var(--interactive4);
}
${p => p.hidden && css`opacity: 0;`}
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
`

const ScrollButtonLeft = styled(ScrollButton)`
left: -16px;
transform: rotate(180deg);
`

const ScrollButtonRight = styled(ScrollButton)`
right: -16px;
`

const Container = styled(Flex)`
padding: 16px 0;
max-width: calc(${CARD_SIZE_PX} * 3 + ${CARD_GAP} * 2);
container-name: carousel;
container-type: inline-size;
&:not(:hover, :has(:focus-visible)) ${ScrollButton} {
opacity: 0;
}
`

const Header = styled.div`
width: 100%;
font-weight: 600;
font-size: 16px;
margin: 8px 0;
`

const Subtitle = styled.span`
font-size: 12px;
`

const CarouselContainer = styled.div`
position: relative;
`

const ItemsContainer = styled(Flex)`
margin: 8px 0;
overflow-x: auto;
scroll-snap-type: x mandatory;
&::-webkit-scrollbar {
display: none;
width: 0;
}
`

const FeedCardContainer = styled.div`
min-width: calc((100cqi - ${CARD_GAP} * 2) / 3);
max-width: ${CARD_SIZE_PX};
scroll-snap-align: start;
`

interface Props {
title: string | JSX.Element
subtitle?: React.ReactNode
publisherIds: string[]
}

export default function Carousel (props: Props) {
const scrollContainerRef = React.useRef<HTMLDivElement>()
const [availableDirections, setAvailableDirections] = React.useState<'none' | 'left' | 'right' | 'both'>('right')
const updateAvailableDirections = React.useCallback(() => {
if (!scrollContainerRef.current) return

const end = scrollContainerRef.current.scrollWidth - scrollContainerRef.current.clientWidth
const scrollPos = scrollContainerRef.current.scrollLeft
if (end <= 0) {
setAvailableDirections('none')
} else if (end > scrollPos && scrollPos > 0) {
setAvailableDirections('both')
} else if (end > scrollPos) {
setAvailableDirections('right')
} else {
setAvailableDirections('left')
}
}, [])

const scroll = React.useCallback((dir: 'left' | 'right') => {
if (!scrollContainerRef.current) return

scrollContainerRef.current.scrollBy({
behavior: 'smooth',
left: CARD_SIZE * (dir === 'left' ? -1 : 1)
})
}, [])

return (
<Container direction='column'>
<Flex direction='row' gap={8} align='center'>
<Header>{props.title}</Header>
</Flex>
{props.subtitle && <Subtitle>
{props.subtitle}
</Subtitle>}
<CarouselContainer>
<ItemsContainer direction='row' gap={CARD_GAP} ref={scrollContainerRef as any} onScroll={updateAvailableDirections}>
{props.publisherIds.map(p => <FeedCardContainer key={p}>
<FeedCard publisherId={p}/>
</FeedCardContainer>)}
</ItemsContainer>
<ScrollButtonLeft onClick={() => scroll('left')} hidden={availableDirections === 'right' || availableDirections === 'none'}>
{ArrowRight}
</ScrollButtonLeft>
<ScrollButtonRight onClick={() => scroll('right')} hidden={availableDirections === 'left' || availableDirections === 'none'}>
{ArrowRight}
</ScrollButtonRight>
</CarouselContainer>
</Container>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { useNewTabPref } from '../../../../hooks/usePref'
import { useBraveNews } from './Context'
import { getLocale } from '$web-common/locale'
import { formatMessage } from '../../../../../brave_rewards/resources/shared/lib/locale_context'
import { SuggestionsPage } from './Suggestions'
import { PopularPage } from './Popular'

const Grid = styled.div`
width: 100%;
Expand Down Expand Up @@ -104,11 +106,15 @@ const Content = styled.div`

export default function Configure () {
const [enabled, setEnabled] = useNewTabPref('isBraveTodayOptedIn')
const { setCustomizePage } = useBraveNews()
const { setCustomizePage, customizePage } = useBraveNews()

let content: JSX.Element
if (!enabled) {
content = <DisabledPlaceholder enableBraveNews={() => setEnabled(true)} />
} else if (customizePage === 'suggestions') {
content = <SuggestionsPage/>
} else if (customizePage === 'popular') {
content = <PopularPage />
} else {
content = <Discover />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import Modal from './Modal'
// Leave possibility for more pages open.
type NewsPage = null
| 'news'
| 'suggestions'
| 'popular'

interface BraveNewsContext {
customizePage: NewsPage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styled from 'styled-components'

const CustomizeLink = styled.button`
all: unset;
font-weight: 600;
font-size: 12px;
line-height: 18px;
color: var(--interactive5);
cursor: pointer;
`
export default CustomizeLink
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react'
import styled from 'styled-components'
import { getLocale } from '../../../../../common/locale'
import Button from '../../../../../web-components/button'
import Flex from '../../../Flex'
import { useBraveNews } from './Context'
import { BackArrow } from './Icons'

const BackButtonContainer = styled.div`
all: unset;
flex: 1;
&> button {
--inner-border-size: 0;
--outer-border-size: 0;
padding: 0;
&:hover {
--inner-border-size: 0;
--outer-border-size: 0;
}
}
`

const Header = styled.span`
font-weight: 500;
font-size: 16px;
color: var(--text01);
flex: 5;
text-align: center;
`

const Spacer = styled.div`flex: 1;`

export default function CustomizePage (props: {
title: string
children: React.ReactNode
}) {
const { setCustomizePage } = useBraveNews()
return <Flex direction="column">
<Flex align="center">
<BackButtonContainer>
<Button onClick={() => setCustomizePage('news')}>
{BackArrow}
{getLocale('braveNewsBackButton')}
</Button>
</BackButtonContainer>
<Header>{props.title}</Header>
<Spacer />
</Flex>
{props.children}
</Flex>
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import ChannelCard from './ChannelCard'
import DiscoverSection from './DiscoverSection'
import FeedCard, { DirectFeedCard } from './FeedCard'
import useSearch from './useSearch'
import Suggestions from './Suggestions'
import Popular from './Popular'
import { SuggestionsCarousel } from './Suggestions'
import { PopularCarousel } from './Popular'

const Header = styled.span`
font-size: 24px;
Expand All @@ -31,16 +31,6 @@ const SearchInput = styled(TextInput)`
--focus-border: #737ADE;
`

const LoadMoreButtonContainer = styled.div`
display: flex;
flex-direction: column;
align-items: stretch;
grid-column: 2;
`

// The default number of category cards to show.
const DEFAULT_NUM_CATEGORIES = 3

export default function Discover () {
const [query, setQuery] = useState('')

Expand All @@ -55,40 +45,23 @@ export default function Discover () {
}

function Home () {
const [showingAllCategories, setShowingAllCategories] = React.useState(false)
const channels = useChannels()
const { filteredPublisherIds, updateSuggestedPublisherIds } = useBraveNews()
const { updateSuggestedPublisherIds } = useBraveNews()

const visibleChannelIds = React.useMemo(() => channels
// If we're showing all channels, there's no end to the slice.
// Otherwise, just show the default number.
.slice(0, showingAllCategories
? undefined
: DEFAULT_NUM_CATEGORIES)
.map(c => c.channelName),
[channels, showingAllCategories])
const channelNames = React.useMemo(() => channels.map(c => c.channelName),
[channels])

// When we mount this component, update the suggested publisher ids.
React.useEffect(() => { updateSuggestedPublisherIds() }, [])

return (
<>
<Suggestions/>
<Popular/>
<PopularCarousel />
<SuggestionsCarousel />
<DiscoverSection name={getLocale('braveNewsChannelsHeader')}>
{visibleChannelIds.map(channelName =>
{channelNames.map(channelName =>
<ChannelCard key={channelName} channelName={channelName} />
)}
{!showingAllCategories && <LoadMoreButtonContainer>
<Button onClick={() => setShowingAllCategories(true)}>
{getLocale('braveNewsShowMoreButton')}
</Button>
</LoadMoreButtonContainer>}
</DiscoverSection>
<DiscoverSection name={getLocale('braveNewsAllSourcesHeader')}>
{filteredPublisherIds.map(publisherId =>
<FeedCard key={publisherId} publisherId={publisherId} />
)}
</DiscoverSection>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styled from 'styled-components'
import Flex from '../../../Flex'

interface Props {
name: string
name?: string
subtitle?: React.ReactNode
children?: React.ReactNode
}
Expand Down Expand Up @@ -36,9 +36,9 @@ const ItemsContainer = styled.div`

export default function DiscoverSection (props: Props) {
return <Container direction='column'>
<Flex direction='row' gap={8} align='center'>
{props.name && <Flex direction='row' gap={8} align='center'>
<Header>{props.name}</Header>
</Flex>
</Flex>}
{props.subtitle && <Subtitle>
{props.subtitle}
</Subtitle>}
Expand Down
Loading

0 comments on commit 4792000

Please sign in to comment.