forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}