Bluesky app fork with some witchin' additions 馃挮
at feat/tealfm 152 lines 4.7 kB view raw
1import {useState} from 'react' 2import {Pressable, View} from 'react-native' 3import {type ChatBskyConvoDefs} from '@atproto/api' 4import EmojiPicker from '@emoji-mart/react' 5import {msg} from '@lingui/macro' 6import {useLingui} from '@lingui/react' 7import {DropdownMenu} from 'radix-ui' 8 9import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 10import {useSession} from '#/state/session' 11import {type Emoji} from '#/view/com/composer/text-input/web/EmojiPicker' 12import {useWebPreloadEmoji} from '#/view/com/composer/text-input/web/useWebPreloadEmoji' 13import {atoms as a, flatten, useTheme} from '#/alf' 14import {DotGrid_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid' 15import * as Menu from '#/components/Menu' 16import {type TriggerProps} from '#/components/Menu/types' 17import {Text} from '#/components/Typography' 18import {hasAlreadyReacted, hasReachedReactionLimit} from './util' 19 20export function EmojiReactionPicker({ 21 message, 22 children, 23 onEmojiSelect, 24}: { 25 message: ChatBskyConvoDefs.MessageView 26 children?: TriggerProps['children'] 27 onEmojiSelect: (emoji: string) => void 28}) { 29 if (!children) 30 throw new Error('EmojiReactionPicker requires the children prop on web') 31 32 const {_} = useLingui() 33 34 return ( 35 <Menu.Root> 36 <Menu.Trigger label={_(msg`Add emoji reaction`)}>{children}</Menu.Trigger> 37 <MenuInner message={message} onEmojiSelect={onEmojiSelect} /> 38 </Menu.Root> 39 ) 40} 41 42function MenuInner({ 43 message, 44 onEmojiSelect, 45}: { 46 message: ChatBskyConvoDefs.MessageView 47 onEmojiSelect: (emoji: string) => void 48}) { 49 const t = useTheme() 50 const {control} = Menu.useMenuContext() 51 const {currentAccount} = useSession() 52 53 useWebPreloadEmoji({immediate: true}) 54 55 const [expanded, setExpanded] = useState(false) 56 57 const [prevOpen, setPrevOpen] = useState(control.isOpen) 58 59 const enableSquareButtons = useEnableSquareButtons() 60 61 if (control.isOpen !== prevOpen) { 62 setPrevOpen(control.isOpen) 63 if (!control.isOpen) { 64 setExpanded(false) 65 } 66 } 67 68 const handleEmojiPickerResponse = (emoji: Emoji) => { 69 handleEmojiSelect(emoji.native) 70 } 71 72 const handleEmojiSelect = (emoji: string) => { 73 control.close() 74 onEmojiSelect(emoji) 75 } 76 77 const limitReacted = hasReachedReactionLimit(message, currentAccount?.did) 78 79 return expanded ? ( 80 <DropdownMenu.Portal> 81 <DropdownMenu.Content 82 sideOffset={5} 83 collisionPadding={{left: 5, right: 5, bottom: 5}}> 84 <div onWheel={evt => evt.stopPropagation()}> 85 <EmojiPicker 86 onEmojiSelect={handleEmojiPickerResponse} 87 autoFocus={true} 88 /> 89 </div> 90 </DropdownMenu.Content> 91 </DropdownMenu.Portal> 92 ) : ( 93 <Menu.Outer style={[enableSquareButtons ? a.rounded_sm : a.rounded_full]}> 94 <View style={[a.flex_row, a.gap_xs]}> 95 {['馃憤', '馃槅', '鉂わ笍', '馃憖', '馃槩'].map(emoji => { 96 const alreadyReacted = hasAlreadyReacted( 97 message, 98 currentAccount?.did, 99 emoji, 100 ) 101 return ( 102 <DropdownMenu.Item 103 key={emoji} 104 className={[ 105 'EmojiReactionPicker__Pressable', 106 alreadyReacted && '__selected', 107 limitReacted && '__disabled', 108 ] 109 .filter(Boolean) 110 .join(' ')} 111 onSelect={() => handleEmojiSelect(emoji)} 112 style={flatten([ 113 a.flex, 114 a.flex_col, 115 enableSquareButtons ? a.rounded_sm : a.rounded_full, 116 a.justify_center, 117 a.align_center, 118 a.transition_transform, 119 { 120 width: 34, 121 height: 34, 122 }, 123 alreadyReacted && { 124 backgroundColor: t.atoms.bg_contrast_100.backgroundColor, 125 }, 126 ])}> 127 <Text style={[a.text_center, {fontSize: 28}]} emoji> 128 {emoji} 129 </Text> 130 </DropdownMenu.Item> 131 ) 132 })} 133 <DropdownMenu.Item 134 asChild 135 className="EmojiReactionPicker__PickerButton"> 136 <Pressable 137 accessibilityRole="button" 138 role="button" 139 onPress={() => setExpanded(true)} 140 style={flatten([ 141 enableSquareButtons ? a.rounded_sm : a.rounded_full, 142 {height: 34, width: 34}, 143 a.justify_center, 144 a.align_center, 145 ])}> 146 <DotGridIcon size="lg" style={t.atoms.text_contrast_medium} /> 147 </Pressable> 148 </DropdownMenu.Item> 149 </View> 150 </Menu.Outer> 151 ) 152}