Update trending topics UI (#11304)

This commit is contained in:
DS Boyce
2026-07-27 16:34:15 -07:00
committed by GitHub
parent d9eacc3d98
commit a5f0e8839a
10 changed files with 281 additions and 203 deletions
@@ -4,7 +4,10 @@ import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useTrendingSettings} from '#/state/preferences/trending'
import {
useTrendingSettings,
useTrendingSettingsApi,
} from '#/state/preferences/trending'
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
import {useTrendingConfig} from '#/state/service-config'
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
@@ -16,10 +19,12 @@ import {
useTheme,
type ViewStyleProp,
} from '#/alf'
import {alpha} from '#/alf/utils'
import {AvatarStack} from '#/components/AvatarStack'
import {Button, ButtonIcon} from '#/components/Button'
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
import {Link} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
@@ -39,6 +44,8 @@ function Inner() {
const {t: l} = useLingui()
const gutters = useGutters([0, 'base'])
const ax = useAnalytics()
const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDisabled} = useTrendingSettingsApi()
const {
data: trending,
error,
@@ -47,90 +54,109 @@ function Inner() {
} = useGetTrendsQuery({limit: TOPIC_COUNT})
const noTopics = !isLoading && !error && !trending?.trends?.length
const shadowColor = alpha(t.palette.primary_100, 0.5)
if (error || noTopics) {
return null
}
return (
<View
style={[
gutters,
a.pt_lg,
a.pb_xl,
a.gap_sm,
a.border_t,
t.atoms.border_contrast_low,
]}>
<>
<View
style={[
a.relative,
a.z_20,
a.px_xs,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_sm,
gutters,
a.pt_xs,
a.pb_lg,
a.gap_xs,
a.border_t,
t.atoms.border_contrast_low,
t.atoms.bg_contrast_25,
]}>
<View style={[a.flex_row, a.align_center, a.justify_between, a.gap_xs]}>
<TrendingIcon width={18} />
<Text style={[a.text_md, a.font_medium]} numberOfLines={1}>
<Trans>Trending</Trans>
</Text>
</View>
<Link label={l`See more trending topics`} to="/search">
{({hovered, pressed}) => (
<Text
style={[
a.text_sm,
a.font_medium,
{
color:
hovered || pressed
? t.palette.contrast_800
: t.palette.contrast_500,
},
]}
numberOfLines={1}>
<Trans>See more</Trans>
<View
style={[
a.relative,
a.z_20,
a.pl_xs,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_sm,
]}>
<View
style={[a.flex_row, a.align_center, a.justify_between, a.gap_xs]}>
<TrendingIcon width={18} fill={t.atoms.text.color} />
<Text style={[a.text_md, a.font_medium]} numberOfLines={1}>
<Trans>Trending</Trans>
</Text>
)}
</Link>
</View>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Link label={l`See more trending topics`} to="/search">
{({hovered, pressed}) => (
<Text
style={[
a.text_sm,
a.font_medium,
{
color:
hovered || pressed
? t.palette.contrast_800
: t.palette.contrast_500,
},
]}
numberOfLines={1}>
<Trans>See more</Trans>
</Text>
)}
</Link>
<Button
variant="ghost"
size="medium"
color="secondary"
shape="round"
label={l`Trending options`}
onPress={() => trendingPrompt.open()}
style={[a.bg_transparent]}>
<ButtonIcon icon={EllipsisIcon} size="md" />
</Button>
</View>
</View>
<View style={[a.relative, a.z_10, a.rounded_xl, t.atoms.shadow_md]}>
<View
style={[
a.overflow_hidden,
a.border,
a.rounded_xl,
t.atoms.bg,
t.atoms.border_contrast_low,
]}>
{isLoading || isRefetching
? Array.from({length: TOPIC_COUNT}).map((_, i) => (
<TrendingTopicRowSkeleton key={i} rank={i + 1} />
))
: trending?.trends?.map((trend, index) => (
<TrendRow
key={trend.link}
trend={trend}
rank={index + 1}
onPress={() => {
ax.metric('trendingTopic:click', {
context: 'interstitial',
})
}}
/>
))}
</View>
</View>
</View>
<View
style={[
a.relative,
a.z_10,
a.border,
a.rounded_xl,
t.atoms.bg,
{
borderColor: t.palette.primary_100,
boxShadow: `0 0 16px 0 ${shadowColor}`,
elevation: 8,
shadowColor: shadowColor,
shadowOffset: {width: 0, height: 0},
shadowOpacity: 1,
shadowRadius: 16,
},
]}>
{isLoading || isRefetching
? Array.from({length: TOPIC_COUNT}).map((_, i) => (
<TrendingTopicRowSkeleton key={i} rank={i + 1} />
))
: trending?.trends?.map((trend, index) => (
<TrendRow
key={trend.link}
trend={trend}
rank={index + 1}
onPress={() => {
ax.metric('trendingTopic:click', {context: 'interstitial'})
}}
/>
))}
</View>
</View>
<Prompt.Basic
control={trendingPrompt}
title={l`Hide trending topics?`}
description={l`You can update this later from your settings.`}
confirmButtonCta={l`Hide`}
onConfirm={() => {
ax.metric('trendingTopics:hide', {context: 'interstitial'})
setTrendingDisabled(true)
}}
/>
</>
)
}
@@ -156,12 +182,7 @@ function TrendRow({
label={l`Browse topic ${trend.displayName}`}
to={trend.link}
onPress={onPress}
style={[
rank < TOPIC_COUNT && a.border_b,
{
borderColor: t.palette.primary_100,
},
]}
style={[rank < TOPIC_COUNT && a.border_b, t.atoms.border_contrast_low]}
PressableComponent={Pressable}>
{({hovered, pressed}) => (
<>
+27 -28
View File
@@ -1,9 +1,7 @@
import {useCallback, useEffect, useMemo} from 'react'
import {ScrollView, View} from 'react-native'
import {AppBskyEmbedVideo, AtUri} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
import {VIDEO_FEED_URI} from '#/lib/constants'
@@ -14,7 +12,7 @@ import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {atoms as a, useGutters, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
import {Link} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
@@ -35,7 +33,7 @@ const FEED_PARAMS: {
export function TrendingVideos() {
const t = useTheme()
const {_} = useLingui()
const {t: l} = useLingui()
const ax = useAnalytics()
const gutters = useGutters([0, 'base'])
const {data, isLoading, error} = usePostFeedQuery(FEED_DESC, FEED_PARAMS)
@@ -48,7 +46,7 @@ export function TrendingVideos() {
.getQueryCache()
.find({queryKey: RQKEY(FEED_DESC, FEED_PARAMS)})
if (query && query.getObserversCount() <= 1) {
query.fetch()
void query.fetch()
}
}
}, [queryClient])
@@ -83,20 +81,22 @@ export function TrendingVideos() {
a.align_center,
a.justify_between,
]}>
<Text style={[a.text_sm, a.font_semi_bold, a.leading_snug]}>
<Trans>Trending Videos</Trans>
</Text>
<Button
label={_(msg`Dismiss this section`)}
size="tiny"
variant="solid"
color="secondary"
shape="square"
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={X} size="sm" />
</Button>
<View style={[a.pl_xs, a.flex_row, a.align_center]}>
<Text style={[a.flex_1, a.text_md, a.font_semi_bold]}>
<Trans>Trending videos</Trans>
</Text>
<Button
label={l`Dismiss this section`}
size="small"
variant="ghost"
color="secondary"
shape="round"
style={[a.bg_transparent]}
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={EllipsisIcon} size="md" />
</Button>
</View>
</View>
<BlockDrawerGesture>
<ScrollView
horizontal
@@ -131,12 +131,11 @@ export function TrendingVideos() {
</View>
</ScrollView>
</BlockDrawerGesture>
<Prompt.Basic
control={trendingPrompt}
title={_(msg`Hide trending videos?`)}
description={_(msg`You can update this later from your settings.`)}
confirmButtonCta={_(msg`Hide`)}
title={l`Hide trending videos?`}
description={l`You can update this later from your settings.`}
confirmButtonCta={l`Hide`}
onConfirm={onConfirmHide}
/>
</View>
@@ -186,7 +185,7 @@ function VideoCards({
function ViewMoreCard() {
const t = useTheme()
const {_} = useLingui()
const {t: l} = useLingui()
const href = useMemo(() => {
const urip = new AtUri(VIDEO_FEED_URI)
@@ -197,16 +196,16 @@ function ViewMoreCard() {
<View style={[{width: CARD_WIDTH * 2}]}>
<Link
to={href}
label={_(msg`View more`)}
label={l`View more`}
style={[
a.justify_center,
a.align_center,
a.flex_1,
a.rounded_lg,
a.rounded_xl,
a.border,
t.atoms.border_contrast_low,
t.atoms.bg,
t.atoms.shadow_sm,
t.atoms.shadow_md,
]}>
{({pressed}) => (
<View
@@ -225,7 +224,7 @@ function ViewMoreCard() {
color="primary"
size="small"
shape="round"
label={_(msg`View more trending videos`)}>
label={l`View more trending videos`}>
<ButtonIcon icon={ChevronRight} />
</Button>
</View>