diff options
author | Hailey <me@haileyok.com> | 2024-05-09 09:07:57 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-09 09:07:57 -0700 |
commit | 2fe76333bc6161d78c9f9b43f4855ff6919de507 (patch) | |
tree | c33e1df9f7b2829df62173fd956e45a45c05cdc8 | |
parent | 55ac287d5e822a24c201d6f756b2d0ab76df242c (diff) | |
download | voidsky-2fe76333bc6161d78c9f9b43f4855ff6919de507.tar.zst |
[🐴] Add hover context menu for convo list on web (#3923)
* remove some unnecessary props * add hover trigger on web for convo list * lint * use `UserAvatar` to not affect accessibility * remove extra wrapper * add `label` * always show on mobile * adjust size of dots * make the message trigger dots the same size * ❓
-rw-r--r-- | src/components/dms/ActionsWrapper.web.tsx | 6 | ||||
-rw-r--r-- | src/components/dms/ConvoMenu.tsx | 50 | ||||
-rw-r--r-- | src/components/dms/MessageMenu.tsx | 7 | ||||
-rw-r--r-- | src/screens/Messages/List/index.tsx | 51 |
4 files changed, 71 insertions, 43 deletions
diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index f4c85ab94..5cb30d3da 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -57,9 +57,6 @@ export function ActionsWrapper({ message={message} control={menuControl} triggerOpacity={showActions || menuControl.isOpen ? 1 : 0} - onTriggerPress={onMouseEnter} - // @ts-expect-error web only - onMouseLeave={onMouseLeave} /> </View> )} @@ -75,9 +72,6 @@ export function ActionsWrapper({ message={message} control={menuControl} triggerOpacity={showActions || menuControl.isOpen ? 1 : 0} - onTriggerPress={onMouseEnter} - // @ts-expect-error web only - onMouseLeave={onMouseLeave} /> </View> )} diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 263befd56..cac4eb4d9 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -1,5 +1,5 @@ import React, {useCallback} from 'react' -import {Keyboard, Pressable} from 'react-native' +import {Keyboard, Pressable, View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' import {ChatBskyConvoDefs} from '@atproto-labs/api' import {msg, Trans} from '@lingui/macro' @@ -32,17 +32,19 @@ let ConvoMenu = ({ profile, onUpdateConvo, control, - hideTrigger, currentScreen, showMarkAsRead, + hideTrigger, + triggerOpacity, }: { convo: ChatBskyConvoDefs.ConvoView profile: AppBskyActorDefs.ProfileViewBasic onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void control?: Menu.MenuControlProps - hideTrigger?: boolean currentScreen: 'list' | 'conversation' showMarkAsRead?: boolean + hideTrigger?: boolean + triggerOpacity?: number }): React.ReactNode => { const navigation = useNavigation<NavigationProp>() const {_} = useLingui() @@ -89,26 +91,28 @@ let ConvoMenu = ({ <> <Menu.Root control={control}> {!hideTrigger && ( - <Menu.Trigger label={_(msg`Chat settings`)}> - {({props, state}) => ( - <Pressable - {...props} - onPress={() => { - Keyboard.dismiss() - // eslint-disable-next-line react/prop-types -- eslint is confused by the name `props` - props.onPress() - }} - style={[ - a.p_sm, - a.rounded_sm, - (state.hovered || state.pressed) && t.atoms.bg_contrast_25, - // make sure pfp is in the middle - {marginLeft: -10}, - ]}> - <DotsHorizontal size="lg" style={t.atoms.text} /> - </Pressable> - )} - </Menu.Trigger> + <View style={{opacity: triggerOpacity}}> + <Menu.Trigger label={_(msg`Chat settings`)}> + {({props, state}) => ( + <Pressable + {...props} + onPress={() => { + Keyboard.dismiss() + // eslint-disable-next-line react/prop-types -- eslint is confused by the name `props` + props.onPress() + }} + style={[ + a.p_sm, + a.rounded_full, + (state.hovered || state.pressed) && t.atoms.bg_contrast_25, + // make sure pfp is in the middle + {marginLeft: -10}, + ]}> + <DotsHorizontal size="md" style={t.atoms.text} /> + </Pressable> + )} + </Menu.Trigger> + </View> )} <Menu.Outer> <Menu.Group> diff --git a/src/components/dms/MessageMenu.tsx b/src/components/dms/MessageMenu.tsx index 3a5fa54d8..75807f818 100644 --- a/src/components/dms/MessageMenu.tsx +++ b/src/components/dms/MessageMenu.tsx @@ -5,6 +5,7 @@ import {ChatBskyConvoDefs} from '@atproto-labs/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isWeb} from 'platform/detection' import {useConvo} from 'state/messages/convo' import {ConvoStatus} from 'state/messages/convo/types' import {useSession} from 'state/session' @@ -21,12 +22,10 @@ import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '../icons/Clipb export let MessageMenu = ({ message, control, - hideTrigger, triggerOpacity, }: { hideTrigger?: boolean triggerOpacity?: number - onTriggerPress?: () => void message: ChatBskyConvoDefs.MessageView control: Menu.MenuControlProps }): React.ReactNode => { @@ -64,7 +63,7 @@ export let MessageMenu = ({ return ( <> <Menu.Root control={control}> - {!hideTrigger && ( + {isWeb && ( <View style={{opacity: triggerOpacity}}> <Menu.Trigger label={_(msg`Chat settings`)}> {({props, state}) => ( @@ -75,7 +74,7 @@ export let MessageMenu = ({ a.rounded_full, (state.hovered || state.pressed) && t.atoms.bg_contrast_25, ]}> - <DotsHorizontal size="sm" style={t.atoms.text} /> + <DotsHorizontal size="md" style={t.atoms.text} /> </Pressable> )} </Menu.Trigger> diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index a82f2f00a..4d218bda8 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -5,11 +5,12 @@ import {View} from 'react-native' import {ChatBskyConvoDefs} from '@atproto-labs/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {sha256} from 'js-sha256' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' -import {MessagesTabNavigatorParams} from '#/lib/routes/types' +import {MessagesTabNavigatorParams, NavigationProp} from '#/lib/routes/types' import {useGate} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -18,7 +19,7 @@ import {useListConvos} from '#/state/queries/messages/list-converations' import {useSession} from '#/state/session' import {List} from '#/view/com/util/List' import {TimeElapsed} from '#/view/com/util/TimeElapsed' -import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' +import {UserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {CenteredView} from '#/view/com/util/Views' import {ScrollView} from '#/view/com/util/Views' @@ -239,6 +240,7 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { const {_} = useLingui() const {currentAccount} = useSession() const menuControl = useMenuControl() + const {gtMobile} = useBreakpoints() let lastMessage = _(msg`No messages yet`) let lastMessageSentAt: string | null = null @@ -258,15 +260,43 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { member => member.did !== currentAccount?.did, ) + const navigation = useNavigation<NavigationProp>() + const [showActions, setShowActions] = React.useState(false) + + const onMouseEnter = React.useCallback(() => { + setShowActions(true) + }, []) + + const onMouseLeave = React.useCallback(() => { + setShowActions(false) + }, []) + + const onFocus = React.useCallback<React.FocusEventHandler>(e => { + if (e.nativeEvent.relatedTarget == null) return + setShowActions(true) + }, []) + + const onPress = React.useCallback(() => { + navigation.push('MessagesConversation', { + conversation: convo.id, + }) + }, [convo.id, navigation]) + if (!otherUser) { return null } return ( - <Link - to={`/messages/${convo.id}`} + <Button + label={otherUser.displayName || otherUser.handle} + onPress={onPress} style={a.flex_1} - onLongPress={isNative ? menuControl.open : undefined}> + onLongPress={isNative ? menuControl.open : undefined} + // @ts-expect-error web only + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + onFocus={onFocus} + onBlur={onMouseLeave}> {({hovered, pressed}) => ( <View style={[ @@ -279,7 +309,7 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { (hovered || pressed) && t.atoms.bg_contrast_25, ]}> <View pointerEvents="none"> - <PreviewableUserAvatar profile={otherUser} size={42} /> + <UserAvatar avatar={otherUser?.avatar} size={42} /> </View> <View style={[a.flex_1]}> <Text @@ -336,15 +366,16 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { convo={convo} profile={otherUser} control={menuControl} - // TODO(sam) show on hover on web - // tricky because it captures the mouse event - hideTrigger currentScreen="list" showMarkAsRead={convo.unreadCount > 0} + hideTrigger={isNative} + triggerOpacity={ + !gtMobile || showActions || menuControl.isOpen ? 1 : 0 + } /> </View> )} - </Link> + </Button> ) } |