this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at e28f6d2f370b4e882ed6f23d08ca0f8d94dbac5f 137 lines 4.3 kB view raw
1import {useMemo, useState} from 'react' 2import {useWindowDimensions, View} from 'react-native' 3import {type ChatBskyConvoDefs} from '@atproto/api' 4import {msg} from '@lingui/core/macro' 5import {useLingui} from '@lingui/react' 6 7import {useSession} from '#/state/session' 8import {atoms as a, tokens, useTheme} from '#/alf' 9import * as ContextMenu from '#/components/ContextMenu' 10import { 11 useContextMenuContext, 12 useContextMenuMenuContext, 13} from '#/components/ContextMenu/context' 14import { 15 EmojiHeartEyes_Stroke2_Corner0_Rounded as EmojiHeartEyesIcon, 16 EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon, 17} from '#/components/icons/Emoji' 18import {type TriggerProps} from '#/components/Menu/types' 19import {Text} from '#/components/Typography' 20import {EmojiPopup} from './EmojiPopup' 21import {hasAlreadyReacted, hasReachedReactionLimit} from './util' 22 23export function EmojiReactionPicker({ 24 message, 25 onEmojiSelect, 26}: { 27 message: ChatBskyConvoDefs.MessageView 28 children?: TriggerProps['children'] 29 onEmojiSelect: (emoji: string) => void 30}) { 31 const {_} = useLingui() 32 const {currentAccount} = useSession() 33 const t = useTheme() 34 const isFromSelf = message.sender?.did === currentAccount?.did 35 const {measurement, close} = useContextMenuContext() 36 const {align} = useContextMenuMenuContext() 37 const [layout, setLayout] = useState({width: 0, height: 0}) 38 const {width: screenWidth} = useWindowDimensions() 39 40 // 1 in 100 chance of showing heart eyes icon 41 const EmojiIcon = useMemo(() => { 42 return Math.random() < 0.01 ? EmojiHeartEyesIcon : EmojiSmileIcon 43 }, []) 44 45 const position = useMemo(() => { 46 return { 47 x: align === 'left' ? 12 : screenWidth - layout.width - 12, 48 y: (measurement?.y ?? 0) - tokens.space.xs - layout.height, 49 height: layout.height, 50 width: layout.width, 51 } 52 }, [measurement, align, screenWidth, layout]) 53 54 const limitReacted = hasReachedReactionLimit(message, currentAccount?.did) 55 56 const bgColor = t.scheme === 'light' ? t.atoms.bg : t.atoms.bg_contrast_25 57 58 return ( 59 <View 60 onLayout={evt => setLayout(evt.nativeEvent.layout)} 61 style={[ 62 bgColor, 63 a.rounded_full, 64 a.absolute, 65 {bottom: '100%'}, 66 isFromSelf ? a.right_0 : a.left_0, 67 a.flex_row, 68 a.p_xs, 69 a.gap_xs, 70 a.mb_xs, 71 a.z_20, 72 a.border, 73 t.atoms.border_contrast_low, 74 a.shadow_md, 75 ]}> 76 {['👍', '😆', '❤️', '👀', '😢'].map(emoji => { 77 const alreadyReacted = hasAlreadyReacted( 78 message, 79 currentAccount?.did, 80 emoji, 81 ) 82 return ( 83 <ContextMenu.Item 84 position={position} 85 label={_(msg`React with ${emoji}`)} 86 key={emoji} 87 onPress={() => onEmojiSelect(emoji)} 88 unstyled 89 disabled={limitReacted ? !alreadyReacted : false}> 90 {hovered => ( 91 <View 92 style={[ 93 a.rounded_full, 94 hovered 95 ? { 96 backgroundColor: alreadyReacted 97 ? t.palette.negative_100 98 : t.palette.primary_500, 99 } 100 : alreadyReacted 101 ? {backgroundColor: t.palette.primary_200} 102 : bgColor, 103 {height: 40, width: 40}, 104 a.justify_center, 105 a.align_center, 106 ]}> 107 <Text style={[a.text_center, {fontSize: 30}]} emoji> 108 {emoji} 109 </Text> 110 </View> 111 )} 112 </ContextMenu.Item> 113 ) 114 })} 115 <EmojiPopup 116 onEmojiSelected={emoji => { 117 close() 118 onEmojiSelect(emoji) 119 }}> 120 <View 121 style={[ 122 a.rounded_full, 123 t.scheme === 'light' 124 ? t.atoms.bg_contrast_25 125 : t.atoms.bg_contrast_50, 126 {height: 40, width: 40}, 127 a.justify_center, 128 a.align_center, 129 a.border, 130 t.atoms.border_contrast_low, 131 ]}> 132 <EmojiIcon size="xl" fill={t.palette.contrast_400} /> 133 </View> 134 </EmojiPopup> 135 </View> 136 ) 137}