Cleaner sidebar layout (#9603)

This commit is contained in:
Alex Benzer
2026-01-09 14:13:43 -08:00
committed by GitHub
parent 966ae68dd2
commit c4fd9980cc
10 changed files with 422 additions and 129 deletions
+148 -36
View File
@@ -1,4 +1,4 @@
import {View} from 'react-native'
import {Pressable, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation, useNavigationState} from '@react-navigation/native'
@@ -7,10 +7,18 @@ import {getCurrentRoute} from '#/lib/routes/helpers'
import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger'
import {emitSoftReset} from '#/state/events'
import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {
type SavedFeedSourceInfo,
usePinnedFeedsInfos,
} from '#/state/queries/feed'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme, web} from '#/alf'
import {createStaticClick, InlineLinkText} from '#/components/Link'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {Link} from '#/components/Link'
import {Text} from '#/components/Typography'
export function DesktopFeeds() {
const t = useTheme()
@@ -57,13 +65,12 @@ export function DesktopFeeds() {
style={[
a.flex_1,
web({
gap: 10,
gap: 2,
/*
* Small padding prevents overflow prior to actually overflowing the
* height of the screen with lots of feeds.
*/
paddingVertical: 2,
marginHorizontal: -2,
paddingTop: 2,
overflowY: 'auto',
}),
]}>
@@ -72,10 +79,11 @@ export function DesktopFeeds() {
const current = route.name === 'Home' && feed === selectedFeed
return (
<InlineLinkText
<FeedItem
key={feedInfo.uri}
label={feedInfo.displayName}
{...createStaticClick(() => {
feedInfo={feedInfo}
current={current}
onPress={() => {
logger.metric(
'desktopFeeds:feed:click',
{
@@ -89,39 +97,143 @@ export function DesktopFeeds() {
if (route.name === 'Home' && feed === selectedFeed) {
emitSoftReset()
}
})}
style={[
a.text_md,
a.leading_snug,
a.flex_shrink_0,
current
? [a.font_semi_bold, t.atoms.text]
: [t.atoms.text_contrast_medium],
web({
marginHorizontal: 2,
width: 'calc(100% - 4px)',
}),
]}
numberOfLines={1}>
{feedInfo.displayName}
</InlineLinkText>
}}
/>
)
})}
<InlineLinkText
<Link
to="/feeds"
label={_(msg`More feeds`)}
style={[
a.text_md,
a.leading_snug,
web({
marginHorizontal: 2,
width: 'calc(100% - 4px)',
}),
]}
numberOfLines={1}>
{_(msg`More feeds`)}
</InlineLinkText>
a.flex_row,
a.align_center,
a.gap_sm,
a.self_start,
a.rounded_sm,
{paddingVertical: 6, paddingHorizontal: 8},
route.name === 'Feeds' && {backgroundColor: t.palette.primary_50},
]}>
{({hovered}) => {
const isActive = route.name === 'Feeds'
return (
<>
<View
style={[
a.align_center,
a.justify_center,
a.rounded_xs,
isActive
? {backgroundColor: t.palette.primary_100}
: t.atoms.bg_contrast_50,
{
width: 20,
height: 20,
},
]}>
<Plus
style={{width: 16, height: 16}}
fill={
isActive || hovered
? t.atoms.text.color
: t.atoms.text_contrast_medium.color
}
/>
</View>
<Text
style={[
a.text_md,
a.leading_snug,
isActive
? [t.atoms.text, a.font_semi_bold]
: hovered
? t.atoms.text
: t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
{_(msg`More feeds`)}
</Text>
</>
)
}}
</Link>
</View>
)
}
function FeedItem({
feedInfo,
current,
onPress,
}: {
feedInfo: SavedFeedSourceInfo
current: boolean
onPress: () => void
}) {
const t = useTheme()
const {_} = useLingui()
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const isFollowing = feedInfo.feedDescriptor === 'following'
return (
<Pressable
accessibilityRole="link"
accessibilityLabel={feedInfo.displayName}
accessibilityHint={_(msg`Opens ${feedInfo.displayName} feed`)}
onPress={onPress}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
style={[
a.flex_row,
a.align_center,
a.gap_sm,
a.self_start,
a.rounded_sm,
{paddingVertical: 6, paddingHorizontal: 8},
current && {backgroundColor: t.palette.primary_50},
]}>
{isFollowing ? (
<View
style={[
a.align_center,
a.justify_center,
a.rounded_xs,
{
width: 20,
height: 20,
backgroundColor: t.palette.primary_500,
},
]}>
<FilterTimeline
style={{width: 14, height: 14}}
fill={t.palette.white}
/>
</View>
) : (
<UserAvatar
type={feedInfo.type === 'list' ? 'list' : 'algo'}
size={20}
avatar={feedInfo.avatar}
noBorder
/>
)}
<Text
style={[
a.text_md,
a.leading_snug,
current
? [t.atoms.text, a.font_semi_bold]
: hovered
? t.atoms.text
: t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
{feedInfo.displayName}
</Text>
</Pressable>
)
}
+11 -7
View File
@@ -18,7 +18,6 @@ import {
web,
} from '#/alf'
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
import {Divider} from '#/components/Divider'
import {CENTER_COLUMN_OFFSET} from '#/components/Layout'
import {InlineLinkText} from '#/components/Link'
import {ProgressGuideList} from '#/components/ProgressGuide/List'
@@ -86,9 +85,8 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
{hasSession && (
<>
<ProgressGuideList />
<DesktopFeeds />
<Divider />
<ProgressGuideList />
</>
)}
@@ -102,25 +100,31 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
email: currentAccount?.email,
handle: currentAccount?.handle,
})}
style={[t.atoms.text_contrast_medium]}
label={_(msg`Feedback`)}>
{_(msg`Feedback`)}
</InlineLinkText>
{' '}
<Text style={[t.atoms.text_contrast_low]}>{' '}</Text>
</>
)}
<InlineLinkText
to="https://bsky.social/about/support/privacy-policy"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Privacy`)}>
{_(msg`Privacy`)}
</InlineLinkText>
{' '}
<Text style={[t.atoms.text_contrast_low]}>{' '}</Text>
<InlineLinkText
to="https://bsky.social/about/support/tos"
style={[t.atoms.text_contrast_medium]}
label={_(msg`Terms`)}>
{_(msg`Terms`)}
</InlineLinkText>
{' '}
<InlineLinkText label={_(msg`Help`)} to={HELP_DESK_URL}>
<Text style={[t.atoms.text_contrast_low]}>{' '}</Text>
<InlineLinkText
label={_(msg`Help`)}
to={HELP_DESK_URL}
style={[t.atoms.text_contrast_medium]}>
{_(msg`Help`)}
</InlineLinkText>
</Text>
@@ -1,9 +1,8 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {
useTrendingSettings,
useTrendingSettingsApi,
@@ -12,18 +11,13 @@ import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
import {useTrendingConfig} from '#/state/service-config'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/icons/Trending'
import * as Prompt from '#/components/Prompt'
import {
TrendingTopic,
TrendingTopicLink,
TrendingTopicSkeleton,
} from '#/components/TrendingTopics'
import {TrendingTopicLink} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography'
const TRENDING_LIMIT = 6
const TRENDING_LIMIT = 5
export function SidebarTrendingTopics() {
const {enabled} = useTrendingConfig()
@@ -39,64 +33,88 @@ function Inner() {
const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length
const onConfirmHide = React.useCallback(() => {
logEvent('trendingTopics:hide', {context: 'sidebar'})
const onConfirmHide = () => {
logger.metric('trendingTopics:hide', {context: 'sidebar'})
setTrendingDisabled(true)
}, [setTrendingDisabled])
}
return error || noTopics ? null : (
<>
<View style={[a.gap_sm, {paddingBottom: 2}]}>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Graph size="sm" />
<Text
style={[
a.flex_1,
a.text_sm,
a.font_semi_bold,
t.atoms.text_contrast_medium,
]}>
<View
style={[a.p_lg, a.rounded_md, a.border, t.atoms.border_contrast_low]}>
<View style={[a.flex_row, a.align_center, a.gap_xs, a.pb_md]}>
<TrendingIcon width={16} height={16} fill={t.atoms.text.color} />
<Text style={[a.flex_1, a.text_md, a.font_semi_bold, t.atoms.text]}>
<Trans>Trending</Trans>
</Text>
<Button
label={_(msg`Hide trending topics`)}
size="tiny"
variant="ghost"
size="tiny"
color="secondary"
shape="round"
onPress={() => trendingPrompt.open()}>
<ButtonIcon icon={X} />
label={_(msg`Trending options`)}
onPress={() => trendingPrompt.open()}
style={[a.bg_transparent, {marginTop: -6, marginRight: -6}]}>
<ButtonIcon icon={Ellipsis} size="xs" />
</Button>
</View>
<View style={[a.flex_row, a.flex_wrap, {gap: '6px 4px'}]}>
<View style={[a.gap_xs]}>
{isLoading ? (
Array(TRENDING_LIMIT)
.fill(0)
.map((_n, i) => (
<TrendingTopicSkeleton key={i} size="small" index={i} />
<View key={i} style={[a.flex_row, a.align_center, a.gap_sm]}>
<Text
style={[
a.text_sm,
t.atoms.text_contrast_low,
{minWidth: 16},
]}>
{i + 1}.
</Text>
<View
style={[
a.rounded_xs,
t.atoms.bg_contrast_50,
{height: 14, width: i % 2 === 0 ? 80 : 100},
]}
/>
</View>
))
) : !trending?.topics ? null : (
<>
{trending.topics.slice(0, TRENDING_LIMIT).map(topic => (
{trending.topics.slice(0, TRENDING_LIMIT).map((topic, i) => (
<TrendingTopicLink
key={topic.link}
topic={topic}
style={a.rounded_full}
style={[a.self_start]}
onPress={() => {
logEvent('trendingTopic:click', {context: 'sidebar'})
logger.metric('trendingTopic:click', {context: 'sidebar'})
}}>
{({hovered}) => (
<TrendingTopic
size="small"
topic={topic}
style={[
hovered && [
t.atoms.border_contrast_high,
t.atoms.bg_contrast_25,
],
]}
/>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Text
style={[
a.text_sm,
a.leading_snug,
t.atoms.text_contrast_low,
{minWidth: 16},
]}>
{i + 1}.
</Text>
<Text
style={[
a.text_sm,
a.leading_snug,
hovered
? [t.atoms.text, a.underline]
: t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
{topic.displayName ?? topic.topic}
</Text>
</View>
)}
</TrendingTopicLink>
))}
@@ -111,7 +129,6 @@ function Inner() {
confirmButtonCta={_(msg`Hide`)}
onConfirm={onConfirmHide}
/>
<Divider />
</>
)
}