diff options
author | Eric Bailey <git@esb.lol> | 2024-04-12 17:01:32 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-12 17:01:32 -0500 |
commit | 1f61109cfa8307cbbceea604b1daec7486dd3393 (patch) | |
tree | dbbad83a4367555e1586f6c2d5b0450612600d44 /src/view/com | |
parent | f91aa37c6bd900bdc4eec1095c9ecd83da2f13f2 (diff) | |
download | voidsky-1f61109cfa8307cbbceea604b1daec7486dd3393.tar.zst |
Profile card hover preview (#3508)
* feat: initial user card hover * feat: flesh it out some more * fix: initialize middlewares once * chore: remove floating-ui react-native * chore: clean up * Update moderation apis, fix lint * Refactor profile hover card to alf * Clean up * Debounce, fix positioning when loading * Fix going away * Close on all link presses * Tweak styles * Disable on mobile web * cleanup some of the changes pt. 1 * cleanup some of the changes pt. 2 * cleanup some of the changes pt. 3 * cleanup some of the changes pt. 4 * Re-revert files * Fix handle presentation * Don't follow yourself, silly * Collapsed notifications group * ProfileCard * Tree view replies * Suggested follows * Fix hover-back-on-card edge case * Moar --------- Co-authored-by: Mary <git@mary.my.id> Co-authored-by: Hailey <me@haileyok.com>
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 101 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 37 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeaderSuggestedFollows.tsx | 32 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 23 | ||||
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 42 | ||||
-rw-r--r-- | src/view/com/util/UserPreviewLink.tsx | 31 |
6 files changed, 133 insertions, 133 deletions
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 78b1677c3..e1dae6659 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -1,20 +1,20 @@ -import React, {memo, useMemo, useState, useEffect} from 'react' +import React, {memo, useEffect, useMemo, useState} from 'react' import { Animated, - TouchableOpacity, Pressable, StyleSheet, + TouchableOpacity, View, } from 'react-native' import { + AppBskyActorDefs, AppBskyEmbedImages, + AppBskyEmbedRecordWithMedia, AppBskyFeedDefs, AppBskyFeedPost, - ModerationOpts, - ModerationDecision, moderateProfile, - AppBskyEmbedRecordWithMedia, - AppBskyActorDefs, + ModerationDecision, + ModerationOpts, } from '@atproto/api' import {AtUri} from '@atproto/api' import { @@ -22,28 +22,30 @@ import { FontAwesomeIconStyle, Props, } from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import {FeedNotification} from '#/state/queries/notifications/feed' -import {s, colors} from 'lib/styles' -import {niceDate} from 'lib/strings/time' +import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' +import {usePalette} from 'lib/hooks/usePalette' +import {HeartIconSolid} from 'lib/icons' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {pluralize} from 'lib/strings/helpers' -import {HeartIconSolid} from 'lib/icons' -import {Text} from '../util/text/Text' -import {UserAvatar, PreviewableUserAvatar} from '../util/UserAvatar' -import {UserPreviewLink} from '../util/UserPreviewLink' -import {ImageHorzList} from '../util/images/ImageHorzList' +import {niceDate} from 'lib/strings/time' +import {colors, s} from 'lib/styles' +import {isWeb} from 'platform/detection' +import {Link as NewLink} from '#/components/Link' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import {FeedSourceCard} from '../feeds/FeedSourceCard' import {Post} from '../post/Post' +import {ImageHorzList} from '../util/images/ImageHorzList' import {Link, TextLink} from '../util/Link' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {formatCount} from '../util/numeric/format' -import {makeProfileLink} from 'lib/routes/links' +import {Text} from '../util/text/Text' import {TimeElapsed} from '../util/TimeElapsed' -import {isWeb} from 'platform/detection' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {FeedSourceCard} from '../feeds/FeedSourceCard' +import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar' const MAX_AUTHORS = 5 @@ -356,8 +358,10 @@ function CondensedAuthorsList({ <View style={styles.avis}> {authors.slice(0, MAX_AUTHORS).map(author => ( <View key={author.href} style={s.mr5}> - <UserAvatar + <PreviewableUserAvatar size={35} + did={author.did} + handle={author.handle} avatar={author.avatar} moderation={author.moderation.ui('avatar')} type={author.associated?.labeler ? 'labeler' : 'user'} @@ -386,6 +390,7 @@ function ExpandedAuthorsList({ visible: boolean authors: Author[] }) { + const {_} = useLingui() const pal = usePalette('default') const heightInterp = useAnimatedValue(visible ? 1 : 0) const targetHeight = @@ -409,33 +414,39 @@ function ExpandedAuthorsList({ visible ? s.mb10 : undefined, ]}> {authors.map(author => ( - <UserPreviewLink + <NewLink key={author.did} - did={author.did} - handle={author.handle} - style={styles.expandedAuthor}> - <View style={styles.expandedAuthorAvi}> - <UserAvatar - size={35} - avatar={author.avatar} - moderation={author.moderation.ui('avatar')} - type={author.associated?.labeler ? 'labeler' : 'user'} - /> - </View> - <View style={s.flex1}> - <Text - type="lg-bold" - numberOfLines={1} - style={pal.text} - lineHeight={1.2}> - {sanitizeDisplayName(author.displayName || author.handle)} - - <Text style={[pal.textLight]} lineHeight={1.2}> - {sanitizeHandle(author.handle)} + label={_(msg`See profile`)} + to={makeProfileLink({ + did: author.did, + handle: author.handle, + })}> + <View style={styles.expandedAuthor}> + <View style={styles.expandedAuthorAvi}> + <ProfileHoverCard did={author.did}> + <UserAvatar + size={35} + avatar={author.avatar} + moderation={author.moderation.ui('avatar')} + type={author.associated?.labeler ? 'labeler' : 'user'} + /> + </ProfileHoverCard> + </View> + <View style={s.flex1}> + <Text + type="lg-bold" + numberOfLines={1} + style={pal.text} + lineHeight={1.2}> + {sanitizeDisplayName(author.displayName || author.handle)} + + <Text style={[pal.textLight]} lineHeight={1.2}> + {sanitizeHandle(author.handle)} + </Text> </Text> - </Text> + </View> </View> - </UserPreviewLink> + </NewLink> ))} </Animated.View> ) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 235139fff..e6df5f6d0 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -6,22 +6,23 @@ import { ModerationCause, ModerationDecision, } from '@atproto/api' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {UserAvatar} from '../util/UserAvatar' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {FollowButton} from './FollowButton' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {getModerationCauseKey, isJustAMute} from 'lib/moderation' +import {Trans} from '@lingui/macro' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/queries/preferences' -import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' -import {Trans} from '@lingui/macro' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {usePalette} from 'lib/hooks/usePalette' +import {getModerationCauseKey, isJustAMute} from 'lib/moderation' +import {makeProfileLink} from 'lib/routes/links' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {s} from 'lib/styles' +import {Link} from '../util/Link' +import {Text} from '../util/text/Text' +import {PreviewableUserAvatar} from '../util/UserAvatar' +import {FollowButton} from './FollowButton' export function ProfileCard({ testID, @@ -76,8 +77,10 @@ export function ProfileCard({ anchorNoUnderline> <View style={styles.layout}> <View style={styles.layoutAvi}> - <UserAvatar + <PreviewableUserAvatar size={40} + did={profile.did} + handle={profile.handle} avatar={profile.avatar} moderation={moderation.ui('avatar')} type={isLabeler ? 'labeler' : 'user'} @@ -221,9 +224,11 @@ function FollowersList({ {followersWithMods.slice(0, 3).map(({f, mod}) => ( <View key={f.did} style={styles.followedByAviContainer}> <View style={[styles.followedByAvi, pal.view]}> - <UserAvatar - avatar={f.avatar} + <PreviewableUserAvatar size={32} + did={f.did} + handle={f.handle} + avatar={f.avatar} moderation={mod.ui('avatar')} type={f.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index 3602cdb9a..cf35885cd 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -1,28 +1,28 @@ import React from 'react' -import {View, StyleSheet, Pressable, ScrollView} from 'react-native' +import {Pressable, ScrollView, StyleSheet, View} from 'react-native' import {AppBskyActorDefs, moderateProfile} from '@atproto/api' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' -import * as Toast from '../util/Toast' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useModerationOpts} from '#/state/queries/preferences' +import {useProfileFollowMutationQueue} from '#/state/queries/profile' +import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' +import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' -import {Text} from 'view/com/util/text/Text' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Button} from 'view/com/util/forms/Button' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink} from 'lib/routes/links' -import {Link} from 'view/com/util/Link' -import {useAnalytics} from 'lib/analytics/analytics' import {isWeb} from 'platform/detection' -import {useModerationOpts} from '#/state/queries/preferences' -import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' -import {useProfileShadow} from '#/state/cache/profile-shadow' -import {useProfileFollowMutationQueue} from '#/state/queries/profile' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' +import {Button} from 'view/com/util/forms/Button' +import {Link} from 'view/com/util/Link' +import {Text} from 'view/com/util/text/Text' +import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' +import * as Toast from '../util/Toast' const OUTER_PADDING = 10 const INNER_PADDING = 14 @@ -218,8 +218,10 @@ function SuggestedFollow({ backgroundColor: pal.view.backgroundColor, }, ]}> - <UserAvatar + <PreviewableUserAvatar size={60} + did={profile.did} + handle={profile.handle} avatar={profile.avatar} moderation={moderation.ui('avatar')} /> diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 529fc54e0..b37c69448 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,18 +1,19 @@ import React, {memo} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import {Text} from './text/Text' -import {TextLinkOnWebOnly} from './Link' -import {niceDate} from 'lib/strings/time' +import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' + +import {usePrefetchProfileQuery} from '#/state/queries/profile' import {usePalette} from 'lib/hooks/usePalette' -import {TypographyVariant} from 'lib/ThemeContext' -import {UserAvatar} from './UserAvatar' +import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' +import {niceDate} from 'lib/strings/time' +import {TypographyVariant} from 'lib/ThemeContext' import {isAndroid, isWeb} from 'platform/detection' +import {TextLinkOnWebOnly} from './Link' +import {Text} from './text/Text' import {TimeElapsed} from './TimeElapsed' -import {makeProfileLink} from 'lib/routes/links' -import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' -import {usePrefetchProfileQuery} from '#/state/queries/profile' +import {PreviewableUserAvatar} from './UserAvatar' interface PostMetaOpts { author: AppBskyActorDefs.ProfileViewBasic @@ -38,9 +39,11 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { <View style={[styles.container, opts.style]}> {opts.showAvatar && ( <View style={styles.avatar}> - <UserAvatar - avatar={opts.author.avatar} + <PreviewableUserAvatar size={opts.avatarSize || 16} + did={opts.author.did} + handle={opts.author.handle} + avatar={opts.author.avatar} moderation={opts.avatarModeration} type={opts.author.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 4beedbd5b..89aa56b73 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -1,30 +1,32 @@ import React, {memo, useMemo} from 'react' import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' -import Svg, {Circle, Rect, Path} from 'react-native-svg' import {Image as RNImage} from 'react-native-image-crop-picker' -import {useLingui} from '@lingui/react' -import {msg, Trans} from '@lingui/macro' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import Svg, {Circle, Path, Rect} from 'react-native-svg' import {ModerationUI} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' -import {HighPriorityImage} from 'view/com/util/images/Image' -import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' +import {usePalette} from 'lib/hooks/usePalette' import { - usePhotoLibraryPermission, useCameraPermission, + usePhotoLibraryPermission, } from 'lib/hooks/usePermissions' +import {makeProfileLink} from 'lib/routes/links' import {colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {isWeb, isAndroid, isNative} from 'platform/detection' -import {UserPreviewLink} from './UserPreviewLink' -import * as Menu from '#/components/Menu' +import {isAndroid, isNative, isWeb} from 'platform/detection' +import {HighPriorityImage} from 'view/com/util/images/Image' +import {tokens, useTheme} from '#/alf' import { - Camera_Stroke2_Corner0_Rounded as Camera, Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, + Camera_Stroke2_Corner0_Rounded as Camera, } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' -import {useTheme, tokens} from '#/alf' +import {Link} from '#/components/Link' +import * as Menu from '#/components/Menu' +import {ProfileHoverCard} from '#/components/ProfileHoverCard' +import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' @@ -372,10 +374,18 @@ export {EditableUserAvatar} let PreviewableUserAvatar = ( props: PreviewableUserAvatarProps, ): React.ReactNode => { + const {_} = useLingui() return ( - <UserPreviewLink did={props.did} handle={props.handle}> - <UserAvatar {...props} /> - </UserPreviewLink> + <ProfileHoverCard did={props.did}> + <Link + label={_(msg`See profile`)} + to={makeProfileLink({ + did: props.did, + handle: props.handle, + })}> + <UserAvatar {...props} /> + </Link> + </ProfileHoverCard> ) } PreviewableUserAvatar = memo(PreviewableUserAvatar) diff --git a/src/view/com/util/UserPreviewLink.tsx b/src/view/com/util/UserPreviewLink.tsx deleted file mode 100644 index a2c46afc0..000000000 --- a/src/view/com/util/UserPreviewLink.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import {StyleProp, ViewStyle} from 'react-native' -import {Link} from './Link' -import {isWeb} from 'platform/detection' -import {makeProfileLink} from 'lib/routes/links' -import {usePrefetchProfileQuery} from '#/state/queries/profile' - -interface UserPreviewLinkProps { - did: string - handle: string - style?: StyleProp<ViewStyle> -} -export function UserPreviewLink( - props: React.PropsWithChildren<UserPreviewLinkProps>, -) { - const prefetchProfileQuery = usePrefetchProfileQuery() - return ( - <Link - onPointerEnter={() => { - if (isWeb) { - prefetchProfileQuery(props.did) - } - }} - href={makeProfileLink(props)} - title={props.handle} - asAnchor - style={props.style}> - {props.children} - </Link> - ) -} |