diff options
-rw-r--r-- | src/components/FeedInterstitials.tsx | 201 | ||||
-rw-r--r-- | src/components/ProfileCard.tsx | 45 |
2 files changed, 161 insertions, 85 deletions
diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index a92e7be7f..2a3a00ba7 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -25,18 +25,17 @@ import { type ViewStyleProp, web, } from '#/alf' -import {Button} from '#/components/Button' +import {Button, ButtonText} from '#/components/Button' import * as FeedCard from '#/components/FeedCard' import {ArrowRight_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' -import {PersonPlus_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' import {InlineLinkText} from '#/components/Link' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' import {ProgressGuideList} from './ProgressGuide/List' -const MOBILE_CARD_WIDTH = 300 +const MOBILE_CARD_WIDTH = 165 function CardOuter({ children, @@ -48,8 +47,8 @@ function CardOuter({ <View style={[ a.w_full, - a.p_lg, - a.rounded_md, + a.p_md, + a.rounded_lg, a.border, t.atoms.bg, t.atoms.border_contrast_low, @@ -65,14 +64,30 @@ function CardOuter({ export function SuggestedFollowPlaceholder() { const t = useTheme() + return ( - <CardOuter style={[a.gap_md, t.atoms.border_contrast_low]}> - <ProfileCard.Header> - <ProfileCard.AvatarPlaceholder /> - <ProfileCard.NameAndHandlePlaceholder /> - </ProfileCard.Header> + <CardOuter + style={[a.gap_md, t.atoms.border_contrast_low, t.atoms.shadow_sm]}> + <ProfileCard.Outer> + <View + style={[a.flex_col, a.align_center, a.gap_sm, a.pb_sm, a.mb_auto]}> + <ProfileCard.AvatarPlaceholder size={88} /> + <ProfileCard.NamePlaceholder /> + <View style={[a.w_full]}> + <ProfileCard.DescriptionPlaceholder numberOfLines={2} /> + </View> + </View> - <ProfileCard.DescriptionPlaceholder numberOfLines={2} /> + <Button + label="" + size="small" + variant="solid" + color="secondary" + disabled + style={[a.w_full, a.rounded_sm]}> + <ButtonText>Follow</ButtonText> + </Button> + </ProfileCard.Outer> </CardOuter> ) } @@ -243,10 +258,9 @@ export function ProfileGrid({ const t = useTheme() const {_} = useLingui() const moderationOpts = useModerationOpts() - const navigation = useNavigation<NavigationProp>() const {gtMobile} = useBreakpoints() const isLoading = isSuggestionsLoading || !moderationOpts - const maxLength = gtMobile ? 4 : 6 + const maxLength = gtMobile ? 3 : 6 const content = isLoading ? ( Array(maxLength) @@ -254,7 +268,14 @@ export function ProfileGrid({ .map((_, i) => ( <View key={i} - style={[gtMobile && web([a.flex_0, {width: 'calc(50% - 6px)'}])]}> + style={[ + gtMobile && + web([ + a.flex_0, + a.flex_grow, + {width: `calc(30% - ${a.gap_md.gap / 2}px)`}, + ]), + ]}> <SuggestedFollowPlaceholder /> </View> )) @@ -276,44 +297,69 @@ export function ProfileGrid({ }} style={[ a.flex_1, - gtMobile && web([a.flex_0, {width: 'calc(50% - 6px)'}]), + gtMobile && + web([ + a.flex_0, + a.flex_grow, + {width: `calc(30% - ${a.gap_md.gap / 2}px)`}, + ]), ]}> {({hovered, pressed}) => ( <CardOuter style={[ a.flex_1, + t.atoms.shadow_sm, (hovered || pressed) && t.atoms.border_contrast_high, ]}> <ProfileCard.Outer> - <ProfileCard.Header> + <View + style={[ + a.flex_col, + a.align_center, + a.gap_sm, + a.pb_sm, + a.mb_auto, + ]}> <ProfileCard.Avatar profile={profile} moderationOpts={moderationOpts} + size={88} /> - <ProfileCard.NameAndHandle - profile={profile} - moderationOpts={moderationOpts} - /> - <ProfileCard.FollowButton - profile={profile} - moderationOpts={moderationOpts} - logContext="FeedInterstitial" - shape="round" - colorInverted - onFollow={() => { - logEvent('suggestedUser:follow', { - logContext: - viewContext === 'feed' - ? 'InterstitialDiscover' - : 'InterstitialProfile', - location: 'Card', - recId, - position: index, - }) - }} - /> - </ProfileCard.Header> - <ProfileCard.Description profile={profile} numberOfLines={2} /> + <View style={[a.flex_col, a.align_center, a.max_w_full]}> + <ProfileCard.Name + profile={profile} + moderationOpts={moderationOpts} + /> + <ProfileCard.Description + profile={profile} + numberOfLines={2} + style={[ + t.atoms.text_contrast_medium, + a.text_center, + a.text_xs, + ]} + /> + </View> + </View> + + <ProfileCard.FollowButton + profile={profile} + moderationOpts={moderationOpts} + logContext="FeedInterstitial" + withIcon={false} + style={[a.rounded_sm]} + onFollow={() => { + logEvent('suggestedUser:follow', { + logContext: + viewContext === 'feed' + ? 'InterstitialDiscover' + : 'InterstitialProfile', + location: 'Card', + recId, + position: index, + }) + }} + /> </ProfileCard.Outer> </CardOuter> )} @@ -333,36 +379,30 @@ export function ProfileGrid({ <View style={[ a.p_lg, - a.pb_xs, + a.py_md, a.flex_row, a.align_center, a.justify_between, ]}> - <Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}> + <Text style={[a.text_sm, a.font_bold, t.atoms.text]}> {viewContext === 'profile' ? ( <Trans>Similar accounts</Trans> ) : ( <Trans>Suggested for you</Trans> )} </Text> - <Person fill={t.atoms.text_contrast_low.color} size="sm" /> + <InlineLinkText + label={_(msg`See more suggested profiles on the Explore page`)} + to="/search"> + <Trans>See more</Trans> + </InlineLinkText> </View> {gtMobile ? ( - <View style={[a.flex_1, a.px_lg, a.pt_sm, a.pb_lg, a.gap_md]}> - <View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_sm]}> + <View style={[a.px_lg, a.pb_lg]}> + <View style={[a.flex_1, a.flex_row, a.flex_wrap, a.gap_md]}> {content} </View> - - <View style={[a.flex_row, a.justify_end, a.align_center, a.gap_md]}> - <InlineLinkText - label={_(msg`Browse more suggestions`)} - to="/search" - style={[t.atoms.text_contrast_medium]}> - <Trans>Browse more suggestions</Trans> - </InlineLinkText> - <Arrow size="sm" fill={t.atoms.text_contrast_medium.color} /> - </View> </View> ) : ( <BlockDrawerGesture> @@ -371,29 +411,12 @@ export function ProfileGrid({ horizontal showsHorizontalScrollIndicator={false} snapToInterval={MOBILE_CARD_WIDTH + a.gap_md.gap} - decelerationRate="fast"> - <View style={[a.px_lg, a.pt_sm, a.pb_lg, a.flex_row, a.gap_md]}> + decelerationRate="fast" + style={[a.overflow_visible]}> + <View style={[a.px_lg, a.pb_lg, a.flex_row, a.gap_md]}> {content} - <Button - label={_(msg`Browse more accounts on the Explore page`)} - onPress={() => { - navigation.navigate('SearchTab') - }}> - <CardOuter style={[a.flex_1, {borderWidth: 0}]}> - <View style={[a.flex_1, a.justify_center]}> - <View style={[a.flex_row, a.px_lg]}> - <Text style={[a.pr_xl, a.flex_1, a.leading_snug]}> - <Trans> - Browse more suggestions on the Explore page - </Trans> - </Text> - - <Arrow size="xl" /> - </View> - </View> - </CardOuter> - </Button> + <SeeMoreSuggestedProfilesCard /> </View> </ScrollView> </View> @@ -403,6 +426,32 @@ export function ProfileGrid({ ) } +function SeeMoreSuggestedProfilesCard() { + const navigation = useNavigation<NavigationProp>() + const t = useTheme() + const {_} = useLingui() + + return ( + <Button + label={_(msg`Browse more accounts on the Explore page`)} + onPress={() => { + navigation.navigate('SearchTab') + }}> + <CardOuter style={[a.flex_1, t.atoms.shadow_sm]}> + <View style={[a.flex_1, a.justify_center]}> + <View style={[a.flex_col, a.align_center, a.gap_md]}> + <Text style={[a.leading_snug, a.text_center]}> + <Trans>See more accounts you might like</Trans> + </Text> + + <Arrow size="xl" /> + </View> + </View> + </CardOuter> + </Button> + ) +} + export function SuggestedFeeds() { const numFeedsToDisplay = 3 const t = useTheme() diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index e01c27655..f12d922fd 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -20,7 +20,13 @@ import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {PreviewableUserAvatar, UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, platform, useTheme} from '#/alf' +import { + atoms as a, + platform, + type TextStyleProp, + useTheme, + type ViewStyleProp, +} from '#/alf' import { Button, ButtonIcon, @@ -136,12 +142,14 @@ export function Avatar({ onPress, disabledPreview, liveOverride, + size = 40, }: { profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts onPress?: () => void disabledPreview?: boolean liveOverride?: boolean + size?: number }) { const moderation = moderateProfile(profile, moderationOpts) @@ -149,7 +157,7 @@ export function Avatar({ return disabledPreview ? ( <UserAvatar - size={40} + size={size} avatar={profile.avatar} type={profile.associated?.labeler ? 'labeler' : 'user'} moderation={moderation.ui('avatar')} @@ -157,7 +165,7 @@ export function Avatar({ /> ) : ( <PreviewableUserAvatar - size={40} + size={size} profile={profile} moderation={moderation.ui('avatar')} onBeforePress={onPress} @@ -166,7 +174,7 @@ export function Avatar({ ) } -export function AvatarPlaceholder() { +export function AvatarPlaceholder({size = 40}: {size?: number}) { const t = useTheme() return ( <View @@ -174,8 +182,8 @@ export function AvatarPlaceholder() { a.rounded_full, t.atoms.bg_contrast_25, { - width: 40, - height: 40, + width: size, + height: size, }, ]} /> @@ -274,7 +282,7 @@ export function Name({ ) const verification = useSimpleVerificationState({profile}) return ( - <View style={[a.flex_row, a.align_center]}> + <View style={[a.flex_row, a.align_center, a.max_w_full]}> <Text emoji style={[ @@ -343,13 +351,32 @@ export function NameAndHandlePlaceholder() { ) } +export function NamePlaceholder({style}: ViewStyleProp) { + const t = useTheme() + + return ( + <View + style={[ + a.rounded_xs, + t.atoms.bg_contrast_25, + { + width: '60%', + height: 14, + }, + style, + ]} + /> + ) +} + export function Description({ profile: profileUnshadowed, numberOfLines = 3, + style, }: { profile: bsky.profile.AnyProfileView numberOfLines?: number -}) { +} & TextStyleProp) { const profile = useProfileShadow(profileUnshadowed) const rt = useMemo(() => { if (!('description' in profile)) return @@ -369,7 +396,7 @@ export function Description({ <View style={[a.pt_xs]}> <RichText value={rt} - style={[a.leading_snug]} + style={[a.leading_snug, style]} numberOfLines={numberOfLines} disableLinks /> |