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 {useLingui} from '@lingui/react/macro'
5import {DropdownMenu} from 'radix-ui'
6
7import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
8import {useSession} from '#/state/session'
9import {atoms as a, flatten, useTheme} from '#/alf'
10import * as EmojiPicker from '#/components/EmojiPicker'
11import {DotGrid3x1_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid'
12import * as Menu from '#/components/Menu'
13import {Text} from '#/components/Typography'
14import {hasAlreadyReacted, hasReachedReactionLimit} from './util'
15
16export function EmojiReactionPicker({
17 message,
18 children,
19 onEmojiSelect,
20}: {
21 message: ChatBskyConvoDefs.MessageView
22 children?: EmojiPicker.TriggerProps['children']
23 onEmojiSelect: (emoji: string) => void
24}) {
25 if (!children)
26 throw new Error('EmojiReactionPicker requires the children prop on web')
27
28 const {t: l} = useLingui()
29
30 return (
31 <EmojiPicker.Root onEmojiSelect={emoji => onEmojiSelect(emoji.native)}>
32 <EmojiPicker.Trigger label={l`Add emoji reaction`}>
33 {children}
34 </EmojiPicker.Trigger>
35 <MenuInner message={message} onEmojiSelect={onEmojiSelect} />
36 </EmojiPicker.Root>
37 )
38}
39
40function MenuInner({
41 message,
42 onEmojiSelect,
43}: {
44 message: ChatBskyConvoDefs.MessageView
45 onEmojiSelect: (emoji: string) => void
46}) {
47 const t = useTheme()
48 const {control} = Menu.useMenuContext()
49 const {currentAccount} = useSession()
50
51 const [expanded, setExpanded] = useState(false)
52
53 const [prevOpen, setPrevOpen] = useState(control.isOpen)
54
55 const enableSquareButtons = useEnableSquareButtons()
56
57 if (control.isOpen !== prevOpen) {
58 setPrevOpen(control.isOpen)
59 if (!control.isOpen) {
60 setExpanded(false)
61 }
62 }
63
64 const handleEmojiSelect = (emoji: string) => {
65 control.close()
66 onEmojiSelect(emoji)
67 }
68
69 const limitReacted = hasReachedReactionLimit(message, currentAccount?.did)
70
71 return expanded ? (
72 <EmojiPicker.Picker keepOpenWhenShiftHeld={false} />
73 ) : (
74 <Menu.Outer style={[enableSquareButtons ? a.rounded_sm : a.rounded_full]}>
75 <View style={[a.flex_row, a.gap_xs]}>
76 {['👍', '😆', '❤️', '👀', '😢'].map(emoji => {
77 const alreadyReacted = hasAlreadyReacted(
78 message,
79 currentAccount?.did,
80 emoji,
81 )
82 return (
83 <DropdownMenu.Item
84 key={emoji}
85 className={[
86 'EmojiReactionPicker__Pressable',
87 alreadyReacted && '__selected',
88 limitReacted && '__disabled',
89 ]
90 .filter(Boolean)
91 .join(' ')}
92 onSelect={() => handleEmojiSelect(emoji)}
93 style={flatten([
94 a.flex,
95 a.flex_col,
96 enableSquareButtons ? a.rounded_sm : a.rounded_full,
97 a.justify_center,
98 a.align_center,
99 a.transition_transform,
100 {
101 width: 34,
102 height: 34,
103 },
104 alreadyReacted && {
105 backgroundColor: t.atoms.bg_contrast_100.backgroundColor,
106 },
107 ])}>
108 <Text style={[a.text_center, {fontSize: 28}]} emoji>
109 {emoji}
110 </Text>
111 </DropdownMenu.Item>
112 )
113 })}
114 <DropdownMenu.Item
115 asChild
116 className="EmojiReactionPicker__PickerButton">
117 <Pressable
118 accessibilityRole="button"
119 role="button"
120 onPress={() => setExpanded(true)}
121 style={flatten([
122 enableSquareButtons ? a.rounded_sm : a.rounded_full,
123 {height: 34, width: 34},
124 a.justify_center,
125 a.align_center,
126 ])}>
127 <DotGridIcon size="lg" style={t.atoms.text_contrast_medium} />
128 </Pressable>
129 </DropdownMenu.Item>
130 </View>
131 </Menu.Outer>
132 )
133}